C# 如何正确清理TcpListener,以避免;“地址已在使用中”;错误?

C# 如何正确清理TcpListener,以避免;“地址已在使用中”;错误?,c#,sockets,.net-core,C#,Sockets,.net Core,在多次运行我的程序时,我不断得到一个已在使用的SocketException:Address 最简单的例子: using System; using System.Net; using System.Net.Sockets; using System.Threading; namespace test { class Program { static TcpListener listener; static void Main(string[]

在多次运行我的程序时,我不断得到一个已在使用的
SocketException:Address

最简单的例子:

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

namespace test
{
    class Program
    {
        static TcpListener listener;
        static void Main(string[] args)
        {
            listener = new TcpListener(new IPEndPoint(IPAddress.Any, 3349));
            listener.Start();
            listener.BeginAcceptSocket(Accept, null);
            Console.WriteLine("Started!");

            // Simulate a random other client connecting, nc localhost 3349 does the same thing
            var client = new TcpClient();
            client.Connect("localhost", 3349);

            Thread.Sleep(2000);
            listener.Stop();

            Console.WriteLine("Done!");
        }

        static void Accept(IAsyncResult result)
        {
            using(var socket = listener.EndAcceptSocket(result))
            {
                Console.WriteLine("Accepted socket");
                Thread.Sleep(500);
                socket.Shutdown(SocketShutdown.Both);
            }

            Console.WriteLine("Socket fully closed");
        }
    }
}
运行程序两次(
dotnet Run
):第一次将正常完成,但第二次将失败,并显示“地址已在使用”

请注意,缺少的dispose of
客户端
不是这里的问题——我可以使用
nc localhost 3349
手动复制相同的错误

如何清理侦听器以避免出错

操作系统和.NET信息:

dotnet --version
2.1.103

lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.4 LTS
Release:        16.04
Codename:       xenial

Windows上不存在此问题。使用mono时也不会出现这种情况,因此这似乎是特定于Microsoft的linux实现。

这实际上是预期的行为。若要修复此错误,应将套接字选项设置为
true
,前提是且仅当代码未在Windows上运行:

if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
}

Windows和Linux(可能还有MacOS)之间的差异是由以下因素造成的:

  • 在Windows上,您可以绑定到一个地址和IP,除非在同一地址/IP组合上有另一个连接处于活动状态
  • 在Linux上,您可以绑定到一个地址,除非同一地址/IP组合上有任何其他连接。这意味着,如果仍有连接处于TIME_WAIT或CLOSE_WAIT状态,则无法绑定
如中所述,
SO_REUSEADDR
可用于放松Linux上的这些限制

Mono试图通过将
SO\u REUSEADDR
设置为
true
来模拟Windows的行为。官方的.NET核心运行时不这样做,因此会在Linux上导致“地址已在使用”错误

然而,这并不意味着总是将
设置为\u REUSEADDR
是安全的!在Windows上,
SO_REUSEADDR
的意思稍有不同():

具有SO_REUSEADDR的套接字始终可以绑定到完全相同的源 地址和端口作为已绑定的套接字,即使另一个套接字 绑定时未设置此选项。这种行为是错误的 有些危险,因为它允许应用程序“窃取” 另一个应用程序的已连接端口


因此,只能在非Windows系统上设置
SO\u REUSEADDR

一个解决方法可能是将
设置为true
@itsme86谢谢!我还没想到。你知道这个选择是否有副作用吗?(只要一个程序的实例不超过一个)过去我自己用过,没有副作用。如果有,我不知道。在服务器中使用
是相当标准的,所以\u REUSEADDR
。使用它产生问题的可能性实际上很小。这里首先解释了问题的原因: