8

I have a client/server infrastructure. At present they use a TcpClient and TcpListener to send a receive data between all the clients and server.

What I currently do is when data is received (on it's own thread), it is put in a queue for another thread to process in order to free the socket so it is ready and open to receive new data.

                // Enter the listening loop.
                while (true)
                {
                    Debug.WriteLine("Waiting for a connection... ");

                    // Perform a blocking call to accept requests.
                    using (client = server.AcceptTcpClient())
                    {
                        data = new List<byte>();

                        // Get a stream object for reading and writing
                        using (NetworkStream stream = client.GetStream())
                        {
                            // Loop to receive all the data sent by the client.
                            int length;

                            while ((length = stream.Read(bytes, 0, bytes.Length)) != 0)
                            {
                                var copy = new byte[length];
                                Array.Copy(bytes, 0, copy, 0, length);
                                data.AddRange(copy);
                            }
                        }
                    }

                    receivedQueue.Add(data);
                }

However I wanted to find out if there is a better way to do this. For example if there are 10 clients and they all want to send data to the server at the same time, one will get through while all the others will fail.Or if one client has a slow connection and hogs the socket all other communication will halt.

Is there not some way to be able to receive data from all clients at the same time and add the received data in the queue for processing when it has finished downloading?

Dylan
  • 1,919
  • 3
  • 27
  • 51
  • Shameless plug: http://jonathan.dickinsons.co.za/blog/2011/02/net-sockets-and-you/ - it touches on the async loop briefly; and contains a real implementation (you shouldn't be using `ThreadPool` like @Jalal suggested). – Jonathan Dickinson Aug 18 '11 at 08:55

4 Answers4

19

So here is an answer that will get you started - which is more beginner level than my blog post.

.Net has an async pattern that revolves around a Begin* and End* call. For instance - BeginReceive and EndReceive. They nearly always have their non-async counterpart (in this case Receive); and achieve the exact same goal.

The most important thing to remember is that the socket ones do more than just make the call async - they expose something called IOCP (IO Completion Ports, Linux/Mono has these two but I forget the name) which is extremely important to use on a server; the crux of what IOCP does is that your application doesn't consume a thread while it waits for data.

How to Use The Begin/End Pattern

Every Begin* method will have exactly 2 more arguments in comparisson to it's non-async counterpart. The first is an AsyncCallback, the second is an object. What these two mean is, "here is a method to call when you are done" and "here is some data I need inside that method." The method that gets called always has the same signature, inside this method you call the End* counterpart to get what would have been the result if you had done it synchronously. So for example:

private void BeginReceiveBuffer()
{
   _socket.BeginReceive(buffer, 0, buffer.Length, BufferEndReceive, buffer);
}

private void EndReceiveBuffer(IAsyncResult state)
{
   var buffer = (byte[])state.AsyncState; // This is the last parameter.
   var length = _socket.EndReceive(state); // This is the return value of the method call.
   DataReceived(buffer, 0, length); // Do something with the data.
}

What happens here is .Net starts waiting for data from the socket, as soon as it gets data it calls EndReceiveBuffer and passes through the 'custom data' (in this case buffer) to it via state.AsyncResult. When you call EndReceive it will give you back the length of the data that was received (or throw an exception if something failed).

Better Pattern for Sockets

This form will give you central error handling - it can be used anywhere where the async pattern wraps a stream-like 'thing' (e.g. TCP arrives in the order it was sent, so it could be seen as a Stream object).

private Socket _socket;
private ArraySegment<byte> _buffer;
public void StartReceive()
{
    ReceiveAsyncLoop(null);
}

// Note that this method is not guaranteed (in fact
// unlikely) to remain on a single thread across
// async invocations.
private void ReceiveAsyncLoop(IAsyncResult result)
{
    try
    {
        // This only gets called once - via StartReceive()
        if (result != null)
        {
            int numberOfBytesRead = _socket.EndReceive(result);
            if(numberOfBytesRead == 0)
            {
                OnDisconnected(null); // 'null' being the exception. The client disconnected normally in this case.
                return;
            }

            var newSegment = new ArraySegment<byte>(_buffer.Array, _buffer.Offset, numberOfBytesRead);
            // This method needs its own error handling. Don't let it throw exceptions unless you
            // want to disconnect the client.
            OnDataReceived(newSegment);
        }

        // Because of this method call, it's as though we are creating a 'while' loop.
        // However this is called an async loop, but you can see it the same way.
        _socket.BeginReceive(_buffer.Array, _buffer.Offset, _buffer.Count, SocketFlags.None, ReceiveAsyncLoop, null);
    }
    catch (Exception ex)
    {
        // Socket error handling here.
    }
}

Accepting Multiple Connections

What you generally do is write a class that contains your socket etc. (as well as your async loop) and create one for each client. So for instance:

public class InboundConnection
{
    private Socket _socket;
    private ArraySegment<byte> _buffer;

    public InboundConnection(Socket clientSocket)
    {
        _socket = clientSocket;
        _buffer = new ArraySegment<byte>(new byte[4096], 0, 4096);
        StartReceive(); // Start the read async loop.
    }

    private void StartReceive() ...
    private void ReceiveAsyncLoop() ...
    private void OnDataReceived() ...
}

Each client connection should be tracked by your server class (so that you can disconnect them cleanly when the server shuts down, as well as search/look them up).

Tieson T.
  • 20,774
  • 6
  • 77
  • 92
Jonathan Dickinson
  • 9,050
  • 1
  • 37
  • 60
  • 1
    I forgot to mention that you can also accept clients the same way, e.g. BeginAcceptTcpClient. You can set up the async loop identically as well. – Jonathan Dickinson Aug 18 '11 at 10:04
  • 1
    The blog post link is dead. But here it is on archive.org: https://web.archive.org/web/20121127003207/http://jonathan.dickinsons.co.za/blog/2011/02/net-sockets-and-you – Joseph Daigle May 28 '15 at 14:48
  • 2
    @JosephDaigle thanks for the reminder, I'll re-host it. – Jonathan Dickinson Jun 01 '15 at 07:49
  • Everyone just keep in mind to use numberOfBytesRead when converting the newSegment (inside `OnDataReceived`) to whatever type of payload you're dealing with. For example I'm dealing with string messages being sent over TCP, and was using `Encoding.ASCII.GetString(dataSegment.Array, 0, dataSegment.Count);` where I should have been using `Encoding.ASCII.GetString(dataSegment.Array, 0, numberOfBytesRead);`. In hindsight it's a really silly mistake but it took me a bit of time to figure it out. – tkit Aug 18 '17 at 15:04
1

You should use asynchronous method of reading the data, an example is:

// Enter the listening loop.
while (true)
{
    Debug.WriteLine("Waiting for a connection... ");

    client = server.AcceptTcpClient();

    ThreadPool.QueueUserWorkItem(new WaitCallback(HandleTcp), client);
}

private void HandleTcp(object tcpClientObject)
{
    TcpClient client = (TcpClient)tcpClientObject;
    // Perform a blocking call to accept requests.

    data = new List<byte>();

    // Get a stream object for reading and writing
    using (NetworkStream stream = client.GetStream())
    {
        // Loop to receive all the data sent by the client.
        int length;

        while ((length = stream.Read(bytes, 0, bytes.Length)) != 0)
        {
            var copy = new byte[length];
            Array.Copy(bytes, 0, copy, 0, length);
            data.AddRange(copy);
        }
    }

    receivedQueue.Add(data);
} 

Also you should consider using AutoResetEvent or ManualResetEvent to be notified when new data is added to the collection so the thread that handle the data will know when data is received, and if you are using 4.0 you better switch off to using BlockingCollection instead of Queue.

Jalal Said
  • 15,906
  • 7
  • 45
  • 68
  • Already using a BlockingCollection. And I'm using synchronous methods due to the fact that I have a dedicated thread to receive the files. In your example above, if two clients connect at the same time, will server.AcceptTcpClient accept both or will one be queued until the TcpListener is next available (after the HandleTcp)? Also as a note, if you're using .Net 4 you should use the Task library instead of ThreadPool. – Dylan Aug 18 '11 at 08:37
  • It will accept both, that is why we are using `Thread` here, because it will read the first one data at another thread so it will not block the current thread, so `ThreadPoo.Queu..` will return the handle to the caller thread instantly and at the same time create a new thread to handle the client. – Jalal Said Aug 18 '11 at 08:40
  • -1 for using ThreadPool. In .Net 3.5 you should be using `Begin/End-Receive`. On .Net 4.0 you can use the new eventing model, or TPL. Your code doesn't use IOCP at all; which makes it a poor suggestion. – Jonathan Dickinson Aug 18 '11 at 08:57
  • @Jonathan: You are so wrong! There is no **general** rule to always follow, Also being use `4.0` doesn't means we should use `TPL` instead of `ThreadPool`, using `Task` "which is uses `ThreadPool` in its internal implementation anyway" is sometimes overkill, because the `Task` is `IDisposable`, that means we should care to `Dispose` it when it finishes its execution. – Jalal Said Aug 18 '11 at 09:07
  • You should never use `ThreadPool` full stop. You are going to starve your server of worker threads and the whole system will come to a grinding halt (if the amount of clients connected exceeds 30*CPU count). I said you *could* use TPL in 4.0, or the eventing model, or the Begin/End pattern. Each has its merits, but ThreadPool wrapping a sync call is just plain blatantly bad advice for sockets. – Jonathan Dickinson Aug 18 '11 at 09:23
  • @Jonathan: It depends on the time need to execute the data in the `ThreadPool` at the first place, for instance here he is only reading the data, put is in the queue and `Dispose` the stream.. – Jalal Said Aug 18 '11 at 09:27
  • So let's say you use a SQL query in your code there (FYI `SqlConnection` also uses the ThreadPool). A set of clients connect (say you get DDOSed) and you start doing your queries - now, you have consumed all your `ThreadPool` threads because of your non-async calls; but your `SqlConnection`s need them. Welcome to the wonderful world of a `ThreadPool` deadlock. You either do sockets right or not at all; there are reasons for IOCP otherwise it would never have been implemented by Microsoft, or exposed in the .Net Framework. – Jonathan Dickinson Aug 18 '11 at 09:36
  • @Jonathan: Yah.... Right, I assumed you didn't read my previous comment! the code he posted will be executed in nano seconds, no one will block any thing, and you didn't know that the `ThreadPool` in 3.5 has above `255` thread per cpu and in `4.0` more than `1000` per cpu, why to add the overhead in your answer in this case. Another thing to mentioned that I really hate it when some one downvote others answer(s) then push his answer, this is really a bad technique! – Jalal Said Aug 18 '11 at 09:42
  • @EJP Your comment and downvote here is not correct! you said at your comment: `No. He should be using a separate thread per accepted connection` I already use a seperate thread for each accepted connection `ThreadPool.QueueUserWorkItem` will start a new thread. – Jalal Said Aug 18 '11 at 10:41
1

You should use asynchronous socket programming to achieve this. Take a look at the example provided by MSDN.

m0sa
  • 10,712
  • 4
  • 44
  • 91
0

What I do usually is using a thread pool with several threads. Upon each new connection I'm running the connection handling (in your case - everything you do in the using clause) in one of the threads from the pool.

By that you achieve both performance since you're allowing several simultaneously accepted connection and you also limiting the number of resources (threads, etc') you allocate for handling incoming connections.

You have a nice example here

Good Luck

Lior Ohana
  • 3,467
  • 4
  • 34
  • 49