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

12
我有一个客户端服务器应用程序,需要在网络上发送和接收自定义类的对象。我使用TcpClient类传输数据。我将对象序列化并将结果字节流发送到接收方。但是在接收方,当我尝试反序列化接收到的字节时,会抛出Serialization Exception异常,详细信息如下:
"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 ..."
我的服务器代码将对象序列化为:
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传输自定义类对象的其他方法。
谢谢, Rakesh。
3个回答

13

请看这段代码,它采取了稍微不同的方法。

上面链接给出的示例: - 注意:他还遇到另一个问题,在初始样本代码后的链接中解决了这个问题(keep-alive)。

发送对象类(记得 [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(); 
  } 
}

12

在客户端接收数据时,您不知道要读取多少数据。您只能依赖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()方法返回一个数字,指示它已经读取了多少数据。 如果传入的数据大于接收缓冲区,则可能需要循环读取直到读取完所有数据。你需要做如下操作:

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

    } while(dataRead < len);
}

1

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

为了解决这个问题,您可以使用DataLength字段(如cornerback84建议的那样),或者您可以使用自己的“应用层数据包”结构。

例如,您可以使用类似以下内容的东西:

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

在编程中, Begin-起始包标识符(例如0x0A、0x0B、0x0C、0x0D) DataLength-数据字段长度(例如从0到MaxLength) Data-实际数据(序列化的Person类或其他一些数据) End-结束包标识符(例如0x01、0x05、0x07、0x0F)。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接