Skip to content

Commit

Permalink
Catalog implemented. Port sharing implemented. Renamed trace to log.
Browse files Browse the repository at this point in the history
  • Loading branch information
steforster committed Mar 4, 2014
1 parent e4cb68a commit f0c05aa
Show file tree
Hide file tree
Showing 40 changed files with 1,035 additions and 1,165 deletions.
1 change: 0 additions & 1 deletion .gitignore
@@ -1,7 +1,6 @@
*~
[Bb]in/
[Oo]bj/
[Ll]ogs/

*.suo
*.user
Expand Down
127 changes: 114 additions & 13 deletions README.md
@@ -1,18 +1,28 @@
Remote Actors for .NET
======================

**Remact.Net** facilitates the development of [actors](http://en.wikipedia.org/wiki/Actor_model) in .NET languages.
**Remact.Net** facilitates the development of remote actors in .NET languages.

It is a class library written in C-Sharp.
It runs in Microsoft .NET and Linux-Mono environments.

The [Actor model](http://en.wikipedia.org/wiki/Actor_model) is inspired by physics and by nature.
It brings order to multithread, multicore, multihost systems.
These days many systems have such requirements. But industrial control systems with dynamically connected
and movable intelligent subsystems have been in focus of the design of Remact.Net.
Therefore, Remact.Net supports many instances of the same application running on one or on distributed hosts.
It also supports dynamic discovery of actors and does not require configuration of host names and TCP ports.

In spite of all these features, Remact.Net is remarkably slim and easy to adapt for special needs.


**Project status**
Remact.Net is work in progress. Some integration test are running.
[AsyncWcfLib](http://sourceforge.net/projects/asyncwcflib/) is the predecessor of Remact.Net.

The motivation for this new project is improved support for bidirectional communication,
higher performance on Linux/Mono based environments and wider interoperability
so that actors written in Java or JavaScript (browser based actors) can participate.
The motivation for this new project is to improve support for bidirectional communication,
higher performance on Linux/Mono based environments and interoperability
with actors written in Java or JavaScript (browser based actors).



Expand All @@ -24,7 +34,7 @@ The following goals have been reached:
- [*] Local actors (message passing between threads)
- [*] Remote actors (message passing between hosts or processes)
- [*] WebSockets, Json and other open standards are used to link Remact actors.
- [*] High throughput on Linux and Windows: More than 5000 request/respose pairs per second.
- [*] High throughput on Linux and Windows: More than 5000 request/respose pairs per second between processes.



Expand All @@ -46,7 +56,7 @@ For other languages there are libraries to help programming remote actors:
Dependencies and standards
--------------------------
Remact.Net is built on open standards and uses open source components.
I would like to thank all participants for their contribution to the open source community.
I would like to thank all who built these bits for their contribution to the open source community.

* [WebSocket](http://tools.ietf.org/html/rfc6455), the IETF standard RFC6455

Expand All @@ -55,6 +65,8 @@ I would like to thank all participants for their contribution to the open source

* [WAMP](http://wamp.ws/), the WebSocket Application Messaging Protocol

* [JSON-RPC](http://www.jsonrpc.org/specification), instad of WAMP, Json-RPC can be used on top of the WebSocket protocol.

* [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json), a class library for Json serialization

* [Log4Net](http://logging.apache.org/log4net/), logging component from Apache
Expand All @@ -71,12 +83,11 @@ Documentation
-------------
Currently, [the main conceptual ideas](http://sourceforge.net/p/asyncwcflib/wiki/Actors/) should be read on the AsyncWcfLib pages.

The folder **src/Remact.Net/Contracts** contains remotely callable methods and their corresponding request- and response messages.
These definitions and their XML comments form the basic interface definition of actors.
The folder **src/Remact.Net/Contracts** and **test/SpeedTestApp/src/Contracts** contains interfaces for remotely callable methods and their
corresponding request- and response messages. These definitions and their XML comments form the basic interface definition of actors.

Application actors will add more contracts in other assemblies.
These definitions must be present on both sides of the communication channel.
For actors not written in a .NET programming language (e.g. Java Script), the interface definition must be translated.
These contracts must be present on both sides of the communication channel.
For actors not written in a .NET programming language (e.g. Java Script), the interface contract must be translated.

Receiving of WAMP messages is done in five steps:
* Deserialization to a [Newtonsoft.Json.Linq.JToken](http://weblog.west-wind.com/posts/2012/Aug/30/Using-JSONNET-for-dynamic-JSON-parsing)
Expand All @@ -86,9 +97,95 @@ Receiving of WAMP messages is done in five steps:
* Optional dispatching to a method having the matching parameter type


Conceptual parts
----------------

**Actors**

An actor is a group of objects that are accessed by one thread. Data inside an actor is consistant.
An actor may feature several input and output ports. These ports are connected to other actors on the same or on a remote process.
The actor contains one message queue. All incoming messages pass this queue (incoming requests, incoming responses).


**Outputs**

An output port is connected to an input port of another actor.
Messages are sent by the output to the connected input. Sending is a non blocking operation.
Optionally the remote actor may send a reply message. It is handled as an asynchronous callback event
in a lambda expression or in an async method.
The remote actor may send notification messages to an output. It is handled in a method defined by the contract interface.
Disconnecting the output is signaled to the input. A disconnect signal may also be issued by the input.


**Inputs**

Many remote outputs may be connected to one input port.
Once connected, messages may be sent from the output to the input but also from the input to the output.
Address and version information for each connected output is available from the input server.
Additional session data may be kept for each connected actor output.
Periodic messages should be exchanged by the actors to check the communication channels.


**ActorMessage and Payload**

The ActorMessage class addresses source and destination port. It is used to route the payload data through the system.
The payload may be of any serializable object type.
Serialization is done by Newtonsoft.Json. Therefore, attributes like [JsonProperty] and [JsonIgnore] may be used to
control the serialization process. By default all public properties and fields are serialized.


**Methods**

Messages are sent to the method that handles the message payload type as a single input parameter.
The method also defines the reply payload as the return type.
A void method will normally not reply a message.
In case of error or exception, methods will reply an ErrorMessage.


How to build
------------
**Contract interface**

A contract interface defines the set of methods and the corresponding request and response message types that are available
on a certain input or output port.
On the receiving side the methods will have the specified, single message payload parameter and additional parameters
for message source identification and session data.



Communication models
--------------------

**Client / Service**

One actor may contain several clients and services.
The output (client) sends a request to the input (service) of another actor and will get the reply from it.


**Notifications**

The input port may send a callback notification to the output port.
The output may send a notification to the input port.
Notifications are defined as parameter of a void method of the receiving port contract interface.


**Service / Client**

Communication is symmetrical. Therefore inputs may also send requests to a method of the connected output
and get a response from it. The difference between input and output lies in the 1 : many relationship
and in the active part of the output during connection buildup.
Some day we hopefully will find a better name for the two connected communication port types.


**Publish / Subscribe**

An input port can send messages to all its connected output ports.
Connecting to such an input in fact means - subscribing to its publications.
The publisher knows who received its publications because all communication is done through reliable
TCP connections to known partners.



How to build and test Remact.Net
--------------------------------
The Remact.Mono solution is used for VisualStudio 2012 and for MonoDevelop 2.8.6.3.
Source projects of other git repositories are referenced from Remact.Mono.sln.
You have to clone all these (small) repos to be able to build Remact:
Expand All @@ -102,6 +199,10 @@ Afterwards your project folder should look like this:
$ ls
Alchemy-Websockets Newtonsoft.Json Remact.Net ...

Newtonsoft.Json needs some small adaptions to run under Mono. I copied "Newtonsoft.Json.Net40.csproj" to
"Newtonsoft.Json.Mono.csproj" and switched the target framework to 4.0 (not client profile).

Then you should be able to compile in VS2010 or VS2012 the "Remact.Mono.sln" (Release) and start "test\SpeedTestApp\Mono\_startTest2.cmd".


License
Expand Down
File renamed without changes.
6 changes: 4 additions & 2 deletions src/Remact.Catalog/Catalog.cs
Expand Up @@ -11,6 +11,7 @@
using Remact.Net;
using Remact.Net.Remote;
using Remact.Net.Protocol;
using Remact.Net.Contracts;

namespace Remact.Catalog
{
Expand Down Expand Up @@ -293,7 +294,8 @@ private void Open()
m_CatalogService = new CatalogService();

// Open the service
m_RemactService = new ActorInput(RemactConfigDefault.Instance.CatalogServiceName, m_CatalogService.OnRequest);
m_RemactService = new ActorInput(RemactConfigDefault.Instance.CatalogServiceName, m_CatalogService.OnUnknownRequest);
m_RemactService.Dispatcher.AddActorInterface(typeof(IRemactCatalog), m_CatalogService);
m_RemactService.OnInputConnected += m_CatalogService.OnClientConnectedOrDisconnected;
m_RemactService.OnInputDisconnected += m_CatalogService.OnClientConnectedOrDisconnected;
m_RemactService.LinkInputToNetwork( null, RemactConfigDefault.Instance.CatalogPort, publishToCatalog: false, serviceConfig: this ); // calls our DoServiceConfiguration
Expand All @@ -306,7 +308,7 @@ private void Open()
if (host != null && host.Trim().Length > 0)
{
var output = new ActorOutput<SvcDat>("Clt>"+host, OnResponseFromPeerCatalog);
output.LinkOutputToRemoteService(new Uri("http://" + host + ':' + RemactConfigDefault.Instance.CatalogPort
output.LinkOutputToRemoteService(new Uri("ws://" + host + ':' + RemactConfigDefault.Instance.CatalogPort
+ "/" + RemactConfigDefault.WsNamespace + "/" + RemactConfigDefault.Instance.CatalogServiceName),// no catalog lookup as uri is given.
this ); // calls our DoClientConfiguration
output.OutputContext = new SvcDat();
Expand Down
134 changes: 40 additions & 94 deletions src/Remact.Catalog/CatalogService.cs
Expand Up @@ -17,118 +17,64 @@ namespace Remact.Catalog
/// </summary>
class CatalogService
{
//----------------------------------------------------------------------------------------------
#region Public Methods


public void OnClientConnectedOrDisconnected (ActorMessage id)
{
Program.Catalog.SvcRegisterChanged = true;
}

public void OnRequest (ActorMessage id)
public void OnUnknownRequest (ActorMessage msg)
{
ActorInfo service = id.Payload as ActorInfo;
bool ok = false;
if (service != null && service.IsServiceName)
{
switch (service.Usage)
{
case ActorInfo.Use.ServiceEnableRequest: ok = RegisterService(service, id); break;
case ActorInfo.Use.ServiceDisableRequest: ok = RegisterService(service, id); break;
case ActorInfo.Use.ServiceAddressRequest: ok = GetAddress(service, id); break;
default: break;// continue below
}
}

ActorInfoList list = id.Payload as ActorInfoList;
if (list != null)
{
ok = RegisterList(list, id);
}


if (!ok)
{
RaLog.Warning(id.SvcRcvId, "Unknown request or no service: " + id.Payload.ToString());
id.SendResponse(new ErrorMessage(ErrorMessage.Code.AppRequestNotAcceptedByService, "Remact.CatalogService"));
}
RaLog.Warning(msg.SvcRcvId, "Unknown request or no service: " + msg.Payload.ToString());
msg.SendResponse(new ErrorMessage(ErrorMessage.Code.AppRequestNotAcceptedByService, "Remact.CatalogService"));
}


#endregion
//----------------------------------------------------------------------------------------------
#region Private Methods

// A single service entry is beeing enabled, disabled or updated
// return the service info as response
private bool RegisterService (ActorInfo req, ActorMessage id)
// service request method implements IRemactCatalog
private ReadyMessage InputIsOpen(ActorInfo actorInput, ActorMessage msg)
{
ActorInfo response = req;
if (Program.Catalog.RegisterService (req, id.SvcRcvId))
{
// req is used in the SvcRegister now. We have to create a copy
response = new ActorInfo(req);
}

// reply the registered service
if (req.Usage == ActorInfo.Use.ServiceEnableRequest)
{
response.Usage = ActorInfo.Use.ServiceEnableResponse;
}
else
{
response.Usage = ActorInfo.Use.ServiceDisableResponse;
}

id.SendResponse (response);
return true;
Program.Catalog.RegisterService(actorInput, msg.SvcRcvId);
return new ReadyMessage();
}

// service request method implements IRemactCatalog
ReadyMessage InputIsClosed(ActorInfo actorInput, ActorMessage msg)
{
Program.Catalog.RegisterService(actorInput, msg.SvcRcvId);
return new ReadyMessage();
}

// A list of service entries is beeing enabled, disabled or updated
// return our list as response, to synchronize the peer catalog
private bool RegisterList (ActorInfoList list, ActorMessage id)
// service request method implements IRemactCatalog
ActorInfo LookupInput(string actorInputName, ActorMessage msg)
{
RaLog.Info( id.SvcRcvId, "PeerRtr sends list containing " + list.Item.Count + " services." );
foreach( ActorInfo s in list.Item )
ActorInfo found = null;
foreach (ActorInfo s in Program.Catalog.SvcRegister.Item)
{
if (s.Name == actorInputName && s.IsServiceName)
{
found = new ActorInfo(s); // create a copy in order not to change the SvcRegister
found.Usage = ActorInfo.Use.ServiceAddressResponse;
break;
}
}

if (found == null)
{
Program.Catalog.RegisterService (s, id.SvcRcvId);
msg.SendResponse(new ErrorMessage(ErrorMessage.Code.AppDataNotAvailableInService,
"Service name = '" + actorInputName + "' not registered in '" + Program.Catalog.Service.Uri + "'"));
}
id.SendResponse (Program.Catalog.SvcRegister);
return true;

return found;
}


// GetAddress: Search URI (with TCP port number) of a registered service
private bool GetAddress (ActorInfo search, ActorMessage id)
// service request method implements IRemactCatalog
ActorInfoList SynchronizeCatalog(ActorInfoList serviceList, ActorMessage msg)
{
bool found = false;
foreach (ActorInfo s in Program.Catalog.SvcRegister.Item)
{
if (s.Name == search.Name
&& s.IsServiceName == search.IsServiceName)
RaLog.Info(msg.SvcRcvId, "Peer catalog sends list containing " + serviceList.Item.Count + " services.");
foreach (ActorInfo s in serviceList.Item)
{
search = new ActorInfo (s); // create a copy in order not to change the SvcRegister
search.Usage = ActorInfo.Use.ServiceAddressResponse;
found = true;
break;
Program.Catalog.RegisterService(s, msg.SvcRcvId);
}
}

if (!found)
{
id.SendResponse (new ErrorMessage (ErrorMessage.Code.AppDataNotAvailableInService,
"Service name = '" + search.Name + "' not registered in '" + Program.Catalog.Service.Uri + "'"));
}
else
{
id.SendResponse (search);
}
return true;
}// GetAddress

#endregion

}// class CatalogService
}// namespace
return Program.Catalog.SvcRegister;
}
}
}

0 comments on commit f0c05aa

Please sign in to comment.