在C#中使用Tcpclient类发送和接收自定义对象

我有一个客户端服务器应用程序,其中服务器和客户端需要通过网络发送和接收自定义类的对象。 我正在使用TcpClient类来传输数据。 我在发送方端序列化对象并将得到的字节流发送到接收方。 但是在接收器中,当我尝试对接收到的字节进行反序列化时,它会抛出序列化exception,详细信息如下:

输入流不是有效的二进制格式。 起始内容(以字节为单位)为:0D-0A-00-01-00-00-00-FF-FF-FF-FF-01-00-00-00-00-00 …

我序列化对象的服务器代码是:

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

反序列化的客户端代码是:

 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; 

请帮我解决这个问题,并可能建议使用C#中的TcpClient在网络上传输自定义类对象的任何其他方法。

谢谢,拉克什。

在客户端接收时,您不知道要读取多少数据。 您只依赖于ReceiveBufferSize,而您的数据可能更大或更小。

我认为这里最好的方法是发送4个字节,告诉客户端传入数据的长度:

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

在接收端,您首先读取数据长度,然后读取确切的数据量。

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

事实上,我刚刚意识到,你可能需要做更多的事情以确保你阅读所有数据(只是一个想法,因为我没有尝试过,但只是因为你再次遇到问题,你可以试试这个)。

NetworkStream.Read()方法返回一个数字,表示它已读取的数据量。 输入数据可能比RecieveBuffer大。 在这种情况下,您必须循环,直到您读取所有数据。 你必须做这样的事情:

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

看看这段代码 。 它需要稍微不同的方法。

上面链接给出的例子: – 注意:他面临的另一个问题是他在这里解决了(保持活着)。 它位于初始示例代码之后的链接中。

要发送的对象类(记住[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; ... } } 

要发送对象的类:

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

接收对象的类:

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

TCP是基于流的协议(与数据报协议相反),因此可以通过Read方法调用仅接收部分已发送数据。

要解决此问题,您可以使用DataLength字段(建议使用cornerback84),也可以使用自己的“应用程序级数据包”结构。

例如,您可以使用这样的东西

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

其中Begin – 起始数据包标识符(例如0x0A,0x0B,0x0C,0x0D)DataLength – 数据字段长度(例如,从0到MaxLength)数据 – 实际数据(序列化Person类或其他一些数据)端 – 端数据包标识符(例如0x01,0x05,0x07,0x0F)。

也就是说,在客户端,您不仅要等待传入数据,在接收数据后,您将搜索应用程序级别数据包,并且只有在收到有效数据包后才可以反序列化数据部分。