【问题标题】:How do I properly clean up TcpListener, to avoid "Address already in use" errors?如何正确清理 TcpListener,以避免“地址已在使用”错误?
【发布时间】:2018-07-17 18:24:22
【问题描述】:

多次运行我的程序时,我不断收到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时也不会出现这种情况,所以这似乎是特定于微软的linux实现。

【问题讨论】:

  • 解决方法可能是使用SetSocketOption()ReuseAddress 设置为true
  • @itsme86 谢谢!我还没有想到。您是否会碰巧知道该选项是否有任何副作用? (只要同时运行的程序实例不超过一个)
  • 我自己过去用过,没有任何副作用。如果有的话,我不知道。
  • 在服务器中使用SO_REUSEADDR 是相当标准的。使用它出现问题的可能性实际上很小。首先解释问题的原因在这里:stackoverflow.com/a/3233022/1076479

标签: c# sockets .net-core


【解决方案1】:

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

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

Windows 和 Linux(可能还有 MacOS)之间的差异是由differences in the socket implementations 引起的:

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

正如this other question 中所述,SO_REUSEADDR 可用于放宽 Linux 上的这些限制。

Mono 已尝试通过将 SO_REUSEADDR 设置为 true by default 来模拟 Windows 的行为。官方的 .NET Core 运行时不这样做,因此会在 Linux 上导致“地址已在使用”错误。

但这并不意味着始终设置SO_REUSEADDR 是安全的!在 Windows 上,SO_REUSEADDR 的含义略有不同 (source):

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

因此,SO_REUSEADDR 只能在非 Windows 系统上设置。

【讨论】:

    猜你喜欢
    • 2012-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-22
    • 1970-01-01
    • 2021-01-25
    相关资源
    最近更新 更多