DotNet: SocketException (111): Debian 上的 UDP 连接被拒绝

问题描述

我遇到了一个奇怪的问题;我有两个应用程序,一个前端和一个后端。前端使用 UDP 向后端发送消息。我有使用 UDP 的特定原因,但其中之一是我不在乎后端何时因某种原因关闭或无法访问。我只是想让消息丢失。是的,你没看错?

现在忘记后端。让我们播放后端未启动并且前端无论如何都会向后端发送 UDP 数据包的场景。客户端发送永远不会到达的消息,在 Windows 上就好了。如预期。在 Linux(确切地说是 Debian)上,我得到一个 SocketException (111): Connection refused。当我将消息发送到(不存在的)远程主机时,不会发生。但是,只要主机是 localhost127.0.0.1 甚至 192.168.1.1(机器的 LAN IP),就会抛出异常。当主机是 8.8.8.8192.168.1.10(我的 LAN 中的某个随机主机)或 192.168.1.200(我的 LAN 中不存在的主机)时,例外是not抛出。

因为我觉得我快疯了,所以我用其他语言测试了这个,它们似乎都表现正确(即发送消息(永远不会到达),没有例外)。

这是在同一台 Debian 10 机器上运行的方法

Python

import socket

sock = socket.socket(socket.AF_INET,socket.soCK_DGRAM)
while True:
    sock.sendto(b"Hello,World!",("127.0.0.1",12345))
    print(".",end = '')

PHP

<?PHP
$sock = socket_create(AF_INET,SOCK_DGRAM,SOL_UDP);

$msg = "Hello World!";
$len = strlen($msg);

while (true) {
    socket_sendto($sock,$msg,$len,'127.0.0.1',12345);
    echo '.';
}

Perl

use Socket;
socket(SOCKET,PF_INET,getprotobyname("udp")) or die "socket: $!";

$MSG = "Hello World!";
$endpoint = sockaddr_in(12345,inet_aton("127.0.0.1"));
while(true) {
    send(SOCKET,$MSG,$endpoint) == length($MSG) or die "cannot send: $!";
    print(".");
}

对于上述所有三个脚本,输出为(如预期):.............…

现在是 .Net (C#) 版本(我开始是异步的,但在我的搜索中我什至又回到了同步):

C# 同步

static void Main(string[] args)
{
    var data = Encoding.UTF8.GetBytes("Hello world");
    var client = new UdpClient("127.0.0.1",12345);

    while (true) {
        client.Send(data,data.Length);
        Console.Write(".");
    }
}

C# 异步

static async Task Main(string[] args)
{
    var data = Encoding.UTF8.GetBytes("Hello world");
    var client = new UdpClient("127.0.0.1",12345);

    while (true) {
        await client.SendAsync(data,data.Length).ConfigureAwait(false);
        Console.Write(".");
    }
}

我什至重写了代码使用UdpClient,而是使用Socket;同样的结果。 .Net 不断抛出 SocketException (111): Connection refused

我发现可能唯一相关的是this over at PerlMonks

选项 SO_BSDCOMPAT 的套接字手册页 SOCKET OPTIONS 部分的片段:

如果启用了为 UDP 套接字接收的 ICMP 错误,则不会将其传递给用户程序。在以后的内核版本中,对这个选项的支持已经被逐步淘汰:Linux 2.4 会地忽略它,如果程序使用这个选项,Linux 2.6 会生成一个内核警告 (printk())。

Manpage

但由于我无法设置任何标志,甚至无法设置类似于 SO_BSDCOMPAT 之类的标志,这似乎是一个死胡同(甚至可能不是问题所在)。

有没有人ANY知道这里发生了什么?

总结:当没有“后端”(或服务器)监听时,以上 C# 在 Windows 上运行良好;消息会丢失 - 正如预期的那样。在 Linux (Debian 10) 上,C# 代码抛出异常,但 Python、Perl 和 PHP 似乎工作得很好,排除了操作系统 (恕我直言)。我正在运行 dotnet 5.0.202、Windows 10 和 Debian 10(Linux myhost 4.19.0-13-amd64 #1 SMP Debian 4.19.160-2 (2020-11-28) x86_64 GNU/Linux) - 都是最新的。

解决方法

好吧,你一定是在开玩笑...正如我所说:

我什至重写了代码以使用 UdpClient 而是使用 Socket

...但我在套接字上使用了 .Send().SendAsync() 方法。我只是用 .SendTo().SendToAsync() 方法尝试过,这似乎有效。哪个...有点道理,因为要使 .Send().SendAsync() 方法起作用,您需要先调用 .Connect(),而对于 .SendTo()/.SendToAsync() 允许您在 SendTo... 调用本身中指定远程端点。

static void Main(string[] args)
{
    var data = Encoding.UTF8.GetBytes("Hello world");
    var socket = new Socket(SocketType.Dgram,ProtocolType.Udp);
    var ep = new IPEndPoint(IPAddress.Parse("127.0.0.1"),12345);

    while (true) {
        socket.SendTo(data,ep);
        Console.Write(".");
    }
}

所以这可以/将被标记为已解决。