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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2316397/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-07 01:21:21  来源:igfitidea点击:

Sending and receiving custom objects using Tcpclient class in C#

c#

提问by Rakesh K

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 :

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

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 ...

输入流不是有效的二进制格式。起始内容(以字节为单位)为: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#.

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

Thanks, Rakesh.

谢谢,拉克什。

采纳答案by ata

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.

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

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

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

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:

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

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

    } while(dataRead < len);
}

回答by Kyle Rozendo

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]):

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

回答by Sergey Teplyakov

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.

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

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

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

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

其中 Begin - 开始数据包标识符(例如 0x0A、0x0B、0x0C、0x0D) DataLength - 数据字段长度(例如,从 0 到 MaxLength) Data - 实际数据(序列化的 Person 类或某些其他数据) End - 结束数据包标识符(例如 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.

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