Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading a Protobuf Message in C#

I have a simple message:

package test;

message sendName {  
  optional string username = 1; 
}

The awesome VS plugin generates the .cs file:

namespace test {  

    [global::System.Serializable global::ProtoBuf.ProtoContract(Name=@"sendName")]

    public partial class sendName : global::ProtoBuf.IExtensible {

        public sendName() {}    

        private string _username = "";
        [global::ProtoBuf.ProtoMember(1, IsRequired = false, Name=@"username", DataFormat = > global::ProtoBuf.DataFormat.Default)]
        [global::System.ComponentModel.DefaultValue("")]
        public string username
        {
            get { return _username; }
            set { _username = value; }
        }

        private global::ProtoBuf.IExtension extensionObject;

        global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
        {
            return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing);
        }
    }
}

I am sending a message from the java side by using

objName.build().writeTo((FileOutputStream)socket.getOutputStream());

In my C# application, which acts like the Socket Listener, I have a method called Listen which listens for the message sent by the java client:

public void Listen()
{

    IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
    TcpListener listener = new TcpListener(ipAddress, 4055);
    TcpClient client = null;

    listener.Start();

    while (true)
    {
        Debug.WriteLine("Waiting for a Connection");

        client = listener.AcceptTcpClient();

        Stream stream = client.GetStream();

        // sendName sendNameObj = Serializer.Deserialize<sendName>(stream);
    }
}

I am obviously missing some basics here.

What method should I use to get the sendName object?


When I debug my code in C#, the debugger quits when I call DeserializeWithLengthPrefix method.

This is my C# code:

private void Listen()
{
    IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
    TcpListener listener = new TcpListener(ipAddress,4055);
    listener.Start();
    listener.BeginAcceptSocket(ClientConnected, listener);
}

private void ClientConnected(IAsyncResult result)
{
    TcpListener server = (TcpListener)result.AsyncState;
    using (TcpClient client = server.EndAcceptTcpClient(result))
    using (NetworkStream stream = client.GetStream())
    {
        try
        {
            //SendNameMessage sendNameObj = Serializer.Deserialize<SendNameMessage>(stream);
            SendNameMessage sendNameObj = Serializer.DeserializeWithLengthPrefix<SendNameMessage>(stream, PrefixStyle.Fixed32);
            string name = sendNameObj.sendName;
            if (name != null && name.Length > 0)
            {
                nameTextBox.Text = name;
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}

This is my java code:

SendNameMessage.Builder sendNameMessageObj = null;
sendNameMessageObj = SendNameMessage.newBuilder();
sendNameMessageObj.setSendName("Protobuf-net");

SocketRequest.INSTANCE.openSocket();
sendNameMessageObj.build().writTo(SocketRequest.INSTANCE.getWriter());
like image 760
sonu Avatar asked Jun 30 '10 21:06

sonu


1 Answers

The Serializer.Deserialize<sendName>(stream); should do it, but I'm guessing that it is hanging forever? The reason here is that protobuf messages don't have an inbuilt terminator, so the default is that it reads until the end of the stream - which won't happen until you close the socket.

To get around this, a common trick is to prefix the message with the length, and limit yourself to that. There may be an inbuilt way to handle that in the Java implementation, or you can handle it manually. On the C# side, protobuf-net has DeserializeWithLengthPrefix, which accepts an enum to specify how you've encoded it. For simplicity I would suggest a basic 32-bit little-endian encoding of the length (which maps to PrefixStyle.Fixed32).


Re the edit / comment: the two must match. At the moment you are serializing without a length prefix, but deserializing expecting a length prefix. Does the Java API expose a mechanism to obtain the data-length before you write it? If not, perhaps write first to a temp buffer, see how much you wrote, then write the length and finally the data.

Or; if you are only sending a single message - just close the stream and use Deserialize (no length prefix). I have a C#-to-C# multi-message sockets example available, but I can't comment much on the java side...

like image 54
Marc Gravell Avatar answered Nov 14 '22 23:11

Marc Gravell