【问题标题】:Using .Net 4.5 Async Feature for Socket Programming使用 .Net 4.5 异步功能进行套接字编程
【发布时间】:2012-09-27 22:16:07
【问题描述】:

我以前使用过BeginAccept()BeginRead(),但在 Visual Studio 2012 中,我想在我的套接字服务器程序中使用新的异步(asyncawait)功能。

如何完成AcceptAsyncReceiveAsync功能?

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

【问题讨论】:

  • SendTimeout 和 ReceiveTimeout 不是只适用于同步情况吗?

标签: c# sockets .net-4.5


【解决方案1】:

...因为您如此坚定,所以我整理了一个非常简单的示例,说明如何编写回显服务器以助您顺利上路。收到的任何内容都会回显给客户端。服务器将保持运行 60 秒。尝试在 localhost 端口 6666 上远程登录它。花点时间了解这里到底发生了什么。

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

【讨论】:

  • 非常感谢,这对了解事情进展有很大帮助。
  • 没问题。您似乎对最好的方法有点困惑,所以希望这能澄清一点。
  • 但是现在最后一个问题:D,在性能上使用旧的 BeginReceive 和新的 ReceiveAsync 有区别吗?还是有点一样?!
  • Socket.ReceiveAsync 是一个奇怪的。它与 .net4.5 中的 async/await 功能无关。它被设计为一种替代的套接字 API,它不会像 BeginReceive/EndReceive 那样严重地破坏内存,并且只需要在最核心的服务器应用程序中使用。为了给您一些规模感,我们运行了一个服务器 BeginXXXX/EndXXXX 方法,可以轻松支持 5000 个连接的客户端。我从来不需要使用 ReceiveAsync,在考虑使用 ReceiveAsync 和 SocketAsyncEventArgs 重写之前,我可能会升级硬件,因为就我的时间而言,它可能会更便宜。
  • ...或迁移到已取代它们的 async/await 方法,就像我的示例一样。
【解决方案2】:

您可以使用TaskFactory.FromAsyncBegin / End 对包装成async-ready 操作。

Stephen Toub 在他的博客上有一个awaitable Socket,它包含了更高效的*Async 端点。我建议将其与TPL Dataflow 结合使用,以创建一个完全兼容asyncSocket 组件。

【讨论】:

  • 我不想包装开始和结束(如果我理解正确的话)。我想要做的是使用 .AcceptAsync 而不是 .BeginAccept,和 .ReceiveAsync 而不是 .BeginReceive
  • AcceptAsyncReceiveAsync 使用仅适用于 Socket 类的 special form of asynchronous API。它们与asyncawait 无关。
  • :是的,这就是我想要的,但我无法使用 SocketAsyncEventArgs 实现,我不知道怎么做。如果您能给我提供一个接受连接的示例,使用这些方法从他们那里接收数据,我将不胜感激
  • 点击我上一条评论中的链接。该 MSDN 页面上有一个很长的示例。 MSDN 套接字示例的常见警告适用:它可能无法正确处理边缘条件,并且几乎肯定需要针对任何类型的实际协议进行更改。
  • 我从来没有说过我在挣扎,而且我曾经创建过异步套接字,这对我来说一点也不难,但我只是想使用 SocketAsyncEventArgs,这对我来说有点新,这就是为什么我我只是问。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多