Search code examples
c#.nettcpclient

simplest way to send an object with Tcpclinet c#


im new to c# and i was wondering , what is the simplest way to send an object using tcpClient , itried the following code but it throws a wierd error client code :

TcpClient client = new TcpClient(ip, port);
StreamWriter writer = new StreamWriter(client.GetStream());
NetworkStream strm = client.GetStream();
BinaryFormatter formatter = new BinaryFormatter();
Transaction tx = new transaction();

string msg = string.Empty;

msg = "transaction";
writer.WriteLine(msg);
writer.Flush();

formatter.Serialize(strm,tx);

and on the receiving end

Server Code :

while(true){
TcpClient client = server.AcceptTcpClient();
IFormatter formatter = new BinaryFormatter();
NetworkStream strm = client.GetStream();
StreamReader reader = new StreamReader(client.GetStream());
string msg = string.Empty;
while (!((msg = reader.ReadLine()).Equals("exit"))){

Transaction tx = (Transaction)formatter.Deserialize(strm);

}

it produces this error on the server

input stream is not a valid ibinary format intital content is :0c-02-00-00 .....

so can someone please help me or if there another simple and clean way of sending objects using tcpclient ?


Solution

  • The underlying problem here is tied into the way that you are mixing two different mechanisms to read and write to a stream, specifically: using StreamReader and a separate stream-based parser. It is also a bad idea to do this with StreamWriter, but ... I think you would get away with it sort-of, although it is still a bad idea.

    The problem here is that StreamReader is greedy. When you ask it for a line, it doesn't read from the stream byte-by-byte looking for a \r or \n - it grabs a buffer of data from the stream, and then processes it as you ask for it. In this way, it assumes that it is now the sole owner of the stream.

    So; when you do this:

    while (!((msg = reader.ReadLine()).Equals("exit"))){
       Transaction tx = (Transaction)formatter.Deserialize(strm);
    }
    

    the reader consumes more than just "transaction\r\n" - it consumes that line and some undefined number of bytes from whatever comes after. Then, when BinaryFormatter tries to read the stream, it finds itself half way through a message, and it explodes in a shower of sparks.

    Ideally, limit yourself to one serialization mechanism. Meaning: lose StreamReader/StreamWriter completely here.

    If I could propose an alternative mechanism using protobuf-net and inheritance:

    [ProtoContract]
    [ProtoInclude(1, typeof(ShutdownMessage))]
    [ProtoInclude(2, typeof(TransactionMessage))]
    public abstract class MessageBase {} 
    
    [ProtoContract]
    public sealed class ShutdownMessage : MessageBase {}
    
    [ProtoContract]
    public sealed class TransactionMessage : MessageBase {
        // your data here
    }
    

    and now you can send any number of messages with:

    public void Send(MessageBase message) {
        Serializer.SerializeWithLengthPrefix(strm, message, PrefixStyle.Base128);
    }
    

    and receive any number of messages with:

    while (true) {
        var msg = Serializer.DeserializeWithLengthPrefix<MessageBase>(strm, PrefixStyle.Base128);
        if (msg is null || msg is ShutdownMessage) break; // all done
        switch (msg)
        {
            case TransactionMessage tx: ProcessTransaction(tx); break;
            // etc
        }
    }