Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending and receiving custom objects using Tcpclient class in C#

Tags:

c#

I have a client server application in which the server and the client need to send and receive objects of a custom class over the network. I am using TcpClient class for transmitting the data. I am serializing the object at the sender side and sending the resulting stream of bytes to the receiver. But at the receiver, when I try to de-serialize the bytes received, it throws Serialization Exception and the details are :

The input stream is not a valid binary format. The starting contents (in bytes) are: 0D-0A-00-01-00-00-00-FF-FF-FF-FF-01-00-00-00-00-00 ...

My server code that serializes the object is:

byte[] userDataBytes;
MemoryStream ms = new MemoryStream();
BinaryFormatter bf1 = new BinaryFormatter();
bf1.Serialize(ms, new DataMessage());
userDataBytes = ms.ToArray();
netStream.Write(userDataBytes, 0, userDataBytes.Length);

The client code that de-serializes it is:

readNetStream.Read(readMsgBytes, 0, (int)tcpServer.ReceiveBufferSize);
MemoryStream ms = new MemoryStream(readMsgBytes);
BinaryFormatter bf1 = new BinaryFormatter();
ms.Position = 0;
object rawObj = bf1.Deserialize(ms);
DataMessage msgObj = (DataMessage)rawObj;

Please help me to solve this problem and possibly suggest any other method to transmit objects of custom classes across network using TcpClient in C#.

Thanks, Rakesh.

like image 832
Rakesh K Avatar asked Feb 23 '10 05:02

Rakesh K


2 Answers

Have a look at this code. It takes a slightly different approach.

Example given by the link above: - Note: there was another problem he was facing which he solved here (keep-alive). It's in the link after the initial sample code.

Object class to send (remember the [Serializable]):

[serializable] 
public class Person { 
   private string fn; 
   private string ln; 
   private int age; 
   ... 
   public string FirstName { 
      get { 
         return fn; 
      } 
      set { 
         fn=value; 
      } 
   } 
   ... 
   ... 
   public Person (string firstname, string lastname, int age) { 
      this.fn=firstname; 
      ... 
   } 
} 

Class to send object:

using System; 
using System.Net; 
using System.Net.Sockets; 
using System.Runtime.Serialization; 
using System.Runtime.Serialization.Formatters.Binary; 

class DataSender 
{ 
  public static void Main() 
  { 
   Person p=new Person("Tyler","Durden",30); // create my serializable object 
   string serverIp="192.168.0.1"; 

   TcpClient client = new TcpClient(serverIp, 9050); // have my connection established with a Tcp Server 

   IFormatter formatter = new BinaryFormatter(); // the formatter that will serialize my object on my stream 

   NetworkStream strm = client.GetStream(); // the stream 
   formatter.Serialize(strm, p); // the serialization process 

   strm.Close(); 
   client.Close(); 
  } 
} 

Class to receive object:

using System; 
using System.Net; 
using System.Net.Sockets; 
using System.Runtime.Serialization; 
using System.Runtime.Serialization.Formatters.Binary; 

class DataRcvr 
{ 
  public static void Main() 
  { 
   TcpListener server = new TcpListener(9050); 
   server.Start(); 
   TcpClient client = server.AcceptTcpClient(); 
   NetworkStream strm = client.GetStream(); 
   IFormatter formatter = new BinaryFormatter(); 

   Person p = (Person)formatter.Deserialize(strm); // you have to cast the deserialized object 

   Console.WriteLine("Hi, I'm "+p.FirstName+" "+p.LastName+" and I'm "+p.age+" years old!"); 

   strm.Close(); 
   client.Close(); 
   server.Stop(); 
  } 
}
like image 117
Kyle Rosendo Avatar answered Oct 04 '22 23:10

Kyle Rosendo


When receiving on client side you do not know how much data you want to read. You are only relying on the ReceiveBufferSize, while your data can be larger or smaller then that.

I think the best approach here is to send 4 bytes that tells your client about the length of incoming data:

byte[] userDataLen = BitConverter.GetBytes((Int32)userDataBytes.Length);
netStream.Write(userDataLen, 0, 4);
netStream.Write(userDataBytes, 0, userDataBytes.Length);

and on the recieving end you first read the data length and then read exact amount of data.

byte[] readMsgLen = new byte[4];
readNetStream.Read(readMsgLen, 0, 4);

int dataLen = BitConverter.ToInt32(readMsgLen);
byte[] readMsgData = new byte[dataLen];
readNetStream.Read(readMsgData, 0, dataLen);

Infact, I just realized, that you might has to do a little more to assure you read all data (just an idea because I haven't tried it, but just incase you run into problem again you can try this).

The NetworkStream.Read() method returns a number indicating the amount of data it has read. It might be possible that the incoming data is larger then the RecieveBuffer. In that case you have to loop until you read all of the data. You have to do something like this:

SafeRead(byte[] userData, int len)
{
    int dataRead = 0;
    do
    {       
        dataRead += readNetStream.Read(readMsgData, dataRead, len - dataRead);

    } while(dataRead < len);
}
like image 21
ata Avatar answered Oct 04 '22 22:10

ata