开发者

TCP client message handling

开发者 https://www.devze.com 2023-02-08 06:45 出处:网络
I\'m r开发者_StackOverflow社区eceiving a stream of bytes and i need to split out messages, for example

I'm r开发者_StackOverflow社区eceiving a stream of bytes and i need to split out messages, for example

Message1\nMessage2\nMessage3\nMess

Each message will be appended by '\n' character but when a complete message cannot fit into buffer it gets a part of message and another part of it on next recv call which might require memory reallocations to append the message.

Am i doing this correctly or would there be any better way to handle the messages instead of reallocating buffer?


You could prepend the length of your message to the message, and read that first. Then allocate a buffer big enough to recieve the contents, and recv until it's read the required number of bytes.

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);
}


The length delimited option is likely your best bet. It allows you to be smart about allocating your buffers on the receiving side, and allows you to send messages that contain any character you want. It also saves you from having to carefully examine each character to see if you've gotten to the end of the message. Unfortunately, it's very easy to implement this poorly.

I will provide you with some nice code that will do this properly.

On the receiver side:

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);
}

On the sending side:

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;
        }
    }
}

The main issue the other code posted here has is that it doesn't handle things very well if you only receive part of the length, and not the whole thing. There is nothing to say that information won't get split up such that things will get split in the middle of the length information.

The other issue is that the length is being sent in a way that's not CPU and compiler agnostic. Different kinds of CPUs and different C++ compilers store their integers in different ways. If the compiler/CPU combination used by the sender differs from the compiler/CPU combination used by the receiver, this will cause problems.

So, explicitly taking the integer apart into characters in a platform neutral way and putting it back together again is the best way to go.


In case when incoming message is very long (~MBs or GBs) you can use buffer of a const length and a helper data structure where you would store pieces of MessageN (N = 1,2...). Each recv() fills buffer from the beginning. Then you need to process its content - search for \n. If you find it - you can extract new message (MessageN); if not - store content of a buffer in helper data structure (probably vector or a list) and execute recv() again. If you find \n and list is not empty - then it means bytes before \n are actually last piece of the MessageN - concatenate list elements and this piece together and then empty the list. If you find \n and list is empty then it means that all bytes from the buffer beginning till \n are MessageN. You then need to save in a list bytes after \n (till the next found \n or the end of the buffer) as the first part of Message(N+1).


If you do not need to get the whole message to start processing it, you can also use circular buffer (wiki, boost).

Send first, the size is good when can't know it at start, may I suggest you to not use unsigned int, since a deflect client could make you allocating to much memory (and have throw for length limit).

0

精彩评论

暂无评论...
验证码 换一张
取 消