-
Notifications
You must be signed in to change notification settings - Fork 89
Remoting (tutorial)
This is a simple example that shows how to realize remoting in Nemerle. Essentially this is translation of the example presented in the MonoHandbook.
Remoting allow clients to access objects in the server. And thus it is a good abstraction to distribute computing.
In this example both Server and Client share the definition of ServerObjects. The server program opens a port for conection and publish a ServerObject. Later the client program connects himself to the server port and access, remotely, the published objects and their methods.
The example shows the basic remoting usage, managing objects by reference and by value (through serialization).
Save the following code in the file "Client.n".
using System;
using System.Net;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace RemotingTest
{
class RemotingClient
{
static Main () :void
{
def ch = TcpChannel (0);
ChannelServices.RegisterChannel (ch);
// This gets the object from the server (a list of ServerObject)
Console.WriteLine ("Getting instance ...");
def remOb = Activator.GetObject (typeof (ServerList),
"tcp://localhost:1122/test.rem");
def list = remOb :>ServerList;
// These are remote calls that return references to remote objects
Console.WriteLine ("Creating two remote items...");
def item1 = list.NewItem ("Created at server 1") :ServerObject;
item1.SetValue (111); // another call made to the remote object
def item2 = list.NewItem ("Created at server 2") :ServerObject;
item2.SetValue (222);
Console.WriteLine ();
// Two objects created in this client app
Console.WriteLine ("Creating two client items...");
def item3 = ServerObject ("Created at client 1");
item3.SetValue (333);
def item4 = ServerObject ("Created at client 2");
item4.SetValue (444);
Console.WriteLine ();
// Object references passed to the remote list
Console.WriteLine ("Adding items...");
list.Add (item3);
list.Add (item4);
Console.WriteLine ();
// This sums all values of the ServerObjects in the list. The server
// makes a remote call to this client to get the value of the
// objects created locally
Console.WriteLine ("Processing items...");
list.ProcessItems ();
Console.WriteLine ();
// Passing some complex info as parameter and return value
Console.WriteLine ("Setting complex data...");
def cd = ComplexData (AnEnum.D, array ["some" :object, 22, "info"]);
def res = list.SetComplexData (cd) :ComplexData;
Console.WriteLine ("This is the result:");
res.Dump ();
Console.WriteLine ();
list.Clear ();
Console.WriteLine ("Done.");
ch.StopListening (null);
}
}
}
Save the following code in the file "Server.n".
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace RemotingTest
{
public class RemotingServer
{
static Main () :int
{
Console.WriteLine ("Starting Server");
def ch = TcpChannel (1122);
ChannelServices.RegisterChannel (ch);
def ser = ServerList ();
_ = RemotingServices.Marshal (ser, "test.rem");
Console.WriteLine ("Server Running ...");
_ = Console.ReadLine ();
ch.StopListening (null);
0;
}
}
}
Save the following code in the file "ServerObjects.n".
using System;
using System.Runtime.Remoting;
using System.Collections;
namespace RemotingTest
{
// A list of ServerObject instances
public class ServerList :MarshalByRefObject
{
values : ArrayList;
public this ()
{
values = ArrayList ();
}
public Add (v :ServerObject) :void
{
_ = values.Add (v);
System.Console.WriteLine ("Added " + v.Name);
}
public ProcessItems () :void
{
System.Console.WriteLine ("Processing...");
mutable total = 0;
foreach (ob in values) total += (ob :> ServerObject).GetValue ();
System.Console.WriteLine ("Total: " + total.ToString());
}
public Clear () :void
{
values.Clear ();
}
public NewItem (name :string) :ServerObject
{
def obj = ServerObject (name);
Add (obj);
obj;
}
public SetComplexData (data :ComplexData) :ComplexData
{
System.Console.WriteLine ("Showing content of ComplexData");
data.Dump ();
data;
}
}
// A remotable object
public class ServerObject :MarshalByRefObject
{
mutable _value :int;
_name :string;
public this (name :string)
{
_name = name;
}
public Name :string
{
get { _name; }
}
public SetValue (v :int) :void
{
System.Console.WriteLine ("ServerObject " + _name + ": setting " + v.ToString() );
_value = v;
}
public GetValue () :int
{
System.Console.WriteLine ("ServerObject " + _name + ": getting " + _value.ToString());
_value;
}
}
public enum AnEnum { |A |B |C |D |E };
[Serializable]
public class ComplexData
{
public Val :AnEnum;
public Info : array [object];
public this ( va:AnEnum, info : array [object])
{
Info = info;
Val = va;
}
public Dump () :void
{
System.Console.WriteLine ("Content:");
System.Console.WriteLine ("Val: " + Val.ToString());
foreach (ob in Info) System.Console.WriteLine ("Array item: " + ob.ToString());
}
}
}
Save the following code in the file "Makefile". <bash>all: ncc -t:library ServerObjects.n -o ServerObjects.dll ncc -r:ServerObjects.dll -r:System.Runtime.Remoting.dll Client.n -o Client.exe ncc -r:ServerObjects.dll -r:System.Runtime.Remoting.dll Server.n -o Server.exe </bash>
Run "make" to compile the programs. Then execute the "Server.exe" in a console and "Client.exe" in another one.
By default custom attribues on variants are not passed to its variant options.
Thus using
[Serializable]
public variant AnEnum { |A |B |C |D |E };
instead of the proposed enum (which is directly serializable) will create an System.Runtime.Serialization.SerializationException
error - you must explicitly declare each element of the variant as Serializable
.
[Serializable]
public variant AnEnum {
[Serializable] | A
[Serializable] | B
[Serializable] | C { x : string; }
[Serializable] | D { x : int; }
[Serializable] | E
};
It is possible to use a macro Nemerle.MarkOptions
to realize this in a compact way.
For a little insight of how it works, there is its a full source, which fortunately is short.
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
[Nemerle.MacroUsage (Nemerle.MacroPhase.BeforeInheritance,
Nemerle.MacroTargets.Class)]
macro MarkOptions (t : TypeBuilder, attribute)
{
// iterate through members of this type and select only variant options
foreach (ClassMember.TypeDeclaration
(TopDeclaration.VariantOption as vo) in t.GetParsedMembers ())
{
// add given custom attribute to this variant option
vo.AddCustomAttribute (attribute)
}
}
Now you can use
[Serializable]
[Nemerle.MarkOptions (Serializable)]
public variant AnEnum {
| A
| B
| C { x : string; }
| D { y : int; }
| E
}
Side note Remember that if you use variant instead of enum, the expression
AnEnum.B
```
has to be changed to
```nemerle
AnEnum.B ()
This is for keeping consistency with ```nemerle AnEnum.C ("Ala") ```