web-dev-qa-db-fra.com

Utilisation de la fonctionnalité Async .Net 4.5 pour la programmation de socket

J'ai déjà utilisé BeginAccept() et BeginRead(), mais avec Visual Studio 2012, je veux utiliser le nouveau mode asynchrone (async, await) fonctionnalités dans mon programme de serveur de socket.

Comment puis-je compléter les fonctions AcceptAsync et ReceiveAsync?

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()
        {    
        }
    }
}
38
Daniel Eugen

... parce que vous êtes tellement déterminé, j'ai mis en place un exemple très simple de la façon d'écrire un serveur d'écho pour vous aider sur votre chemin. Tout ce qui est reçu est renvoyé au client. Le serveur continuera de fonctionner pendant 60 secondes. Essayez de vous y connecter sur le port local 6666. Prenez le temps de comprendre exactement ce qui se passe ici.

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);
}
56
spender

Vous pouvez utiliser TaskFactory.FromAsync pour boucler Begin/End paires en async- opérations prêtes.

Stephen Toub a un attendable Socket sur son blog qui enveloppe le plus efficace *Async points de terminaison. Je recommande de combiner cela avec TPL Dataflow pour créer un composant entièrement async- compatible Socket.

13
Stephen Cleary