Wednesday, January 23, 2008

How to Transfer Fixed Sized Data With Async Sockets

This post was inspired by the discussion in MDSN forums.

Discussion on that forum showed that there is misunderstanding of the principles of data transfer across the network, especially in asynchronous way.

I wrote simple client/server application that demonstrates usage of asynchronous sockets to transfer fixed sized data from client to server.

Usually data exchange between server and client is done in some special way (or special order). Such an order is called communication protocol.
More information on network and communication protocols can be found here and here

The sample here also uses communication protocol. It is very simple, client prefixes the data with 4 bytes that hold data size. Format of the data that will go to the wire can be shown like this [4 bytes - data size][data - data size].

Here comes implementation with short comments. Asynchronous communication via sockets involves methods like BeginReceive/EndReceive - for data receiving and BeginSend/EndSend for sending. The sample demonstrates only how async sockets work and the fact that data is received in the stream like way.
More information about network programming can be found here


Server

At first we define the state that will be passed between async calls. State is defined as following:


///
/// Server state holds current state of the client socket
///

class ServerState
{
public byte[] Buffer = new byte[512]; //buffer for network i/o
public int DataSize = 0; //data size to be received by the server
public bool DataSizeReceived = false; //whether prefix was received
public MemoryStream Data = new MemoryStream(); //place where data is stored
public Socket Client; //client socket
}

Server is listening for the clients using Socket.Accept method.
Here is the server listening loop:

//server listening loop
while (true)
{
Socket client = serverSocket.Accept();
ServerState state = new ServerState();

state.Client = client;
//issue first receive
client.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None,
new AsyncCallback(ServerReadCallback), state);
}

When data comes to the client socket ServerReadCallback is called. There server can read received data and process it.

Here is the implementation of that method:

private void ServerReadCallback(IAsyncResult
{
ServerState state = (ServerState)ar.AsyncState;
Socket client = state.Client;
SocketError socketError;

int dataRead = client.EndReceive(ar, out socketError);
int dataOffset = 0; //to simplify logic

if (socketError != SocketError.Success)
{
client.Close();
return;
}

if (dataRead <= 0)
{ //connection reset
client.Close();
return;
}

if (!state.DataSizeReceived)
{
if (dataRead >= 4)
{ //we received data size prefix
state.DataSize = BitConverter.ToInt32(state.Buffer, 0);
state.DataSizeReceived = true;
dataRead -= 4;
dataOffset += 4;
}
}

if ((state.Data.Length + dataRead) == state.DataSize)
{ //we have all the data
state.Data.Write(state.Buffer, dataOffset, dataRead);

Console.WriteLine("Data received. Size: {0}", state.DataSize);

client.Close();
return;
}
else
{ //there is still data pending, store what we've
//received and issue another BeginReceive
state.Data.Write(state.Buffer, dataOffset, dataRead);

client.BeginReceive(state.Buffer, 0, state.Buffer.Length,
SocketFlags.None, new AsyncCallback(ServerReadCallback), state);
}
}

As you can see server implementation is very simple. It receives data size prefix at first and if it is complete (first 4 bytes are received) then proceeds with data receive.

It is very important to always check the number of received bytes. This number can vary.


Client

Client implementation is pretty straightforward. It also has a state that is passed along async operations.

public class ClientState
{
public Socket Client; //client socket
public byte[] DataToSend; //data to be trasferred
public int DataSent = 0; //data already sent
}

The data being sent is prefixed with its size.

ClientState state = new ClientState();
state.Client = socket;

//add prefix to data
state.DataToSend = new byte[data.Length + 4];
byte[] prefix = BitConverter.GetBytes(data.Length);
//copy data size prefix
Buffer.BlockCopy(prefix, 0, state.DataToSend, 0, prefix.Length);
//copy the data
Buffer.BlockCopy(data, 0, state.DataToSend, prefix.Length, data.Length);

socket.BeginSend(state.DataToSend, 0, state.DataToSend.Length,
SocketFlags.None, new AsyncCallback(ClientSendCallback), state);

And finally the implementation of ClientSendCallback

private void ClientSendCallback(IAsyncResult ar)
{
ClientState state = (ClientState)ar.AsyncState;
SocketError socketError;
int sentData = state.Client.EndSend(ar, out socketError);

if ( socketError != SocketError.Success )
{
state.Client.Close();
return;
}

state.DataSent += sentData;

if (state.DataSent != state.DataToSend.Length)
{ //not all data was sent
state.Client.BeginSend(state.DataToSend, state.DataSent,
state.DataToSend.Length - state.DataSent, SocketFlags.None,
new AsyncCallback(ClientSendCallback), state);
}
else
{ //all data was sent
Console.WriteLine("All data was sent. Size: {0}",
state.DataToSend.Length);
state.Client.Close();
}
}

As you can see implementation details are very simple.
There are several important things here:
  • Data is received in pieces, you can't predict how much data you will receive during one method call
  • When using async sockets always pass the same state object in the context of the same client

Update
The code sample above illustrates the idea of tranferring size-prefixed data between client and server in the async way. Please, note that only one message per connection is passed from client to server. To add multiple message handling - server code has to be modified according to your custom the protocol.

7 comments:

  1. I never checked for <=0 bytes received from socket.EndReceive and didn't realize it was due to a connection reset. I was facing issues on deployed code as well. Thanks a lot for your example.

    ReplyDelete
  2. Thank you very much for sharing this. I've been working on the same problem for 6 hours. I rewrote and refigured my offsets a 1000 times until I was completely confused. Very nice!

    ReplyDelete
  3. It is extremely interesting for me to read this blog. Thanx for it. I like such topics and anything that is connected to them. I definitely want to read more soon.

    ReplyDelete
  4. hi sir nice information to me as i am new to socket programing and need your guidance in my problem .

    ReplyDelete
  5. Hi,
    I have a server such as yours. Receives from one customer data every 100 to 400 ms. Maximum is 150 KB in one package. If you fire up the client and server on the local network everything works great. When I send from a client upload reaches 1.5 Mbit / s is about, but when the server was running on Mono on Linux client outside the network upload from client to server is a maximum of 300kbit / s regardless of how much needs to send. The process of sending just longer. Does anyone know what may be the reason?
    Thanks in advance for your help.

    ReplyDelete
  6. Last time I was looking at the Mono implementation of async sockets it used ThreadPool to handle I/O requests and make them look async.

    This can explain the behavior you observe - instead of using native OS means of async I/O - Mono just emulates it.

    ReplyDelete
  7. Thank you! This cleared up a bunch of things from me.

    ReplyDelete