5

I want to serialize a 'Player' class and send it over my network stream to a client.

Player Class

    [ProtoMember(1)]
    public int flag;
    [ProtoMember(2)]
    public Int16 id;
    [ProtoMember(3)]
    public MyVector3 CharPos;
    [ProtoMember(7)]
    public bool spawned;

MyVector3 (due to the fact that protobuf does not support serialization of Vector3)

[ProtoContract]
public class MyVector3
{
    [ProtoMember(4)]
    public float X { get; set; }

    [ProtoMember(5)]
    public float Y { get; set; }

    [ProtoMember(6)]
    public float Z { get; set; }

    public MyVector3()
    {
        this.X = 0.0f;
        this.Y = 0.0f;
        this.Z = 0.0f;
    }

    public MyVector3(float x, float y, float z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    public static implicit operator Vector3(MyVector3 v)
    {
        return new Vector3(v.X, v.Y, v.Z);
    }

    public static implicit operator MyVector3(Vector3 v)
    {
        return new MyVector3(v.X, v.Y, v.Z);
    }
}

Serialize and Deserialize Function

public byte[] serialize(Object obj)
    {
        if (obj == null)
        {
            return null;
        }
        MemoryStream ms = new MemoryStream();
        Serializer.SerializeWithLengthPrefix(ms,obj,PrefixStyle.Base128);
        return ms.ToArray();
    }

    public Player deserialize(NetworkStream inc)
    {
        Player obj = Serializer.DeserializeWithLengthPrefix<Player>(inc,PrefixStyle.Base128);
        return obj;
    }

Test Function (which in fact does not work!)

    static void Main(string[] args)
    {
        TcpListener serverSocket = new TcpListener(8888);
        TcpClient clientSocket = default(TcpClient);

        Serialize ser = new Serialize();

        Player temp = new Player();

        serverSocket.Start();

        while (true)
        {
            clientSocket = serverSocket.AcceptTcpClient();
            using (NetworkStream networkStream = clientSocket.GetStream())
            {
                DeserializeTest(ser,networkStream);
                SerializeTest(ser, networkStream);
            }
        }

    }

    static void SerializeTest(Serialize ser,NetworkStream networkStream)
    {
        Player temp = new Player();
        BinaryWriter writer = new BinaryWriter(networkStream);
        writer.Write(ser.serialize(temp));
        networkStream.Flush();
    }

    static void DeserializeTest(Serialize ser, NetworkStream networkStream)
    {
        Player temp = new Player();
        temp = (Player)ser.deserialize(networkStream);
        Console.WriteLine(temp.flag.ToString() + temp.CharPos.ToString());
        networkStream.Flush();
    }
}

What now happens:

When the DeserializeTest is launched, and protobuf tries to deserialize the data from the networkStream the whole function kind of "Freezes" and starts to loop. When I'm only serializing the data (in the client) and send it to the server (this code) where its deserialized, everything works like a charm.

Daniel Mann
  • 57,011
  • 13
  • 100
  • 120
Nop0x
  • 173
  • 1
  • 14
  • Oh, as a side note - I'm pretty sure v2 can handle `Vector3`; I can show you how to configure that if you want – Marc Gravell Jan 17 '12 at 20:39
  • would be very nice how to handle this! And can you show me how to properlx user SerializeWithLengthPrefix and DeserializeWithLengthPrefix ? – Nop0x Jan 17 '12 at 20:40
  • added that already; re Vector2 - I don't have XNA to hand - I would have *expected* it to work out-of-the-box (under the "tuple" handling, since it has a suitable constructor) - however: something like `RuntimeTypeModel.Default.Add(typeof(Vector3), false).Add("x","y","z");` should do it – Marc Gravell Jan 17 '12 at 20:44
  • thanks! this seems to work out, but when i try now to use DeserializeWithLenghtPrefix i get a ProtoException with invalid wire type. i already had a look at [Help!](http://stackoverflow.com/q/2152978/23354) but networkstreams do not support Position :/ also RuntimeTypeModel seems not to be supported. – Nop0x Jan 17 '12 at 20:50
  • re invalid wire type - that *usually* happens if you have, for example, over-written a file without changing the length. Can you update to show what you have, so I can repro? – Marc Gravell Jan 17 '12 at 21:01
  • okay, i just updated the code obove :) – Nop0x Jan 17 '12 at 21:13
  • k; I'm on a call at the moment, but will look – Marc Gravell Jan 17 '12 at 21:17
  • Found the problem; it was the serialize(Object obj) - should be typed as `Player` - it is introducing a silent `` into the mix, which it doesn't like. Full example in edit; note also the usage of the MyVector3 – Marc Gravell Jan 17 '12 at 21:58
  • Note also the use of `DataFormat.Group` - that is entirely optional, but helps avoid a minute, barely measurable overhead in some cases ;p – Marc Gravell Jan 17 '12 at 22:00

1 Answers1

8

A network stream does not end until it is closed, and by default protobuf (as defined by Google) consumes to the end of the stream. It is waiting for more data, or for your stream to conclusively end. If you are sending multiple messages, or just want to keep the stream open, then replace Serialize and Deserialize with SerializeWithLengthPrefix and DeserializeWithLengthPrefix. This will add extra information to let it get an individual message without closing the stream. It is important that both ends of the pipe know that they are using this variant.


Full example (this is using core .NET, but should translate without issue now):

using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using ProtoBuf;
[ProtoContract]
public class Player
{
    [ProtoMember(1)] public int flag;
    [ProtoMember(2)] public Int16 id;
    [ProtoMember(3, DataFormat = DataFormat.Group)] public MyVector3 CharPos;
    [ProtoMember(7)] public bool spawned;
}

public struct MyVector3
{
    public readonly float X, Y, Z;
    public MyVector3(float x, float y, float z)
    {
        X = x;
        Y = y;
        Z = z;
    }
    public override string ToString()
    {
        return string.Format("({0},{1},{2})", X, Y, Z);
    }
}

static class Program
{
    static ManualResetEvent evt = new ManualResetEvent(false);
    static void Main(string[] args)
    {
        var player = new Player() {CharPos = new MyVector3(1, 2, 3), flag=123, id=456, spawned=true};
        ThreadPool.QueueUserWorkItem(x =>
        {
            Console.WriteLine("client: waiting for server");
            evt.WaitOne();
            Console.WriteLine("client: opening connection");
            using (var client = new TcpClient("localhost", 15000))
            using (var ns = client.GetStream())
            {
                serialize(ns, player);
                ns.Flush();
                Console.WriteLine("client: wrote player");

                Console.WriteLine("client: waiting for response");
                while (ns.ReadByte() >= 0)
                {
                    Console.WriteLine("client: receiving...");
                }
                Console.WriteLine("client: connection closed by server");
                ns.Close();
            }
        });
        TcpListener serverSocket = new TcpListener(15000);
        TcpClient clientSocket;

        serverSocket.Start();

        Console.WriteLine("server: accepting connections");
        evt.Set();
        while (true)
        {
            Console.WriteLine("server: waiting for client...");
            clientSocket = serverSocket.AcceptTcpClient();
            Console.WriteLine("server: got client");
            using (NetworkStream networkStream = clientSocket.GetStream())
            {
                var fromNetwork = deserialize(networkStream);
                Console.WriteLine("server: got player");
                Console.WriteLine("> flag: {0}", fromNetwork.flag);
                Console.WriteLine("> id: {0}", fromNetwork.id);
                Console.WriteLine("> spawned: {0}", fromNetwork.spawned);
                Console.WriteLine("> pos: {0}", fromNetwork.CharPos);
            }
        }

    }
    public static void serialize(Stream dest, Player player)
    {
        if (player == null) throw new ArgumentNullException();
        Serializer.SerializeWithLengthPrefix(dest, player, PrefixStyle.Base128);
    }

    public static Player deserialize(Stream inc)
    {
        Player obj = Serializer.DeserializeWithLengthPrefix<Player>(inc, PrefixStyle.Base128);
        return obj;
    }
}

This gives:

client: waiting for server
server: accepting connections
server: waiting for client...
client: opening connection
server: got client
client: wrote player
client: waiting for response
server: got player
> flag: 123
> id: 456
> spawned: True
> pos: (1,2,3)
client: connection closed by server
server: waiting for client...
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thanks first for your help, but im not sure which parameters to give to SerializeWithLengthPrefix oder DeserializeWithLengthPrefix according to my code, because i can not really get the sentence of the doc. – Nop0x Jan 17 '12 at 20:37
  • Actually, it won't matter as long as they agree! But my preferred options would be: `..., PrefixStyle.Base128, 1` – Marc Gravell Jan 17 '12 at 20:39