ProtoBuf is Google's open serialization format which can be used to serialize objects in a standardized way. With it, different platforms can communicate with a format that is much more efficient than XML. Combine the most popular implementation if it, protobuf-net, with Griffin.Framework and you get an easy and fast way of sending information between processes.
(This is a follow up on my previous post.)
As I know that the format is quite efficient, I felt that it would be a powerful combination. I’ve therefore created a simple client/server where I made a benchmark for how long it would take for a Griffin.Framework client to send 20 000 messages to a Griffin.Framework server. The requirement was that the client has to receive the reply before sending the next message. To spice everything up I changed so that the client application used 20 clients simultaneously, and all clients had to receive all replies before the time was stopped.
As MicroMsg sends the type over the network it works well together with protobuf-net (which requires a specific type to be able to deserialize the message),
Both the server and client were running on the same machine.
The Message
The message being transferred and its reply looked like this:
[ProtoContract]
public class Authenticate
{
[ProtoMember(1)]
public string UserName { get; set; }
[ProtoMember(2)]
public string Password { get; set; }
}
[ProtoContract]
public class AuthenticateReply
{
[ProtoMember(1)]
public bool Success { get; set; }
[ProtoMember(2)]
public string Decision { get; set; }
}
The Serializer
To start with, I had to write a serializer for the library which uses protobuf.
class ProtoBufSerializer : IMessageSerializer
{
static ConcurrentDictionary<string, Type> _types = new ConcurrentDictionary<string, Type>();
public void Serialize(object source, Stream destination, out string contentType)
{
Serializer.NonGeneric.Serialize( destination, source);
contentType = "application/protobuf;" + source.GetType().FullName;
}
public object Deserialize(string contentType, Stream source)
{
Type type;
if (!_types.TryGetValue(contentType, out type))
{
int pos = contentType.IndexOf(";");
if (pos == -1)
throw new NotSupportedException("Expected protobuf");
type = Type.GetType(contentType.Substring(pos + 1), true);
_types[contentType] = type;
}
return Serializer.NonGeneric.Deserialize(type, source);
}
}
As you can see, I also use a Type
cache as Type.GetType()
is terrible slow, even thought it has a built in cache. You could implement a JSON or XML serializer in the same way.
The Server
As a server, you typically just create a ChannelTcpListener
:
var settings = new ChannelTcpListenerConfiguration(
() => new MicroMessageDecoder(new ProtoBufSerializer()),
() => new MicroMessageEncoder(new ProtoBufSerializer())
);
var server = new MicroMessageTcpListener(settings);
server.MessageReceived = OnServerMessage;
server.Start(IPAddress.Any, 1234);
That’s it, all you need now is a callback to receive messages from all clients:
private static void OnServerMessage(ITcpChannel channel, object message)
{
var auth = (Authenticate) message;
channel.Send(new AuthenticateReply() {Success = true});
}
How much code/configuration would you need in WCF to achieve the same thing?
The Client
In the client, you do about the same, but you use async
/await
instead of a callback.
private static async Task RunClient()
{
var client = new ChannelTcpClient<object>(
new MicroMessageEncoder(new ProtoBufSerializer()),
new MicroMessageDecoder(new ProtoBufSerializer())
);
await client.ConnectAsync(IPAddress.Parse("192.168.1.3"), 1234);
for (int i = 0; i < 20000; i++)
{
await client.SendAsync(new Authenticate { UserName = "jonas", Password = "king123" });
var reply = (AuthenticateReply)await client.ReceiveAsync();
if (reply.Success)
{
}
else
{
}
}
await client.CloseAsync();
}
The Result
If you study the screenshot, you will see that Griffin.Framework
only takes 28% of the total time for those messages. 37% of the time is used by protobuf-net. The remaining 35% is spent in .NET/Windows.
On my machine, that means that 800 000 .NET objects (400 000 requests and 400 000 replies) where transferred in 11 seconds time. That’s 0,01398375 milliseconds per object. Those digits are not as important as the fact that Griffin.Framework
only took 28% of the time being used by the application.