异步TCP服务器占用了套接字但停止监听。

4
我有一个异步TCP服务器,用于接受客户端上传文件的请求。然而在1-4小时后,该服务器会突然停止接受新的连接请求,这一点让我感到困惑,因为我无法确定地重现这个错误。程序中的其他线程继续正常运行,没有任何异常抛出。你有什么建议吗?
在服务器死亡之前,我只看到它成功完成了挂起的请求,然后就停止了。我猜测服务器经常遇到网络断开连接的情况。如何使这段代码更加容错?我已经在可能失败的两个代码块中添加了try-catch,但我感觉还是漏掉了些什么。
我设置了另一个线程来检查此服务器是否释放了套接字,但似乎即使在停止处理客户端请求后,套接字仍在使用中。我使用netstat -a进行了验证。
internal class TCPServer
{
    private readonly int _listeningPort;
    private TcpListener listener;

    public TCPServer(int port)
    {
        _listeningPort = port;
        listener = new TcpListener(IPAddress.Any, _listeningPort);
        listener.Start(int.MaxValue);
    }

    public async void Start()
    {
        while (true)
        {
            Log.Verbose("Waiting for connections...");
            try
            {
                var tcpClient = await listener.AcceptTcpClientAsync();
                Task t = HandleConnectionAsync(tcpClient);
                await t;
            }
            catch (Exception exp)
            {
                Log.Fatal(exp.ToString());
            }
        }
    }

    private async Task HandleConnectionAsync(TcpClient tcpClient)
    {
        try
        {
            string outputFile = ""; // get a random string

            using (var stream = tcpClient.GetStream())
            using (var output = File.Create(outputFile))
            {
                //...

                while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                {
                    // ...
                    await output.WriteAsync(buffer, 0, bytesRead);
                }
            }

            tcpClient.Close();
        }
        catch (Exception exp)
        {
            Log(exp.Message);
        }
    }

这可能会有所帮助:https://dev59.com/5WEi5IYBdhLWcg3wueN0 - noseratio - open to work
2个回答

6

有三个问题:

  1. 你逐个处理连接。一个故障/挂起的连接会阻塞整个服务器。只需删除 await t;。你已经在 worker 函数中记录了所有错误,这是很好的。
  2. 使 HandleConnectionAsync 很快返回,以便可以快速恢复接受新连接。我愿意牺牲一点效率,并且只说 Task.Run(() => HandleConnectionAsync(tcpClient)),以确保处理可以立即继续进行。目前,所有文件打开的工作都是同步的,并且会阻塞接受工作流程。
  3. 通过包装来确定性地关闭 tcpClient: using (tcpClient) ...。这可防止出现错误时僵尸客户端挂起。

1
这些是非常棒的观察。回想起来,这似乎是一个很愚蠢的错误。非常感谢您的澄清。服务器在过去的三天里没有崩溃过 :) 我会继续监控。最后一个问题:我是否正确地使用了等待操作在工作任务中进行网络读取和文件写入?对我来说,它看起来是正确的,但我只是想通过第二个人的眼睛确认一下。此外,您能否添加一些关于为什么等待操作首先引起问题的信息? - Legend
@Legend 读写是正确的。你可以(启发式地)通过等待代码看出来,它看起来就像同步代码,只是在上面机械地加了一个await。如果你不做任何奇怪的事情,使用await使代码异步化很容易。我会用一个问题回答最后一个问题:在你的脑海中,“await t;”是什么意思? - usr
谢谢!我的理解是它会使线程等待Task t完成,因此在接受另一个连接之前会等待。 - Legend
那几乎是正确的。只是这与线程无关,因为await的目的是释放线程而不是使它们等待/阻塞。但是没错,这就是为什么它不能立即处理下一个连接的原因。 - usr
明白了。谢谢你澄清这个问题! - Legend

-1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接