多次运行程序时,我不断收到SocketException: Address already in use
。
最小示例:
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
):第一次将正常完成,但第二次将失败,提示“地址已在使用中”。
请注意,client
缺少的配置不是这里的问题-我可以使用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实现的。
答案 0 :(得分:1)
这实际上是预期的行为。要更正此错误,只有在代码未在Windows上运行时,才应将ReuseAddress
套接字选项设置为true
:
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
}
Windows和Linux(可能是MacOS)之间的差异是由differences in the socket implementations引起的:
如this other question中所述,SO_REUSEADDR
可用于放宽对Linux的这些限制。
Mono尝试通过将SO_REUSEADDR
设置为true
by default来模仿Windows的行为。官方.NET核心运行时不执行此操作,因此将在Linux上导致“地址已在使用中”错误。
但是,这并不意味着始终设置SO_REUSEADDR
是安全的!在Windows上,SO_REUSEADDR
的含义有所不同(source):
具有SO_REUSEADDR的套接字可以始终绑定到完全相同的源 地址和端口作为已绑定的套接字,,即使另一个套接字 绑定时未设置此选项。此行为是 有点危险,因为它允许应用程序“窃取” 另一个应用程序的连接端口。
因此,SO_REUSEADDR
仅应在非Windows系统上设置。