如何通过解析缓冲区并将数据放入正确的结构来处理传入的数据包连接?

How can I handle an incoming packet connection by parsing the buffer and placing the data into the correct struct?

我有一个工作正常的开放连接,但现在我要添加我必须处理的第二种回复类型。回复转到两个不同的结构。

回复类型一是大小为 16 的结构 = replyTypeOne (int, int, int, int) 回复类型二是大小为 64 的结构 = replyTypeTwo (int, int, int, char[48], int)

之前我将回复直接发送到正确的结构 - 现在我必须添加一个缓冲区来确定回复应该发送到哪个结构。

以下代码的旧句柄:

iResult = recv(connectSocket, (char *)&replyTypeOne, sizeof(replyTypeOne), 0);

以上代码会将回复字段放在适当的位置。但是,由于 replyTypeTwo 不同于 replyTypeOne - 我需要一个解决方案来确定彼此的两个回复。

所以我计划使用缓冲区(也许我应该使用 MSG_PEEK??)来查看传入数据并将其放入正确的结构中。我只是不确定如何处理/解析缓冲区回复 atm。

编辑: 添加了 replyTemp 结构 - int, int, int, int, char[48], int

#define DEFAULT_BUFLEN 128

int iResult;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;

iResult = recv(connectSocket, (char *)&replyTemp, sizeof(replyTemp), 0)

if(iResult > 0)
{
 if(replyTemp.length > 16)
  {
   //handle replyTypeTwo -  assign replyTypeTwo fields to replyTemp fields
  } else {
   // handle replyTypeOne - asking replyTypeOne fields to replyTemp fields
  }
}
else if(iResult == 0)
{
  connectSocket = INVALID_SOCKET;
}

处理此问题的一种方法是使用一个通用的 header,所有回复数据包都继承自该字段,其中包含一个类型(通常是一个大小)字段。然后你所要做的就是检查类型并适当地转换。

当您收到一个不完整的数据包(或一次收到多个数据包)时,您需要增强代码来处理这种情况,但这方面的基础是:

struct replyCommon
{
    short type;
};

struct replyOne : public replyCommon
{
    int a, b, c, d;
};

struct replyTwo : public replyCommon
{
    int a, b, c;
    char d[48];
    int e;
};

#define DEFAULT_BUFLEN 128

int iResult;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;

iResult = recv(connectSocket, recvbuf, recvbuflen, 0)

if(iResult > sizeof(replyCommon))
{
    if (reinterpret_cast<replyCommon*>(recvbuf)->type == 1)
        handleReplyOne(reinterpret_cast<replyOne*>(recvbuf));
    else
    if (reinterpret_cast<replyCommon*>(recvbuf)->type == 2)
        handleReplyTwo(reinterpret_cast<replyTwo*>(recvbuf));

}
else if(iResult == 0)
{
  connectSocket = INVALID_SOCKET;
}

“偷看”的最简单方法是 recv 然后再 recv 一些。

由于这似乎是 TCP,您已经需要一个 recv 循环。 TCP 总是需要 recv 循环。当您要求 recv(sock, &buf, 16, 0) 时,不能保证您会得到 16 个字节。如果消息在发送方被分成两个数据包,那么您只能获得第一个数据包中的 10 个字节,而不能从下一个数据包中获得 6 个字节,直到您执行第二个 recv。因此,您需要继续接收,直到获得全部 16 个字节。 (我写了 a longer explanation in Python terms,但同样的想法也适用于 C++。)

一旦你这样做了,我们在得到 16 个字节后所要做的就是检查长度字段(你暗示的是两个结构中的前 4 个字节)。如果是 16,我们就完成了;如果没有,我们可以继续循环直到我们有 64 个字节。

理想情况下,我们希望读入一个 64 字节的缓冲区,而不是读取一个 16 字节的缓冲区和一个 48 字节的缓冲区,然后移动内存以连接它们。鉴于此,我们可以只使用 union 来允许我们将 recv 放入一个 64 字节的字符数组中,然后将前 16 个字节作为 reply_type_1 或整个内容作为reply_type_2.

所以,这是一个简单粗暴的例子(没有处理错误、EOF 等):

typeset union {
    reply_type_1 r1;
    reply_type_2 r2;
    char buf[64];
} msg_type;

int len = 0;
msg_type msg;
while (len < sizeof(reply_type_1)) {
    n = recv(sock, &msg.buf[len], sizeof(reply_type_1)-len, 0);
    len += n;
}
if msg.r1.length > sizeof(reply_type_1) {
    while (len < sizeof(reply_type_2)) {
        n = recv(sock, &msg.buf[len], sizeof(reply_type_2)-len, 0);
        len += n;
    }
    do_stuff_r2(msg.r2);
} else {
    do_stuff_r1(msg.r1);
}

存储到 msg.buf 然后访问 msg.r1 实际上与只有 char buf[64] 然后执行 do_stuff_r1(*reinterpret_cast<reply_type_1 *>(&buf)) 是一样的。因此,如果 do_stuff_r1 采用 reply_type_1,它会获取前 16 个字节的副本作为 reply_type_1 结构,如果它采用 reply_type_1 &,它会获取对前 16 个字节作为 reply_type_1 结构。