开发者

Sending and receiving custom objects using Tcpclient class in 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;
开发者_如何学JAVA

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.


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(); 
  } 
}


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);
}


TCP is stream-based protocol (as opposed to datagram protocol) so it's possible to receive only part of sended data via Read method call.

To solve this problem you may use DataLength field (as cornerback84 suggested) or you may use your own "application-level packet" structure.

For example, you may use something like this

|-------------------------------|
|Begin|DataLength|   Data   |End|
| 4b  |   4b     | 1..MaxLen|4b |
|-------------------------------|

where Begin - start packet identifier (for example 0x0A, 0x0B, 0x0C, 0x0D) DataLength - Data field length (for example, from 0 to MaxLength) Data - actual data (serialized Person class or some other data) End - end packet identifier (for example 0x01, 0x05, 0x07, 0x0F).

That is, on client side you would wait not only for incoming data, after receiving data you would search you Application level packets, and you may deserialized Data part only after receiving valid packet.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜