了解C#控制台TCP服务器的异步行为

时间:2018-02-28 17:54:07

标签: c# tcp server async-await .net-core

我想在.NET Core 2.0中使用异步创建一个简单的TCP服务器(因为根据我的理解,它比产生线程更合理)采用async/await方法(因为我认为它更符合 - 日期比使用IAsyncResult*Begin/*End方法的日期。

我写过这个小服务器接受来自客户端的新连接,然后开始发送100条消息(它们之间有1秒的延迟)。

主要问题是:

如果我没有产生新线程,那么服务器如何继续向多个客户端发送延迟消息,实际上它是“等待连接”是否涉及任何隐藏的低级信号/事件,或者是否真的只有新线程?

第二个问题是:

如果我没有使用这个全新的async Main语法糖并且我没有“等待”async发送消息的任务 - 我是否正确使用异步?

class Program
{
    public static void Main(string[] args)
    {
        StartServer();
    }

    public static void StartServer()
    {
        IPAddress localhost = IPAddress.Parse("127.0.0.1");
        TcpListener listener = new TcpListener(localhost, 5567);

        Console.WriteLine($"Starting listening on {listener.Server.LocalEndPoint}");
        listener.Start();
        while (true)
        {
            Console.WriteLine("Waiting for connection...");
            var client = listener.AcceptTcpClient(); // synchronous
            Console.WriteLine($"Connected with {client.Client.RemoteEndPoint}!");

            Console.WriteLine("Starting sending messages...");
            SendHundredMessages(client); // not awaited -- StartServer is not async
        }
    }


    public static async Task SendHundredMessages(TcpClient client)
    {
        var stream = client.GetStream();

        for (int i=0; i<100; i++)
        {
            var msg = Encoding.UTF8.GetBytes($"Message no #{i}\n");
            await stream.WriteAsync(msg, 0, msg.Length);    // returning back to caller?

            await Task.Delay(1000);     // and what about here?
        }
        client.Close();
    }
}

原始代码和下面的版本有什么区别? async Main有什么区别?

class Program
{
    public static async Task Main(string[] args)
    {
        await StartServer();
    }

    public static async Task StartServer()
    {
        IPAddress localhost = IPAddress.Parse("127.0.0.1");
        TcpListener listener = new TcpListener(localhost, 5567);

        Console.WriteLine($"Starting listening on {listener.Server.LocalEndPoint}");
        listener.Start();
        while (true)
        {
            Console.WriteLine("Waiting for connection...");
            var client = await listener.AcceptTcpClientAsync(); // does it make any difference when done asynchronously?
            Console.WriteLine($"Connected with {client.Client.RemoteEndPoint}!");

            Console.WriteLine("Starting sending messages...");
            SendHundredMessages(client); // cannot await here, because it blocks next connections
        }
    }


    public static async Task SendHundredMessages(TcpClient client)
    {
        var stream = client.GetStream();

        for (int i=0; i<100; i++)
        {
            var msg = Encoding.UTF8.GetBytes($"Message no #{i}\n");
            var result = stream.WriteAsync(msg, 0, msg.Length);

            await Task.Delay(1000);
            await result;
        }
        client.Close();
    }
}

1 个答案:

答案 0 :(得分:0)

主要问题的答案:

在后台,通常,对象使用一些api(winapi for windows)。这些api可以不同地实现异步。例如,事件(winapi事件)或回调。所以答案是肯定的 - 有隐藏的信号或线程。例如,您可以看到 Ping 类。 Ping.InternalSend使用ThreadPool.RegisterWaitForSingleObject执行异步任务。

关于async Main的答案:

在您的第一个代码中,由于StartServer不是异步,Main方法在您接受&#34;接受&#34;之前无法获得控制权。循环结束。

在第二个代码中,Main方法将返回控制权,然后调用listener.AcceptTcpClientAsync。但是因为您使用await StartServer();,Main方法将等到StartServer结束。

一些代码可以解释:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TestConsole
{
    class Program
    {
        static void Main(string[] args)
        {   
            // not await for exploration
            var task = StartServer();

            Console.WriteLine("Main StartServer finished");
            task.Wait();
            Console.WriteLine("task.Wait() finished");
            Console.ReadKey();
        }        

        static async Task StartServer()
        {
            Console.WriteLine("StartServer enter");

            for(int i = 0; i < 3; i++) {
                await Task.Delay(1000); // listener.AcceptTcpClientAsync simulation
                SendHundredMessages();
            }

            Console.WriteLine("StartServer exit");
        }

        static async Task SendHundredMessages()
        {
            Console.WriteLine("SendHundredMessages enter");

            await Task.Run(() => {
                Thread.Sleep(2000);
            });

            Console.WriteLine("SendHundredMessages exit");
        }        
    }
}

此代码生成此输出:

  

StartServer输入
  主StartServer完成了   SendHundredMessages输入
  SendHundredMessages输入
  SendHundredMessages退出
  SendHundredMessages输入
  StartServer出口
  task.Wait()完成了   SendHundredMessages退出
  SendHundredMessages退出

如您所见,Main方法在第一个Task.Delay之后立即继续执行。

警告:

你不等待SendHundredMessages结束,这非常糟糕。在示例输出中,您可以看到&#34; SendHundredMessages结束&#34;在&#34; task.Wait()完成后&#34;。在示例应用中当然不是危险,但在实际项目中你可能会遇到大问题。