Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

New Version of Griffin.Networking – A Networking Library for .NET

5.00/5 (1 vote)
19 Nov 2012LGPL33 min read 12.2K  
New version of Griffin.Networking

I’ve refactored Griffin.Network quite a lot (I did say that it was still an early release). The new version is called v0.5 (the library is taking shape, but there are still some things to take care of).

Highlights

  1. The pipeline is no longer mandatory
  2. I’m using the Async methods instead of Begin/EndXxxx
  3. The buffer management is more efficient
  4. It’s easier to work with the buffers
  5. Introducing messaging support

Dead Easy to Get Started

If you just want to use the library, here is how you can configure it to send objects over the network using JSON and a small binary header:

C#
public class Program
{
    private static void Main(string[] args)
    {
        // configuring the server
        var serviceFactory = new MyServiceFactory();
        var messageFactory = new BasicMessageFactory();
        var configuration = new MessagingServerConfiguration(messageFactory);
        var server = new MessagingServer(serviceFactory, configuration);
        server.Start(new IPEndPoint(IPAddress.Any, 7652));

        // configuring the client
        var client = new MessagingClient(new BasicMessageFactory());
        client.Connect(new IPEndPoint(IPAddress.Loopback, 7652));

        // Look here! We receive objects!
        client.Received += (sender, eventArgs) => Console.WriteLine
                                                  ("We received: " + eventArgs.Message);

        // And here we are sending one.
        client.Send(new OpenDoor {Id = Guid.NewGuid().ToString()});

        Console.ReadLine();
    }
}

The complete sample.

Let’s see how that works…

Messaging

Typically, you want to create an application that sends and receives messages over the network. It usually is a flow like this:

Receiving

  1. Receive byte[] buffers
  2. Receive enough to be able to build a message (by using a length header or a delimiter)
  3. Build the message
  4. Process the message

Sending

  1. Do your magic
  2. Send a message
  3. Serialize it
  4. Send it asynchronously (might require several SendAsync)

Instead of having to do all those steps, there is now a Messaging server available. What you have to do now is simply to specify a message builder and a message serializer and you’ll automagically get to work with messages directly.

Sample Implementation

The first thing we have to do is to define our factory that creates serializers and message builders. Let’s decide that we should only use a binary header (version + length) and JSON to transport everything.

When we are sending things over the network with Griffin.Networking, we’ll refer to messages instead of objects. For that’s what the library does. It serializes the objects, put them in a message (typically with a binary header) and sends everything to the remote end point.

Serializing Messages

To convert objects to byte arrays, we’ll use serializers. They look just like typical .NET serializers. Here is the one for the basic protocol:

C#
public class BasicMessageSerializer : IMessageSerializer
{
    private static readonly byte[] VersionBuffer = new byte[] {1};
    
    public void Serialize(object message, IBufferWriter writer)
    {
        var str = JsonConvert.SerializeObject(message);
        var bodyBytes = Encoding.UTF8.GetBytes(str);

        // version
        writer.Write(VersionBuffer, 0, VersionBuffer.Length);

        //length
        var buffer = BitConverter.GetBytes(bodyBytes.Length);
        writer.Write(buffer, 0, buffer.Length);

        //body
        writer.Write(bodyBytes, 0, bodyBytes.Length);
    }
}

We first serialize the object as JSON and then start by writing the binary header. Finally, we also write the string.

Building Incoming Messages

When receiving bytes from the socket, we’ll have to do more, since TCP sends everything in chunks. There is no guarantee that everything is received directly. Hence, we need build objects and not just try to deserialize them directly.

C#
public class BasicMessageBuilder : IMessageBuilder
{
    private readonly byte[] _header = new byte[Packet.HeaderLength];
    private readonly Queue<Packet> _messages = new Queue<Packet>();
    private int _bytesLeft = Packet.HeaderLength;
    private Packet _packet;
    private Func<IBufferReader, bool> _parserMethod;

    public BasicMessageBuilder()
    {
        _parserMethod = ReadHeaderBytes;
    }

    // invoked by the server every time something have been received
    public bool Append(IBufferReader reader)
    {
        while (_parserMethod(reader))
        {
        }
        return _messages.Count > 0;
    }

    // server is trying to dequeue a message (if one has been completed)
    public bool TryDequeue(out object message)
    {
        if (_messages.Count == 0)
        {
            message = null;
            return false;
        }

        var packet = _messages.Dequeue();

        using (var reader = new StreamReader(packet.Message))
        {
            var str = reader.ReadToEnd();
            message = JsonConvert.DeserializeObject(str);
        }

        return true;
    }

    // read header bytes from the incoming bytes
    protected virtual bool ReadHeaderBytes(IBufferReader stream)
    {
        var bytesLeftInStream = stream.Count - stream.Position;
        var bytesToCopy = bytesLeftInStream < _bytesLeft
                              ? bytesLeftInStream
                              : _bytesLeft;

        stream.Read(_header, 0, bytesToCopy);

        _bytesLeft -= bytesToCopy;
        if (_bytesLeft > 0)
            return false;

        _packet = CreatePacket(_header);

        _bytesLeft = _packet.ContentLength;
        _parserMethod = ReadBodyBytes;
        return true;
    }

    // build the body
    protected virtual bool ReadBodyBytes(IBufferReader reader)
    {
        var bytesLeftInStream = reader.Count - reader.Position;
        var bytesToCopy = bytesLeftInStream < _bytesLeft
                              ? bytesLeftInStream
                              : _bytesLeft;


        reader.CopyTo(_packet.Message, bytesToCopy);

        _bytesLeft -= bytesToCopy;
        if (_bytesLeft > 0)
            return false;

        _packet.Message.Position = 0;
        _messages.Enqueue(_packet);
        _packet = null;

        _bytesLeft = Packet.HeaderLength;
        _parserMethod = ReadHeaderBytes;
        return true;
    }

    // returns the class which is used to temporarily store the body
    // until we've received the messages entire content
    protected virtual Packet CreatePacket(byte[] header)
    {
        var message = new Packet
            {
                Version = _header[0],
                ContentLength = BitConverter.ToInt32(header, 1)
            };

        if (message.Version <= 0)
            throw new InvalidDataException(string.Format(
                "Received '{0}' as version. 
                Must be larger or equal to 1.", message.Version));
        if (message.ContentLength <= 0)
            throw new InvalidDataException(string.Format(
                "Got invalid content length: '{0}', 
                expected 1 or larger.", message.ContentLength));

        message.Message = new MemoryStream(message.ContentLength);
        return message;
    }
}

The Message Factory

To be able to customize the message handling, we’ll provide the serializer and the message builder through an abstract factory. It has two methods, one for creating serializers and one for creating message builders.

C#
public class BasicMessageFormatterFactory : IMessageFormatterFactory
{
    public IMessageSerializer CreateSerializer()
    {
        return new BasicMessageSerializer();
    }

    public IMessageBuilder CreateBuilder()
    {
        return new BasicMessageBuilder();
    }
}

Working with the Messages

Do note that messaging is just one of the options that is included in Griffin.Networking. You can for instance handle the byte arrays directly if you like to. The framework will still take care of all buffer handling, network IO and connection management for you.

Before creating the server, we have to define the class that will handle all incoming messages. Compare it to a WCF service:

C#
public class MyService : MessagingService
{
    public override void HandleReceive(object message)
    {
        // We can only receive this kind of command
        var msg = (OpenDoor)message;

        Console.WriteLine("Should open door: {0}.", msg.Id);

        // Send a reply
        Context.Write(new DoorOpened(msg.Id));
    }
}

All messages are getting into the same method since we haven’t specified what the server should do. Nothing says that it has to be an RPC server like WCF.

We can of course build something on top of the service to get an approach similar to WCF service classes.

Starting the Server

We’ve put all pieces together. Let’s create a server:

C#
class Program
{
    static void Main(string[] args)
    {
        // factory that produces our service classes which
        // will handle all incoming messages
        var serviceFactory = new MyServiceFactory();

        // factory used to create the classes that will
        // serialize and build our messages
        var messageFactory = new BasicMessageFactory();

        // server configuration.
        // you can limit the number of clients etc.
        var configuration = new MessagingServerConfiguration(messageFactory);

        // actual server
        var server = new MessagingServer(serviceFactory, configuration);
        server.Start(new IPEndPoint(IPAddress.Any, 7652));

        //to prevent the server from shutting down
        Console.ReadLine();
    }
}

Yay.

Example Client

We also need something which can communicate with the server.

C#
// same factory as server side.
var messageFactory = new BasicMessageFactory();

// tell the client to use the factory
var client = new MessagingClient(messageFactory);

// connect
client.Connect(new IPEndPoint(IPAddress.Loopback, 7652));

// Server sent us something
client.Received += (sender, eventArgs) => Console.WriteLine("We received: " + eventArgs.Message);

// Send the initial command to the server
client.Send(new OpenDoor{Id = Guid.NewGuid().ToString()});

Summary

That was a short introduction to the new messaging alternative of Griffin.Networking. The pipeline that I described in the last Griffin.Networking blog post is still included, but it’s not the only option any more.

As a bonus, here is a basic HTTP server:

C#
// console app
internal class Program
{
    public static void RunDemo()
    {
        var server = new MessagingServer(new MyHttpServiceFactory(),
                                            new MessagingServerConfiguration(new HttpMessageFactory()));
        server.Start(new IPEndPoint(IPAddress.Loopback, 8888));
    }
}

// factory
public class MyHttpServiceFactory : IServiceFactory
{
    public IServerService CreateClient(EndPoint remoteEndPoint)
    {
        return new MyHttpService();
    }
}

// and the handler
public class MyHttpService : HttpService
{
    private static readonly BufferSliceStack Stack = new BufferSliceStack(50, 32000);

    public MyHttpService()
        : base(Stack)
    {
    }

    public override void Dispose()
    {
    }

    public override void HandleReceive(object message)
    {
        var msg = (IRequest) message;

        var response = msg.CreateResponse(HttpStatusCode.OK, "Welcome");

        response.Body = new MemoryStream();
        response.ContentType = "text/plain";
        var buffer = Encoding.UTF8.GetBytes("Hello world");
        response.Body.Write(buffer, 0, buffer.Length);
        response.Body.Position = 0;

        Send(response);
    }
}

I’m currently writing a more complete server which will replace my old C# WebServer.

Documentation is also available, code at github.

Nuget:

install-package griffin.networking
install-package griffin.networking.protocol.basic

The post New version of Griffin.Networking – A networking library for .NET appeared first on jgauffin's coding den.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)