C#通过同一套接字发送多个对象

3
我是一个有用的助手,可以翻译文本。
我正在尝试从一个套接字向另一个套接字发送多个不同的对象(它们是相同的对象类型,只是具有不同的值)。我可以使用以下代码成功地发送一个对象:
客户端:
var buffer = new byte[1024];
var binFormatter = new BinaryFormatter();
using (var ms = new MemoryStream())
{
    int bytesRead;
    while ((bytesRead = _clientReceive.Receive(buffer)) > 0);
    {
        ms.Write(buffer, 0, bytesRead);
    } 

    ms.Position = 0;
    message = (Message)binFormatter.Deserialize(ms);
} 

服务器:
var gm = new Message { Message = "ObjectType", Object = Data };

using (var mem = new MemoryStream())
{
    var binFormatter = new BinaryFormatter();
    binFormatter.Serialize(mem, gm);
    var gmData = mem.ToArray();
    client.Send(gmData, gmData.Length, SocketFlags.None);
    client.Close();
}

但是,如果我想发送多个消息(将完整的客户端代码放入循环中,而不是调用 client.Close()),我该如何确定客户端已经接收到完整的对象?像这样将客户端代码放入循环中:
while (_isReceivingStarted)
{
    var buffer = new byte[1024];
    var binFormatter = new BinaryFormatter();
    using (var ms = new MemoryStream())
    {
        int bytesRead;
        while ((bytesRead = _clientReceive.Receive(buffer)) > 0)
        {
            ms.Write(buffer, 0, bytesRead);
        }
        ms.Position = 0;
        message = (Message) binFormatter.Deserialize(ms);
    }
    // Do stuff then wait for new message
}

客户端会停在_clientReceive.Receive(buffer),因为它没有接收到来自客户端的Close(),也从未收到0个字节。如果我在服务器上关闭连接,它将循环执行,然后在反序列化MemoryStream时出错,而不是在_clientReceive.Receive(buffer)处阻塞,等待发送另一个对象。
我希望这有意义。有什么指针吗?
5个回答

3
我强烈建议您查看 Windows Communication Foundation。它将为您处理所有这些事情,包括通过多个可配置信道在线路上传输和反序列化数据类型。

用WCF替换套接字在开销等方面是极端的跳跃。 - Marc Gravell
@Marc:在某种程度上是对的——如果在这种情况下开销非常重要,那么这可能是一个问题。但是考虑到当前的代码(打开/关闭连接并使用同步套接字代码完成所有操作),所感知的开销很容易会更低... - Reed Copsey
我需要低开销的快速通信,否则WCF将是极好的选择。大多数对象的大小在600-800字节之间,但偶尔会有一个对象的大小达到3MB。我需要以每秒20-100个的速度发送较小的对象。 - joe_coolish

3
当使用这样的socket时,我建议使用像protobuf-net这样的东西而不是BinaryFormatter(警告/披露:我写过它)。特别是,如果你使用Base128作为前缀样式(其中之一)以及已知标记(另一个选项)如1来写每个项目,然后在另一端只需要使用DeserializeItems(...)(再次指定Base128和1),就可以了。
这将正确地捕捉到项目,在流关闭之前不会试图过度读取,当流关闭时,它将yield break。每个对象单独返回(yield return),因此它不会在给你数据之前尝试消耗整个流。
如果你喜欢,它也可以用于异构数据,但同质最容易。

嗯...这听起来非常符合我的需求。我会研究一下并回复你的!谢谢 :) - joe_coolish
@Joe,如果我没记错的话,快速入门项目中有一个套接字示例。 - Marc Gravell
1
@Joe,顺便说一下,protobuf几乎总是比BinaryFormatter更小的带宽和CPU。 - Marc Gravell
谢谢!我已经仔细查看了,一切都很顺利,但是我与我的教授(这是为学校项目)交谈后,他说由于我没有编写protobuf,所以不能使用它:( 但是!我肯定会在其他应用程序中使用它。开销非常小!我将我的800字节序列化数组缩小到<600,并且我的3个对象只有2.5!这是因为它不必对成员名称进行编码吗? - joe_coolish
2
@Joe 正确;现在……我想知道你的教授是否意识到你还没有编写BinaryFormatter,Stream等……课程是奇怪的环境,但需要关注的是“不发明轮子”。在专业工作中,“不发明轮子”是一种代价高昂的态度 :) - Marc Gravell
哈哈,说真的。我还记得几年前上大学一年级时,我们不得不重新发明C++中STL的几个部分(这很好,毕竟),前提是我们要用困难的方法来做,然后再学习简单的方法。但当学习STL的时候,学期已经结束了……我仍然没有从教授那里学到关于STL的知识。太高兴了,这是我最后一节课!!!再次感谢您提供的出色项目。我会在空闲时间里查看代码。很高兴看到有人仍在支持.netCF! - joe_coolish

1
你可以发送一个头部消息,告知接收端应该期望多少字节的数据。这类似于HTTP中的Content-Length指令。另一种选择是,在结尾处发送一个自定义的终止字符串(显然不能在序列化对象的二进制负载中以位为单位出现,这就是为什么我会选择前一种解决方案的原因)。

0

0
如果你想这样做,这里有一些建议:
  • 发送 X 字节数并不能保证你会在客户端接收到一个单独的 Receive()。你可以在客户端使用一个 MemoryStream 和缓冲区,使用缓冲区接收数据并将其写入 MemoryStream 直到 Receive 返回 0。
  • 当你完成发送数据后,在调用 Close() 前使用 client.Shutdown(SocketShutdown.Send)(或 .Both)。这将防止 TCP RST 在客户端上。
  • 如果要序列化多个对象,请在服务器上依次对它们进行序列化。然后客户端将缓冲所有传入数据,并在 Receive() 返回 0 后,将客户端 MemoryStream 的位置移动到 0,并开始逐个反序列化对象,直到 ms.Position == ms.Length。

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