【问题标题】:C# multiuser TCP serverC# 多用户 TCP 服务器
【发布时间】:2022-01-01 19:03:08
【问题描述】:

尝试创建异步 tcp 服务器。 以前我可以使用 Begin/End 模式很好地完成此操作,并像这样处理多个客户端:

class Server
{
    IPEndPoint ipep;
    TcpListener listener;
    bool Running;
    List<Client> clients;

    public Server(string host)
    {
        IPAddress ip = IPAddress.Parse(host);
        ipep = new(ip, 21);
        Running = false;
        clients = new();
    }

    public void Start()
    {
        listener = new(ipep);
        listener.Start();
        Running = true;
        while (Running)
            listener.BeginAcceptTcpClient(Accept, null);
    }

    public void Accept(IAsyncResult ar)
    {
        Client client = new(listener.EndAcceptTcpClient(ar));
        clients.Add(client);
        client.WaitCommands();
    }
}

class Client
{
    TcpClient client;
    NetworkStream stream;

    public Client(TcpClient client)
    {
        this.client = client;
        stream = client.GetStream();
    }

    public void WaitCommands()
    {
        stream.BeginRead(/*some buffer stuff*/, Receive, null);
    }

    public void Receive(IAsyncResult ar)
    {
        stream.EndRead(ar);
        stream.BeginRead(/*again buffer stuff*/, Receive, null);
    }
}

互联网上有很多这样的例子。但是 MSDN 似乎建议使用异步方法,所以我想转换为它。这是我所拥有的:

class Server
{
    IPEndPoint ipep;
    TcpListener listener;
    bool Running;
    List<Client> clients;

    public Server(string host)
    {
        IPAddress ip = IPAddress.Parse(host);
        ipep = new(ip, 21);
        Running = false;
        clients = new();
    }

    public async Task Start()
    {
        listener = new(ipep);
        listener.Start();
        Running = true;
        while (Running)
        {
            Client c = await listener.AcceptTcpClientAsync();
            clients.Add(c);
            await c.WaitCommands();
        }
    }
}

class Client
{
    TcpClient client;
    NetworkStream stream;

    public Client(TcpClient client)
    {
        this.client = client;
        stream = client.GetStream();
    }

    public async Task WaitCommands()
    {
        while (true)
        {
            await stream.ReadAsync(/*buffer stuff*/);
        }
    }
}

显然await c.WaitCommands(); 会阻止其他客户端,因为应用程序卡在 while (true) 循环中并且永远不会再次到达 await Accept。我发现一些 _ = Task.Run(async () =&gt; await client.WaitCommands()); 可以解决问题。但据我了解,这与 Begin/End 方法不同(或者我错了吗?这也是问题所在)。

所以问题是

  1. 如何在第二个示例中从客户端开始读取并接受另一个?
  2. 我走对了吗?或者对于大多数用户计数规模应该使用什么方法(即,也许我应该为每个客户端使用一些线程)?

【问题讨论】:

  • 在接受循环中,您不应等待客户端完成。为接受循环使用一个线程并为每个客户端使用一个新线程是一种简单的模型,并且可以很好地扩展到数百个客户端。
  • @DavidBrowne-Microsoft 是的,这就是我所理解的,但是如何使用异步方法?第一个带有回调的例子效果很好,但我不知道如何用 async/await 做同样的事情,或者我什至应该像 MSDN 建议的那样做

标签: c# asynchronous tcp networkstream


【解决方案1】:

类似这样的:

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

var svr = new Server("127.0.0.1");
await svr.Run();
Console.WriteLine("Goodby, World!");

class Server
{
    IPEndPoint ipep;
    TcpListener listener;
    bool Running;
    List<Client> clients;
    private CancellationTokenSource cts;

    public Server(string host)
    {
        IPAddress ip = IPAddress.Parse(host);
        ipep = new(ip, 1121);
        Running = false;
        clients = new();

        this.cts = new CancellationTokenSource();
    }

    public void Stop()
    {
        Running = false;
        cts.Cancel();
    }
    public async Task Run()
    {
        listener = new(ipep);
        listener.Start();
        Running = true;
        while (Running)
        {
            var c = await listener.AcceptTcpClientAsync(cts.Token);
            var client = new Client(c);
            clients.Add(client);
            var clientTask = client.Run(); //don't await
            clientTask.ContinueWith(t => clients.Remove(client));
        }
    }
}

class Client
{
    TcpClient client;
    NetworkStream stream;

    public Client(TcpClient client)
    {
        this.client = client;
        stream = client.GetStream();
        
    }

    public async Task Run()
    {
        var r = new StreamReader(stream);
        var w = new StreamWriter(stream);
        while (true)
        {
            await w.WriteLineAsync("You are standing in an open field west of a white house, with a boarded front door. There is a small mailbox here.");
            await w.WriteAsync(">");
            await w.FlushAsync();

            var l = await r.ReadLineAsync();
            await w.WriteLineAsync("Invalid command " + l);
        }
    }
}

你可以用什么来测试

c:\> telnet localhost 1121

【讨论】:

  • 在没有等待的情况下调用阅读任务完成了工作,谢谢!这种方法比每个客户端线程更好吗?
  • 是的。它结合了每个客户端线程的简单性和没有大量​​空闲线程的可扩展性。
  • 尝试编译此代码时,var c = await listener.AcceptTcpClientAsync(cts.Token); 导致错误 CS1501: No overload for method 'AcceptTcpClientAsync' takes 1 arguments - VS 2019 / .NET 5
  • 这是 .NET 6 中的新功能。而且 .NET 5 即将停止支持。所以你应该升级。以前的版本调用TcpListener.Stop()取消,见例如https://stackoverflow.com/questions/12231789/cancel-blocking-accepttcpclient-call
猜你喜欢
  • 1970-01-01
  • 2019-04-18
  • 2020-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-26
  • 2019-02-26
  • 1970-01-01
相关资源
最近更新 更多