0

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?

tshepang
  • 12,111
  • 21
  • 91
  • 136
Socket2104
  • 122
  • 2
  • 9
  • Maybe you can use the example [here](http://stackoverflow.com/questions/21510204/c-sharp-tcpclient-send-serialized-objects-using-separators) – L.B Feb 03 '14 at 23:14
  • That would be a nice idea but the client does not have any library or method to deserialize JSON (and writing a new one or adapting an existing one would be an inappropriate effort, right now) – Socket2104 Feb 03 '14 at 23:26
  • 1
    You know how much to read from the length you send first. So just be sure to never read *more* than you expect. You first read 4 bytes so you got the length. Then you read length - bytesreceived on your subsequent calls. – Hans Passant Feb 04 '14 at 15:25

3 Answers3

2

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);
    }
}
L.B
  • 114,136
  • 19
  • 178
  • 224
  • Hi. I tried using your code and and it does compile but nothing happends. can you please see it ? http://stackoverflow.com/questions/22662621/server-communication-via-async-await-via-tap – Royi Namir Mar 26 '14 at 13:43
  • @RoyiNamir It tried again, it does write to console as expected. Maybe you have no console? try with `Debug.WriteLine`... – L.B Mar 26 '14 at 14:32
  • Im in linqpad - look here - http://i.stack.imgur.com/NVwrb.jpg - for the working - non async non tap version. However I did not use your code like you did , i split it to sender receiver. (please see my link above) - maybe that's the problem ? can you kindly split the sender receiver to different sections ? ( so 1 cmd will run reciever and 1 cmd will run sender)... I actually tried but I must mess something there – Royi Namir Mar 26 '14 at 14:35
1

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.

Pekka
  • 3,529
  • 27
  • 45
0

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

pm100
  • 48,078
  • 23
  • 82
  • 145
  • Of course, this also requires to going on receiving until the whole message has come. Is that true? Or does it receives the whole message? And what if the first recv reads more than it should? – Socket2104 Feb 03 '14 at 23:21
  • yes you have to keep receiving till you have exactly one message – pm100 Feb 03 '14 at 23:31