Socket.BeginReceive merging TCP packets

2019-06-13 04:56发布

问题:

Is there a setting I can use to prevent merging multiple TCP packets into a single buffer inside Socket.BeginReceive callback?

Your knee-jerk reaction will be that there is nothing I can do to prevent TCP from splitting/merging the data, but this is not what I asking; I can clearly see individual packets being received in Wireshark, and my only concern is latency, i.e. to process the segment as soon as it arrives. This doesn't mean I don't know how to process split/merged segment, but it means I want to avoid the delay.

My code looks like this:

void WaitForData(ISocketInfo soc)
{
    if (socket != null && socket.Connected)
        socket.BeginReceive(buffer, 0, buffer.Length, 
            SocketFlags.None, OnPacketReceived, socket);
}

void OnPacketReceived(IAsyncResult asyn)
{
    try
    {
        var socket = (ISocketInfo)asyn.AsyncState;

        numberOfBytesReceived = socket.EndReceive(asyn);
        if (numberOfBytesReceived > 0)
        {
            _queue.Enqueue(buffer, 0, numberOfBytesReceived);
            OnNewDataReceived();
        }

        WaitForData(socketInfo);
    }
    catch (SocketException ex)
    {
        Log.Warn("Socket error while receiving packet", ex);
        Close();
    }
}

When I examine these packets in WireShark, I can see individual TCP packets being received every 50ms, each of them (say) 100 bytes. But sometimes in my app there is a delay of 100ms, and the OnPacketReceived methods gets 200 bytes.

Since WireShark confirms that this is not a OS/networking issue, what could be the problem here? The OnPacketReceived just fires on a background thread so it doesn't block the method, and the program doesn't really consume much CPU.

(Update)

It seems like I didn't convey my question clearly enough. My problem is not how to parse the data if it gets split across segments. My protocol is well defined (i.e. START_COOKIE, LENGTH, DATA, CRC), and I enqueue the data into a byte FIFO as soon as I receive it (the _queue.Enqueue call inside the snippet above), so I can easily parse it asynchronously.

The question is, if I am seeing packet no. 1 (100 bytes) at +50ms in Wireshark, and packet no. 2 (100 bytes) at +100ms in Wireshark, and my application is not blocking the OnPacketReceived method and consuming no CPU, how come does .NET, every once in a while, invoke OnPacketReceived at +100ms and merge two packets into one?

回答1:

You may receive less or more, there is no guarantee you will receive exactly what was sent.

From MSDN:

There is no guarantee that the data you send will appear on the network immediately. To increase network efficiency, the underlying system may delay transmission until a significant amount of outgoing data is collected. A successful completion of the BeginSend method means that the underlying system has had room to buffer your data for a network send.

Here is a good article to explain this and how you can fix your issue.



回答2:

TCP is a continuous stream of data by definition (it is its main feature, to make it seems as if there were no packets at all, unlike UDP). Thus, it's up to your software to define the message format. You could:

  • Mark the beginning of each message with the length of it (http does that).
  • Add separation mark to the end of the message (/n). To let the receiver know when to start and stop reading a single message, mark both the beginning and the ending of the message (use special symbols that cannot be typed).

To sum up, you have to care of splitting the stream into messages. Or use HTTP, WebSockets or other message format implementations on top of TCP.