微小的传输最终联系在一起,我真的需要解析缓冲区吗?

时间:2015-10-29 16:07:56

标签: c++ sockets c++11

我正在做一些套接字编程,而且我遇到了一些麻烦。

我已经定义了服务器和客户端之间的通信协议,作为扑克游戏的一部分,所以当任何一方想要在farside上触发某个动作时,它会创建一个int(简单封送)阵列,其中第一个int是"操作码",一个简单的代码,详细说明了数据的使用方式,以及传输的大小。任何后续的int都可以看作是该操作的参数(如果适用),模拟函数调用。

服务器在客户端连接时需要做的一件事就是通知其他客户新的一个已加入游戏,因此它会发送一个新的播放器已连接"发送给所有其他客户端的消息,其中包括该新玩家的表号。

当服务器尝试快速连续通知客户端时会出现问题,正如我的伪代码将演示的那样。发生的事情是服务器发送两个8字节的传输,这些传输在farside上作为单个16字节(4个字节)被接收。结果是客户端只传递并使用前8个字节,忽略其余部分,导致mayham(打印出大量的垃圾清单),例如,当未列出的玩家在聊天中说出某些内容时。

由于我无法显示我的实际代码,请在服务器端的相关部分考虑以下伪代码:

enum PROTOCOLENUMS
{
    NOTIFYPROTOCOL = //some number
}

//Reps is simple vector containing custom objects of type playerRepresentation.
for(auto player : Reps)
{
   if (player.getSocketNr() != newfiledescriptor)
   {

       int oldguyInfo[2] = 
       {NOTIFYPROTOCOL,player.getSocketNr() };
       send(newfd,oldguyInfo,sizeof(oldguyInfo),0);
       //the old players are simultaniously notified of the new player here.
   }
}

在客户端,负责接收传输并传递它的代码看起来像这样(忽略错误检查):

short bytecount
while (true)
{
    bytecount = recv(serverSocketDescriptor,buffer,sizeof(buffer),0);
    cout<<bytecount<<endl; //Bytecount should ready 8 every time, instead it will accumulate if recv doesn't get called between sends.
    InterfacePtr->processTransmission(buffer);
}

就像我说的那样,当接收端的缓冲区包含两个(或更多)发送到内部缓存时,问题就出现了,即传输发生得如此之快以至于recv()没有及时解除阻塞第二个消息到达之前缓冲区中的第一条消息。因此,只有第一个传输实际上被后续处理函数(IE processTransmission())使用。

我想也可能是故障位于服务器端,也就是说底层API认为通过将两个小传输打包成一个来节省一些带宽,而不是直接发送。但这似乎不太可能。

现在,我真正想问的是:我是否真的必须在每次recv之后解析缓冲区,以确定是否有多组传输可用?

3 个答案:

答案 0 :(得分:2)

好吧,我们无法看到您正在使用的协议 - TCP(/ IP)或UDP(/ IP)。如果它是TCP,那么你所描述的行为是预期的,因为它是一个“字节流”连接,数据“只是不停地进入”,你可以“根据需要/需要时”阅读它。

如果你使用UDP,那么每条消息都是独立的(它是一个“数据报”),没有“一起网格化”,但是也不会支持错误处理(数据报到达或者它没有,就是这样。)

因此,请检查套接字创建代码,如果您无法理解它,请分享它。

更新:

为了帮助使用TCP进行消息传递,您可以使用“消息分隔”方案/协议,例如Async HDLC。你可以查找它,但实质上,你在消息之间发送0x7E(一个或多个字节),如果要发送0x7E,用0x7F 0x5E(xor w / 0x20)替换(编码) - 当然,你需要编码也是0x7F,但如果你愿意,可以编码其他字节(例如,0x00)。解码很简单,可以“就地”完成。

更新2:

如果你对自己做“可靠的UDP”感到紧张,那么就有一些库。我在一个重要的项目中使用了UDT(http://udt.sourceforge.net/),它运行良好。它的接口是“套接字”,因此,切换到/试用它应该不难。

答案 1 :(得分:2)

这种事情的正常方法是定义一个协议。 协议是将每条消息作为逻辑单元引入的形式化方式。

某些协议(例如HTTP)使用分隔符(如换行符)来分隔消息的各个部分(以及消息本身)。

在游戏中,拥有一个二进制协议更为正常,每个消息都以其长度开头。

例如,您可以采用的一种协议是每条消息前面都有4个字节,用于编码消息长度。

那么你的读者结尾会是这样的:

while(running)
{
    int32_t wire_length = 0;
    recv(fd, &length, sizeof(length), 0);
    auto length = ntohl(wire_length);
    std::vector<uint8_t> buffer(length);
    recv(fd, &buffer[0], length, 0);
    // buffer now contains exactly one message
    // decode and dispatch here
 }

答案 2 :(得分:1)

总之,是的。您希望使用尽可能多的消息并丢弃使用的消息,以免再次使用它们。此外,您必须在发送部分消息时考虑到这一点。鉴于您知道每条消息的长度,上述两项任务应该是直截了当的。