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