Socket2104
Socket2104

Reputation: 122

Receive correct amount of data TCP

I'm bringing up a TCP async server using C# and I'm struggling on the right way to receive the correct amount of data from the server. My question is this one: due to the nature of TCP being a STREAM protocol, there is no delimiter at the end of each message received so the only thing I can do is add at the beginning of the message the upcoming message size and react consequently; the thing is, how can I recv and be sure that I'm not reading the "next" message in the stream?

My pseudo code looks like this:

// client accepted, begin receiving
client.BeginReceive(state.buffer, 0, StateObject.bufferSize, 0, new AsyncCallback(_cbck_Read), state);

private void _cbck_Read(IAsyncResult ar)
{

    StateObject state  = (StateObject)ar.AsyncState;
    Socket client      = state.clientSocket; 

    int bytesRead = client.EndReceive(ar);

        // if data have been received
        if (bytesRead > 0)
        {
            state.bytesReceived += bytesRead;

            // no message header received so far go on reading
            if (state.bytesReceived < Marshal.SizeOf(header))
            {
                client.BeginReceive(state.buffer, 0, StateObject.bufferSize, 0, new AsyncCallback(_cbck_Read), state);
            }

 // ... go ahead reading

If the first recv does not get the whole message, could the next recv go far beyond the boundaries of the first and possibly add some unwanted bytes to the message I'm actually wanting to read?

Upvotes: 0

Views: 866

Answers (3)

Pekka
Pekka

Reputation: 3644

As you observe, TCP provides no native framing. Worse, the a sync I/O events are reported for each TCP segment received from the network stack, and may not even match the send calls.

While building up the contents of the received stream, you will need to use one of:

  • fixed length messages, possibly with some leading type discrimination,
  • explicit length prefix indication, or
  • an unambiguous message delimeter.

The choice will generally depend on non-technical factors.

Upvotes: 1

L.B
L.B

Reputation: 116108

Here is how it can be done using "async/await" with some helper extension methods.

Socket s = new Socket(SocketType.Stream, ProtocolType.Tcp);
await s.ConnectTaskAsync("stackoverflow.com", 80);

await s.SendTaskAsync(Encoding.UTF8.GetBytes("GET /\r\n\r\n"));


var buf1 = await s.ReceiveExactTaskAsync(100); //read exactly 100 bytes
Console.Write(Encoding.UTF8.GetString(buf1));

var buf2 = await s.ReceiveExactTaskAsync(100); //read exactly 100 bytes
Console.Write(Encoding.UTF8.GetString(buf2));

public static class SocketExtensions
{
    public static Task<int> ReceiveTaskAsync(this Socket socket, byte[] buffer, int offset, int count)
    {
        return Task.Factory.FromAsync<int>(
                         socket.BeginReceive(buffer, offset, count, SocketFlags.None, null, socket),
                         socket.EndReceive);
    }

    public static async Task<byte[]> ReceiveExactTaskAsync(this Socket socket, int len)
    {
        byte[] buf = new byte[len];
        int totalRead = 0;
        do{
            int read = await ReceiveTaskAsync(socket, buf, totalRead, buf.Length - totalRead);
            if (read <= 0) throw new SocketException();
            totalRead += read;
        }while (totalRead != buf.Length);
        return buf;
    }

    public static Task ConnectTaskAsync(this Socket socket, string host, int port)
    {
        return Task.Factory.FromAsync(
                         socket.BeginConnect(host, port, null, null),
                         socket.EndConnect);
    }

    public static Task SendTaskAsync(this Socket socket, byte[] buffer)
    {
        return Task.Factory.FromAsync<int>(
                         socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, null, socket),
                         socket.EndSend);
    }
}

Upvotes: 2

pm100
pm100

Reputation: 50110

if you know the pending length (which you say you do based on protocol header of some sort) then only read the known pending length. IN your case Marshal.Sizeof(header) - state.bytesReceived

Upvotes: 0

Related Questions