TCP客户端消息处理

时间:2011-02-04 09:28:02

标签: c++ winsock

我收到一个字节流,我需要拆分消息,例如

Message1\nMessage2\nMessage3\nMess

每个消息都将附加'\ n'字符,但是当一个完整的消息无法容纳到缓冲区时,它会在下一个recv调用中获取消息的一部分和另一部分消息,这可能需要重新分配内存以附加消息消息。

我这样做是否正确或是否有更好的方法来处理消息而不是重新分配缓冲区?

4 个答案:

答案 0 :(得分:4)

您可以将邮件的长度添加到邮件中,然后先阅读。然后分配一个足够大的缓冲区来接收内容,然后重新读取,直到它读取所需的字节数。

e.g。

int len = 0;
if(recv(socket, reinterpret_cast<char*>(&len), sizeof(int), 0) == sizeof(int))
{
    std::vector<char> buffer;
    buffer.resize(len);

    int bytesRead = 0;
    while(bytesRead < len)
    {
        //read as much as we can. note: byteInc may not == len-bytesRead.
        int byteInc = recv(socket, &buffer[bytesRead], len-bytesRead, 0);
        if(byteInc != SOCKET_ERROR)
        {
            bytesRead += byteInc;
        }
        else
        {
            //should probably handle this error properly
            break;
        }
    }

    //buffer now contains the complete message.
    some_processing_function(buffer);
}

答案 1 :(得分:2)

长度分隔选项可能是您最好的选择。它允许您聪明地在接收端分配缓冲区,并允许您发送包含所需字符的消息。它还使您不必仔细检查每个字符,看看您是否已经到达消息的末尾。不幸的是,很难实现这一点。

我将为您提供一些可以正确执行此操作的优秀代码。

在接收方:

unsigned char lenbuf[4];

// This whole thing with the while loop occurs twice here, should probably
// have its own function.
{
    bytesRead = 0;
    while (bytesRead < 4) {
        //read as much as we can. note: byteInc may not == len-bytesRead.
        int byteInc = recv(socket, &lenbuf[bytesRead], 4-bytesRead, 0);
        if(byteInc != SOCKET_ERROR)
        {
            bytesRead += byteInc;
        }
        else
        {
            //should probably handle this error properly
            break;
        }
    }
} // end scope for bytesRead

unsigned int len = ((lenbuf[0] & 0xffu) << 24) | ((lenbuf[1] & 0xffu) << 16)
                   | ((lenbuf[2] & 0xffu) << 8) | (lenbuf[3] & 0xffu);

::std::vector<char> buffer;
buffer.resize(len);

{
    unsigned int bytesRead = 0;
    while(bytesRead < len)
    {
        //read as much as we can. note: byteInc may not == len-bytesRead.
        int byteInc = recv(socket, &buffer[bytesRead], len-bytesRead, 0);
        if(byteInc != SOCKET_ERROR)
        {
            bytesRead += byteInc;
        }
        else
        {
            //should probably handle this error properly
            break;
        }
    }

    //buffer now contains the complete message.
    some_processing_function(buffer);
}

在发送方:

const unsigned char lenbuf[4] = {
        ((bytesToSend >> 24) & 0xffu), ((bytesToSend >> 16) & 0xffu),
        ((bytesToSend >> 8) & 0xffu), (bytesToSend & 0xffu)
    };

// This basic block is repeated twice and should be in a function
{
    unsigned int bytesSent = 0;
    while (bytesSend < 4) {
        const int sentNow = send(socket, &lenbuf[bytesSent], 4-bytesSent, 0);
        if (sentNow != SOCKET_ERROR) {
            bytesSent += sentNow;
        } else {
            // Should handle this error somehow.
            break;
        }
    }
}

{
    unsigned int bytesSent = 0;
    while (bytesSent < bytesToSend) {
        const unsigned int toSend = bytesToSend - bytesSent;
        const int sentNow = send(socket, &byteBuf[bytesSent], toSend, 0);
        if (sentNow != SOCKET_ERROR) {
            bytesSent += sentNow;
        } else {
            // Should handle this error somehow.
            break;
        }
    }
}

这里发布的其他代码的主要问题是,如果你只收到部分长度,而不是整个东西,它就不能很好地处理。没有什么可以说信息不会分裂,以至于事情会在长度信息的中间分裂。

另一个问题是,长度是以不与CPU和编译器无关的方式发送的。不同种类的CPU和不同的C ++编译器以不同的方式存储它们的整数。如果发送方使用的编译器/ CPU组合与接收方使用的编译器/ CPU组合不同,则会导致问题。

因此,以平台中立的方式明确地将整数分成字符并将其重新组合在一起是最好的方法。

答案 2 :(得分:1)

如果传入消息很长(~MBs或GB),您可以使用const长度的缓冲区和辅助数据结构,您可以在其中存储MessageN(N = 1,2 ...)。每个recv()从一开始就填充缓冲区。然后,您需要处理其内容 - 搜索\n。如果你找到它 - 你可以提取新消息(MessageN);如果不是 - 将缓冲区的内容存储在辅助数据结构中(可能是向量或列表)并再次执行recv()。如果你发现\n并且list不为空 - 那么它意味着\n之前的字节实际上是MessageN的最后一部分 - 连接列表元素和这个部分,然后清空列表。如果您发现\n且列表为空,则表示从缓冲区开始直到\n的所有字节都是MessageN。然后,您需要在\n之后(直到下一个找到\n或缓冲区的末尾)将列表字节保存为消息(N + 1)的第一部分。

答案 3 :(得分:0)

如果您不需要让整条消息开始处理它,您也可以使用循环缓冲区(wikiboost)。

首先发送,当开始时无法知道它的大小是好的,我建议你不要使用unsigned int,因为一个deflect客户端可以让你分配到很多内存(并且有长度限制)。 / p>