Introduction
This article is meant to be a step by step guide to give its reader a gist
of .Net Remoting. I've tried to search good tutorials, but it was rather hard
to find one that could work on my PC! So here's my first shot at a .Net Remoting
tutorial in VB.net. It shouldn't be hard to get it running, if you'd follow
this few steps.
Background
It's important for you to understand what's going on before we dive into the
code (don't worry, it's not really hard). First off, we will have a remotable
object called Manager, which will be a singleton object (defined later). Clients
will send their messages by calling a method in the one and only one (singleton)
Manager object called SendText , which in turn will
raise an event, evtReceiveText . This event will be handled by all
the chat clients, which then will display the received message on the textbox,
txtReceivedMsgs .
OK, let's conceptualize it, we have one and only one remotable
object, called the Manager, from the namespace InBetween (which conceptually,
sits in between the server and client, serializing messages to and fro). Next,
we have one Server, which will register or create a new instance our own well
known service or application, called ChatApplication . Next, we
will have the clients themselves, who will implement the Manager 's
event called, evtReceiveText , which is an event raised to all clients
when anyone sends any message.
Now, for those who've done some remoting, I'm not going to use a config file
(an XML file to configure the application's name and port, etc), but rather
I'll be using these two:
On the client side, to get the Manager object (more comments on as we go on):
theManager = CType(Activator.GetObject(Type.GetType("InBetween.Manager,InBetween"),
"http://UP:7777/ChatApplication"), InBetween.Manager)
On the server side, to register the well known service (more comments
on as we go on):
System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownServiceType(
_
Type.GetType("InBetween.Manager, InBetween"), _
"ChatApplication", WellKnownObjectMode.Singleton)
Using the code (Manager.vb)
(Note: I've included the .config files, just in case you want to try using
them)
1. To start, create an empty solution.
2. Add a new class library project called InBetween .
3. Change the default .vb name to Manager.vb
4 . Copy the code below (Be sure to read the comments as we go on)
This guy down here is the remotable object.
Imports System
'We are going to declare an event delegate
'Event delegates sound really big... but what this is doing is that:
' "I want to be able, from this class, to raise a type of an event called
'ReceiveText..."
' "And, I want those who use Manager class to handle that event"
' "...Now, I'm going to pass username As String and text As String."
' "It's gonna be up to you, how you're going to handle it"
Public Delegate Sub ReceiveText(ByVal username As String, ByVal text As String)
Public Class Manager
Inherits MarshalByRefObject
'Why inherit MarshalByRefObject?
'Enables access to objects across application domain boundaries
'in applications that support remoting.
'Src: .NET Framework Class Library MarshalByRefObject Class [Visual Basic]
'Let's break it down (in simple english)...
'MarshalByRefObject is the means which allows objects like this class here,
to
'communicate across boundaries, via remoting.
'An application domain is a partition in an operating system process where one
or more applications reside.
'What's this? I thought we already declared an event handler?
'Here's where we need to declare the event itself.
'Delegates, as its name suggests are 'ambassadors' who advertises this event
'That's why in our Client.vb, we say "theManager.evtReceiveText",
and not 'theManager.ReceiveText'
Public Event evtReceiveText As ReceiveText
Public Overrides Function InitializeLifetimeService() As Object
'This function, if overriden, allows us to tell the Manager object how long
'it should live. If by any reason we'd like to have it stay a 'lil longer, we
renew it and so forth
'We won't do anything here. So what happens is that
'the Manager object is governed by a default lifetime.
Return Nothing
End Function
Public Function SendText(ByVal username As String, ByVal text As String)
'Later in the client.vb code, you would see that chat clients (like John Doe),
will
'raise thise event by calling SendText with the appropriate paramaters.
'This event is then propagated to ALL clients, like Jones and so forth.
'On Jones' PC (for example), Client.vb would handle this event by displaying
something like
'"John: yada yada" on the txtReceivedMsgs.
'Of course John's client window sould also show the same
RaiseEvent evtReceiveText(username, text)
End Function
Public Function getHash() As String
'this is just a handy function to reaffirm that all your clients are communicating
'with a ONE and only Manager object
'Which means (in simple English),
'John and the Jones will see the very same hash code which was assigned to the
Manager object
Return Me.GetType.GetHashCode().ToString
End Function
End Class
Using the code (Server.vb)
1. Add a new Console Application project called Server, or whatever you want
to call it.
2. Change the default .vb name to Server.vb
3. Don't forget to add a reference to InBetween, 'cause we're going to make
the first ever call to it and calling its getHash method.
4 . Copy the code below (Be sure to read the comments as we go on)
This is the guy that's in charged of registering our service, the ChatApplication.
We notice that we create a singleton as shown below.
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Http
Imports System.Runtime.Remoting
Imports System
Imports InBetween
public class Server
Public Shared Sub Main()
Dim server1 As Server
server1 = New Server()
End Sub
Public Sub New()
'Create a HTTP channel for our use
'We'll 'talk' on this port
'IMPORTANT: Make sure you don't have anything running on this channel!
Dim chan As IChannel = New HttpChannel(7777)
'Register it
ChannelServices.RegisterChannel(chan)
'I could have read the config from an xml file with :
'System.Runtime.Remoting.RemotingConfiguration.Configure(your.config.file)
'(XML format)
'Refer .NET Framework Class Library RemotingConfiguration.Configure Method [Visual
Basic]
'BUT somehow, I just couldn't make it work! So I went for this great 1 line
code shown below:
'Notice these things:
'1. We are registering a service: RegisterWellKnownServiceType
'2. It's of type: Type.GetType("InBetween.Manager, InBetween")
' InBetween is the namespace, Manager is the class
'3. We're calling that application, ChatApplication
'4. It's of type: Singleton
' Why Singleton and not singlecall? If u chose singlecall,
' everyone client (John and the Jones) would be creating their own Manager objects
' which would mean no message ever gets across to anyone
System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownServiceType(
_
Type.GetType("InBetween.Manager, InBetween"), _
"ChatApplication", WellKnownObjectMode.Singleton)
'I registered the Manager class and called getHash
'Read Manager.vb for more details on getHash
Dim Manager1 As New Manager()
Console.WriteLine("The Manager object's ID:" & Manager1.getHash())
System.Console.WriteLine("Hit ENTER to exit...")
'We don't want this object to die out too fast, so we just put a
'ReadLine here to sustain the object's lifetime
System.Console.ReadLine()
End Sub
End Class
Using the code (Client.vb)
Now, here's where we design the client.
1. Add a new Windows Application project called Client.
2. Change the default .vb name to Client.vb. If you're planning on copy and
pasting, skip to 5.
3. Add two multilined textbox called txtReceivedMsgs and txtMsgToSend
to handle received messages and to type messages into, respectively
4. Add a simple button, btnSend
5. Again, add a reference to InBetween and System.Runtime.Remoting .
We need the latter too, because we're going to need to create a HTTPChannel
object.
5. Copy the code below, make sure you name the variables as above (if you've
created the interface yourself). (Be sure to read the comments as we go on)
Imports System.Runtime.Remoting.Channels.Http
Public Class Client
Inherits System.Windows.Forms.Form
Private theManager As InBetween.Manager
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
Dim chan As HttpChannel
'Create a HTTP channel for our use
'IMPORTANT: Make sure you don't have anything running on this channel!
chan = New HttpChannel("8888")
'Registers a channel with the channel services.
System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(chan)
'Creates a proxy for a currently running remote object
'This remote object is the our InBetween.Manager's instance
'NOTE: Change 'UP' to your Chat server's name
'http://UP:7777/ChatApplication
theManager = CType(Activator.GetObject(Type.GetType("InBetween.Manager,InBetween"),
"http://UP:7777/ChatApplication"), InBetween.Manager)
'Add our event handler here
'In other words, tell this fellar, "when you receive an event called evtReceiveText
'(of type InBetween.Manager), then use the sub called HandleReceivedMsg
'to handle it
Try
AddHandler Me.theManager.evtReceiveText, AddressOf Me.HandleReceivedMsg
Catch e1 As Exception
'Our simple exception handler
MessageBox.Show(e1.Message)
End Try
'Cosmetic, I'm against it, but...
'(This displays a caption on your client window that says "Client on <PC
NAME>")
Me.Text = "Client on " & Windows.Forms.SystemInformation.ComputerName()
'Now, you would notice that the getHash(), will return a string that identifies
'the 'theManager's hash code. This Hash code will appear on ALL clients.
'Why? Simple, we are dealing with ONE and only ONE instance of InBetween's Manager
class
'We specified singleton on the server (Module1.vb), remember?
'It's easy to remember, a 'single' 'ton', "SINGLE-TON"
MessageBox.Show(Me.theManager.getHash())
End Sub
'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents txtReceivedMsgs As System.Windows.Forms.TextBox
Friend WithEvents btnSend As System.Windows.Forms.Button
Friend WithEvents txtMsgToSend As System.Windows.Forms.TextBox
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.btnSend = New System.Windows.Forms.Button()
Me.txtReceivedMsgs = New System.Windows.Forms.TextBox()
Me.txtMsgToSend = New System.Windows.Forms.TextBox()
Me.SuspendLayout()
'
'btnSend
'
Me.btnSend.Location = New System.Drawing.Point(280, 160)
Me.btnSend.Name = "btnSend"
Me.btnSend.TabIndex = 0
Me.btnSend.Text = "&Send"
'
'txtReceivedMsgs
'
Me.txtReceivedMsgs.Location = New System.Drawing.Point(0, 8)
Me.txtReceivedMsgs.Multiline = True
Me.txtReceivedMsgs.Name = "txtReceivedMsgs"
Me.txtReceivedMsgs.ReadOnly = True
Me.txtReceivedMsgs.Size = New System.Drawing.Size(360, 88)
Me.txtReceivedMsgs.TabIndex = 1
Me.txtReceivedMsgs.Text = ""
'
'txtMsgToSend
'
Me.txtMsgToSend.Location = New System.Drawing.Point(0, 104)
Me.txtMsgToSend.Multiline = True
Me.txtMsgToSend.Name = "txtMsgToSend"
Me.txtMsgToSend.Size = New System.Drawing.Size(360, 48)
Me.txtMsgToSend.TabIndex = 2
Me.txtMsgToSend.Text = ""
'
'Client
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(360, 189)
Me.Controls.AddRange(New System.Windows.Forms.Control() {Me.txtMsgToSend, Me.txtReceivedMsgs,
Me.btnSend})
Me.MaximizeBox = False
Me.Name = "Client"
Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen
Me.Text = "Client"
Me.ResumeLayout(False)
End Sub
#End Region
Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnSend.Click
'What happenning here?
'Once we press our 'Send' button,
'we raise an event via SendText method of Manager (InBetween)
'Then just, erase our textbox - txtMsgToSend
'Easy isn't it?
'To follow the event, peek at InBetween's Manager's SendText method
Me.theManager.SendText(Windows.Forms.SystemInformation.ComputerName, Me.txtMsgToSend.Text)
txtMsgToSend.Text = ""
End Sub
Sub HandleReceivedMsg(ByVal username As String, ByVal text As String)
'Ok, here's what happens...
'John Doe sends u a message, the Manager object raises an event,
'your client intercepts it, and execution drops down here...
'You then append the text here...
'"... and I thought chat programs were hard..." well anyway, here's
the line that does it
Me.txtReceivedMsgs.AppendText(username & " : " & text &
vbCrLf)
End Sub
Private Sub Client_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs)
Handles MyBase.Closing
Try
'Ok, let's undo what we've done
'We've added a handler, (remember?), so now we need to remove it
'You basically do this, ...
RemoveHandler theManager.evtReceiveText,
// AddressOf Me.HandleReceivedMsg
Catch e1 As Exception
'Exception handling for... err, simple ppl like us...
MessageBox.Show(e1.Message)
End Try
End Sub
Private Sub Client_Load(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles MyBase.Load
End Sub
End Class
Run It! (Conclusion)
1. Build the solution. You will be prompted to set the startup object, just
do so.
2. As you're waiting, note these points:
- We could have used the .config files, but I prefer to code it to show you
how to do without the .config
- As each client window loads up, you'll see the same hash code. It's the
hash code of the singleton Manager object.
- We're using the same http port (7777) on both side (client and server).
We could use TCP if we wanted to. You'd better make sure John Doe's ftp server
(or any other application) isn't running at that port too (at the same time)!
- Our Well-known object was exposed on the server end
3. Just before you really run it, place the server.exe into the same directory
as client.exe. Place other clients on other PCs
4. Run the server.exe first, then, all the clients.
5. Chat!
So what's the conclusion?
- The most important thing to note is: We've done a server
activation
- MSDN says "... Server-activated objects are objects
whose lifetimes are directly controlled by the server. The server application
domain creates these objects only when the client makes a method call on the
object, not when the client calls new (New() in Visual Basic) or Activator.GetObject();
this saves a network round trip solely for the purpose of instance creation.
Only a proxy is created in the client application domain when a client requests
an instance of a server-activated type... There are two activation
modes (or WellKnownObjectMode values) for server-activated objects,
Singleton and SingleCall..." .NET Framework Developer's Guide
Server Activation [Visual Basic]
- Our first method call to the object was in
Server.vb . We could
also have made the first call from client.vb . Try this, remove
the comment on theManager = New InBetween.Manager () in client.vb ,
and in server.vb , comment these:
Dim Manager1 As New Manager()
Console.WriteLine("The Manager object's ID:" & Manager1.getHash())
So you see, it still works the same... I had to make the first call
to getHash() in Server.vb because I wanted to get
the Hash code on server.vb to display. So let me recap: the reason why we
call this Server activated is because our object's lifetime was directly controlled
by the server via Singleton activation. We didn't instantiate anything by
saying Dim Manager1 As New Manager() , that just registered the
object. The actual object existed only after we made the method call.
Points of Interest
Strange thing to note: The first client to be executed, must be run from the
same directory of the server.exe! Subsequent clients need not
be started in the same directory as server.exe. Yes that's strange, otherwise,
you'd get this silly error which makes no sense:
System.IO.FileNotFoundException
File or assembly name Client , or one of its dependencies, was not found.
Yes, it's a known problem, you might want to read more on this here: http://www.dotnet247.com/247reference/msgs/12/63594.aspx
I might want to come out with a good walkaround that later on, but till then,
all the best in remoting! Spend a sec' to rate me, will ya? Thanks
Etc
- For those who've done COM before, "what the difference between .Net
remoting and remotely activated COM?&quo;
// t;. Unlike COM, remoting does not
start the host or server application for you. This is an important difference
between .NET remoting and remote activation in COM.
- MSDN calls our
Manager.vb a remotable type. Our Server.vb is
the host application
- Your host application domain is not limited to our simple chat program,
but they may be Windows Services, console applications, Windows Forms applications,
Internet Information Services (IIS) processes, or ASP.NET applications.
- General Host Tasks (eg. what you should think about when
coding your own host)
- What's my host application domain going to be like?
(Windows Services, console applications, Windows Forms applications, Internet
Information Services (IIS) processes, or ASP.NET applications)
- Which activation model should I use? (Client/Server
activation)
- Choose a channel (HTTP or TCP) and a port. Register
it using
ChannelServices.RegisterChannel . Remember, you can't
use the same port as Uncle Joe's FTP server...
- General Client Tasks (eg. what you should think about when
coding your own client)
- What's my client application domain going to be like?
(Windows Services, console applications, Windows Forms applications, Internet
Information Services (IIS) processes, or ASP.NET applications)
- Which activation model should I use? (Client/Server
activation)
- Should I use the client activation URL (eg.
ProtocolScheme://ComputerName:Port/PossibleApplicationName
) or the well-known object URL (eg. ProtocolScheme://ComputerName:Port/PossibleApplicationName/ObjectUri )
of the remote type.
- Choose a channel (HTTP or TCP) and a port. Register
it using
ChannelServices.RegisterChannel .
- If you want to try to use the .config file, which I don't like, bear in
mind that most of the problems using .NET remoting occur because some of these
settings are either incorrect or do not match the configuration settings for
client applications. It is very easy to mistype a name, forget a port, or
neglect an attribute. If you are having problems with your remoting application,
check your configuration settings first.
History
Friday 13, 2003: Uploaded first version.
Saturday 14, 2003: Uploaded second version (due to great ratings).
Monday 16, 2003: Added the Etc section |