43

I've previously used BeginAccept() and BeginRead(), but with Visual Studio 2012 I want to make use of the new asynchronous (async, await) features in my socket server program.

How can I complete the AcceptAsync and ReceiveAsync functions?

using System.Net;
using System.Net.Sockets;

namespace OfficialServer.Core.Server
{
    public abstract class CoreServer
    {
        private const int ListenLength = 500;
        private const int ReceiveTimeOut = 30000;
        private const int SendTimeOut = 30000;
        private readonly Socket _socket;

        protected CoreServer(int port, string ip = "0.0.0.0")
        {
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _socket.Bind(new IPEndPoint(IPAddress.Parse(ip), port));
            _socket.Listen(ListenLength);
            _socket.ReceiveTimeout = ReceiveTimeOut;
            _socket.SendTimeout = SendTimeOut;
            _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
            _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
        }

        public void Start()
        {    
        }
    }
}
p.campbell
  • 98,673
  • 67
  • 256
  • 322
Daniel Eugen
  • 2,712
  • 8
  • 33
  • 56

2 Answers2

62

...because you're so determined, I put together a very simple example of how to write an echo server to get you on your way. Anything received gets echoed back to the client. The server will stay running for 60s. Try telnetting to it on localhost port 6666. Take time to understand exactly what's going on here.

void Main()
{
    CancellationTokenSource cts = new CancellationTokenSource();
    TcpListener listener = new TcpListener(IPAddress.Any, 6666);
    try
    {
        listener.Start();
        //just fire and forget. We break from the "forgotten" async loops
        //in AcceptClientsAsync using a CancellationToken from `cts`
        AcceptClientsAsync(listener, cts.Token);
        Thread.Sleep(60000); //block here to hold open the server
    }
    finally
    {
        cts.Cancel();
        listener.Stop();
    }
}

async Task AcceptClientsAsync(TcpListener listener, CancellationToken ct)
{
    var clientCounter = 0;
    while (!ct.IsCancellationRequested)
    {
        TcpClient client = await listener.AcceptTcpClientAsync()
                                            .ConfigureAwait(false);
        clientCounter++;
        //once again, just fire and forget, and use the CancellationToken
        //to signal to the "forgotten" async invocation.
        EchoAsync(client, clientCounter, ct);
    }

}
async Task EchoAsync(TcpClient client,
                     int clientIndex,
                     CancellationToken ct)
{
    Console.WriteLine("New client ({0}) connected", clientIndex);
    using (client)
    {
        var buf = new byte[4096];
        var stream = client.GetStream();
        while (!ct.IsCancellationRequested)
        {
            //under some circumstances, it's not possible to detect
            //a client disconnecting if there's no data being sent
            //so it's a good idea to give them a timeout to ensure that 
            //we clean them up.
            var timeoutTask = Task.Delay(TimeSpan.FromSeconds(15));
            var amountReadTask = stream.ReadAsync(buf, 0, buf.Length, ct);
            var completedTask = await Task.WhenAny(timeoutTask, amountReadTask)
                                          .ConfigureAwait(false);
            if (completedTask == timeoutTask)
            {
                var msg = Encoding.ASCII.GetBytes("Client timed out");
                await stream.WriteAsync(msg, 0, msg.Length);
                break;
            }
            //now we know that the amountTask is complete so
            //we can ask for its Result without blocking
            var amountRead = amountReadTask.Result;
            if (amountRead == 0) break; //end of stream.
            await stream.WriteAsync(buf, 0, amountRead, ct)
                        .ConfigureAwait(false);
        }
    }
    Console.WriteLine("Client ({0}) disconnected", clientIndex);
}
spender
  • 117,338
  • 33
  • 229
  • 351
  • Thank you very much this helped alot understanding how things going. – Daniel Eugen Sep 27 '12 at 23:33
  • No problem. You seemed a little confused about the best approach, so hopefully this will clear things up a little. – spender Sep 27 '12 at 23:35
  • But one last question for now :D, is there a difference between using the Old BeginReceive, and the new ReceiveAsync in the performance ? or it is kinda the same ?! – Daniel Eugen Sep 27 '12 at 23:35
  • 3
    Socket.ReceiveAsync is a strange one. It has nothing to do with async/await features in .net4.5. It was designed as an alternative socket API that wouldn't thrash memory as hard as BeginReceive/EndReceive, and only needs to be used in the most hardcore of server apps. To give you some sense of scale, we run a server BeginXXXX/EndXXXX methods that support 5000 connected clients with ease. I've never had any need to use ReceiveAsync, and I'd probably upgrade hardware before considering a rewrite using ReceiveAsync and SocketAsyncEventArgs because it would probably be cheaper in terms of my time. – spender Sep 27 '12 at 23:43
  • @DanialEugen In short, don't bother with ReceiveAsync/SocketAsyncEventArgs unless you're hitting performance issues with the other higher level APIs. The added complexity means that it's probably not worth the time investment. – spender Sep 27 '12 at 23:45
  • So i will stick with the BeginAccept, BeginReceive, ...etc methods that i used to use – Daniel Eugen Sep 27 '12 at 23:57
  • 4
    ...or migrate to async/await methods that have superceded them, as with my example. – spender Sep 28 '12 at 00:04
  • @Love. Yes. It should work **as is** with multiple clients (although without code to manage the clients, it's probably not that useful). – spender Jun 11 '14 at 01:15
  • Is it just me or does this only echo back to the client once it times out (15 seconds) ??? – user3822370 Aug 09 '15 at 02:23
  • don't you need a `if (amountReadTask.IsFaulted || amountReadTask.IsCanceled) break;` before accessing Result? if the task is faulted you will get an *invisible* exception. – Bart Calixto Apr 22 '16 at 06:40
  • @user3822370: there's a `Task.WhenAny`, so it will return the 1st completed task, ie either `stream.ReadAsync` or `Task.Delay` – user276648 Jan 18 '17 at 06:50
  • This sir is AWESOME – Paul Stanley Feb 21 '17 at 23:00
  • @spender I understand how the socket either receives or times out but i would like to understand how to send a message to client without first receiving a message from a client. – Paul Stanley May 28 '17 at 16:15
  • Inspiring piece of code! First I did not see the enclosing `using(){}` statement. Without disposing the client the remote web browser did not display what was sent. Is it flushing the stream when disposing that makes the difference? In my test using `close()` on the TcpClient had no effect. What is the mechanism to handle _Connection: Keep-Alive:_? – flodis Oct 29 '17 at 20:47
  • Starting to get it. To have a persistent connection you handle what comes on the TCP port. If you have data delimited or separable by some length descriptor you resond in the receive loop as long as your timeout or activity grants it. If client closes the port you know you will be speaking to yourself and to preserve the pride of the listener you drop the connection as well. (Like in phone calls not hanging up and dialing again for each new topic handled) – flodis Oct 30 '17 at 12:59
  • Adding a `.ContinueWith()` to the `EchoAsync(client, clientCounter, ct)` you can even decrement a _number of active connections_ counter. – flodis Oct 30 '17 at 17:45
  • I know this post is old, but it's one of the few posts about async networking stuff. I have some questions about this: 1, Why did you use ConfigureAwait(false) in this case? 2, If the code is moved to a own class let's call it "Server" would it be safe to use a bool field instead of a cancel token and set it to false in some kind of Cancel/Stop method of the class? 3, Will the async method ever return if the socket is closed before they are done? For example if the socket is closed before a client connects? – R1PFake Jan 08 '19 at 08:28
  • Is there a way to make this code run indefinitely even if there are no active clients? When i run this example executed from a console, it closes out immediately because there isn't any active clients. Using the thread block will hold open the console app for 60 seconds but if no clients are connected in the time frame, the console app shuts down. Should i just leverage a windows service instead? – user1732364 Nov 23 '21 at 20:52
16

You can use TaskFactory.FromAsync to wrap up Begin / End pairs into async-ready operations.

Stephen Toub has an awaitable Socket on his blog which wraps the more efficient *Async endpoints. I recommend combining this with TPL Dataflow to create a fully async-compatible Socket component.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 4
    I don't wanna to wrap Begin and End (if i understand you correctly). what i want to do is to Use .AcceptAsync instead of .BeginAccept, And .ReceiveAsync instead of .BeginReceive – Daniel Eugen Sep 27 '12 at 22:37
  • 4
    `AcceptAsync` and `ReceiveAsync` use a [special form of asynchronous API](http://msdn.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.aspx) that only exists for the `Socket` class. They have nothing to do with `async` and `await`. – Stephen Cleary Sep 27 '12 at 22:38
  • 2
    :D yea thats what i wanted but i can't achieve using SocketAsyncEventArgs, i don't know how. if you could provide me with an example of accepting connections, receiving data from them using those methods i will appreciate it alot – Daniel Eugen Sep 27 '12 at 22:44
  • 3
    Follow the link in my last comment. There's a long example on that MSDN page. The usual caveats with MSDN socket examples apply: it probably does not handle edge conditions correctly and will almost definitely need to change for any kind of realistic protocol. – Stephen Cleary Sep 27 '12 at 22:48
  • I never told that i am struggling, Also i used to create Asynchronous Sockets and it is not hard at all for me but i just wanted to Use SocketAsyncEventArgs and it is kinda new to me this is why i am just asking. – Daniel Eugen Sep 27 '12 at 23:17
  • Why do you want to use SocketAsyncEventArgs. It's a tough API, and there are easier ways to do things asynchronously. See my answer below. – spender Sep 27 '12 at 23:22
  • @StephenCleary Is this similar in case of UdpClient, i see they have some async methods in there as well ? So my question is also similar if one should use BeginReceive (with IAsyncResult as call back) or could there be a betterway to make it awaitable using async/Await mechanism ? – kuldeep May 29 '20 at 04:29
  • [`UdpClient` has async support built-in these days](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.udpclient?view=netcore-3.1), so you can use `ReceiveAsync`. – Stephen Cleary May 29 '20 at 10:50