0

我的背景

我有一个 TCP 网络程序,它通过连接发送已序列化并编码为 base64 的大型对象。我编写了一个客户端库和一个服务器库,它们都使用NetworkStream's Begin/EndReadBegin/EndWrite. 这是我正在使用的(非常简化的版本)代码:

对于服务器:

var Server = new TcpServer(/* network stuffs */);
Server.Connect();
Server.OnClientConnect += new ClientConnectEventHandler(Server_OnClientConnect);

void Server_OnClientConnect()
{
    LargeObject obj = CalculateLotsOfBoringStuff();
    Server.Write(obj.SerializeAndEncodeBase64());
}

然后客户端:

var Client = new TcpClient(/* more network stuffs */);
Client.Connect();
Client.OnMessageFromServer += new MessageEventHandler(Client_OnMessageFromServer);

void Client_OnMessageFromServer(MessageEventArgs mea)
{
    DoSomethingWithLargeObject(mea.Data.DecodeBase64AndDeserialize());
}

客户端库有一个回调方法,NetworkStream.BeginRead该方法触发将OnMessageFromServer数据作为字符串传递的事件MessageEventArgs

我的问题

但是,当通过 接收大量数据BeginRead/EndRead时,它似乎被分散在多条消息中。EG 假装这是一条很长的信息:

"This is a really long message except not because it's for explanatory purposes."

如果那真的是一条长消息,Client_OnMessageFromServer可能会被称为......说三遍“长消息”的碎片部分:

"This is a really long messa"

"ge except not because it's for explanatory purpos"

"es."

呜呜呜……深呼吸

将所有内容通过一个发送到Begin/EndWrite一个电话即可接收的最佳方式是Client_OnMessageFromServer什么?

4

2 回答 2

6

你不能。在 TCP 上,事物的到达方式不一定与它们的发送方式相同。您的代码的工作是了解什么构成完整的消息,并在必要时缓冲传入的数据,直到您拥有完整的消息(注意不要丢弃处理过程中一条消息的开始)。

在文本协议中,这通常意味着“发现换行符/空字符”。对于二进制,它通常意味着“读取消息前导中的长度标头”。

于 2012-08-12T23:13:43.420 回答
4

TCP 是一种流协议,没有固定的消息边界。这意味着您可以接收到消息的一部分或一个消息的结尾和另一个消息的开头。

有两种方法可以解决这个问题:

  1. 更改您的协议以添加消息结束标记。这样您就可以不断收到,直到找到特殊标记。但是,这可能会导致您有一个缓冲区,其中包含一条消息的结尾和另一条消息的开头,这就是我推荐下一种方式的原因。
  2. 更改协议以首先发送消息的长度。然后您将确切知道该消息有多长,并且可以在接收时倒计时,这样您就不会阅读下一条消息的开头。
于 2012-08-12T23:17:59.403 回答