【问题标题】:Release UDP port used by dead process on OS X释放 OS X 上死进程使用的 UDP 端口
【发布时间】:2017-03-23 13:50:05
【问题描述】:

我在 OS X 10.11.6 上并尝试运行通常在启动时侦听 UDP 端口 8008 的程序。

该程序在运行期间通常还会产生几个辅助子进程,但端口受父进程的约束。

不幸的是,当退出程序时,有时端口仍然打开,即使程序(父+子)不再存在。

发生这种情况时,如果我再次尝试运行该程序,它自然会失败并出现 EADDRINUSE 错误,在这些情况下,无论我尝试什么,我找到的唯一解决方案就是重新启动机器。

我很难相信不重新启动就无法释放端口。

以下是我目前运行的一些诊断程序(我在有和没有sudo 的情况下运行了所有这些):

使用端口8008lsof 查找进程:

$ lsof -i -n -P | grep UDP | grep 8008

但令人惊讶的是没有返回任何结果。

不过,netstat 让我更幸运:

$ netstat -tulnvp udp | grep 8008
udp4  0  0  *.8008    *.*    196724   9216  47205   0

所以,端口确实是绑定的,罪魁祸首是pid 47205,然而:

$ ps aux | grep 47205

不返回任何东西。对于 PID 4720647207(最肯定是分配给孩子的 PID)也是如此。我还尝试了grep 的其他变体(程序名称、路径等)。

我还查找了任何将47205 报告为其父级的进程:

$ ps -axo pid,ppid,command | grep 47205

所以子进程显然也死了。

无法kill 任何东西,我尝试 SIGHUP launchd 希望它可以删除任何僵尸子进程:

$ sudo kill HUP 1
$ sudo kill -s HUP 1

但是,唉,netstat 仍然显示端口绑定。

最后,我尝试重启回环接口:

$ sudo ifconfig lo down
$ sudo ifconfig lo up

但同样,没有效果。

自从程序上次运行以来我已经等了几个小时,所以我很确定现在任何超时都会发生,但端口不会被释放。

关于如何在不重新启动的情况下强制释放端口的任何想法?

编辑:

  • 有问题的程序是电子包裹的Patchwork
  • 这个问题来自这个github issue
  • 虽然首先找到防止问题发生的解决方案/错误修复将是理想的,但我也对从终端手动关闭该端口的方法感兴趣

【问题讨论】:

  • 完全不同的东西(基于 Go 和 C++ 的以太坊区块链应用程序,称为 geth 和 eth)有这个确切的问题。有时在退出应用程序时,我只剩下 UDP 端口 30303。除了重新启动之外,我没有尝试释放该端口。
  • 我有同样的问题,在我的 Ctrl-C、make、run 开发周期中发生过几次。不过,这并不总是发生在我身上。我怀疑只有在我杀死它后过早地(重建和)再次运行该过程时才会发生这种情况。也许这让操作系统处于一个糟糕的状态,它还没有完成清理上次运行的套接字。
  • 您应该知道,端口仅在进程关闭端口大约 2 分钟后才被操作系统关闭...这是为了防止“旧”数据包仍然通过网络传输与新端口关联。
  • @Myst 这就是为什么我提到“我已经等了几个小时......”
  • 我正在开发的普通 C 应用程序也有同样的问题,并且每次运行应用程序时都会发生这种情况。设置 SO_REUSEADDR 意味着我可以重新启动应用程序并且新实例能够再次绑定到 UDP 端口,但旧套接字仍然打开(根据netstat)并且每次重新启动应用程序时我都会获得另一个套接字。你有没有找到解决这个问题的方法?

标签: macos sockets osx-elcapitan


【解决方案1】:

在您的代码中,在您创建套接字之后、bind 调用之前,调用以下代码:

int val = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));

然后拨打bind。以上将允许套接字绑定成功,即使端口正在使用中。

两个进程在同一端口上尝试recvfrom,将导致其中一个进程接收数据包,而另一个进程不接收。哪一个会是不确定的。因此,请确保您实际上没有两个进程合法地运行并共享端口。

【讨论】:

  • 这不是我自己的代码,但它是开源的,我现在刚刚添加了项目的详细信息。一位开发人员刚刚提到他们已经尝试过 SO_REUSEADDR(或 node.js 中的等效项)但没有奏效。
【解决方案2】:

确实可以手动关闭端口而无需重新启动机器。在各种 linux 风格中,这通常通过发出伪装成进程的系统调用(例如套接字文件描述符上的close(fd) syscall)来使用 GDB 来完成。

这个过程:

  • 打开一个 UDP 端口:netcat -u 127.0.0.1 33333
  • 检查 UDP 端口:netstat -npu (u for UDP),它将为您提供占用该端口的 PID。
  • 运行:lsof -np $pid 为该 PID 获取套接字的文件描述符。
  • 然后为该 PID 运行 GDB:sudo gdb -p 73599
  • 在 GDB 内部运行 call close(file_descriptor)

示例:

COMMAND   PID  USER   FD   TYPE   DEVICE SIZE/OFF     NODE NAME
netcat  73599 ubunt  cwd    DIR  259,2     4096 13895497 /home/ubunt/Downloads
netcat  73599 ubunt  rtd    DIR  259,2     4096        2 /
netcat  73599 ubunt  txt    REG  259,2    31248 28835938 /bin/nc.openbsd
netcat  73599 ubunt  mem    REG  259,2    47600 23990813 /lib/x86_64-linux-gnu/libnss_files-2.23.so
netcat  73599 ubunt  mem    REG  259,2  1868984 23990714 /lib/x86_64-linux-gnu/libc-2.23.so
netcat  73599 ubunt  mem    REG  259,2   101200 23990866 /lib/x86_64-linux-gnu/libresolv-2.23.so
netcat  73599 ubunt  mem    REG  259,2    81040 23990710 /lib/x86_64-linux-gnu/libbsd.so.0.8.2
netcat  73599 ubunt  mem    REG  259,2   162632 23990686 /lib/x86_64-linux-gnu/ld-2.23.so
netcat  73599 ubunt    0u   CHR 136,19      0t0       22 /dev/pts/19
netcat  73599 ubunt    1u   CHR 136,19      0t0       22 /dev/pts/19
netcat  73599 ubunt    2u   CHR 136,19      0t0       22 /dev/pts/19
netcat  73599 ubunt    3u  IPv4 22142418    0t0      UDP 127.0.0.1:45255->127.0.0.1:33333

然后是 GDB:

$sudo gdb -p 73599
...
(gdb) call close(3u)
$1 = 0

您会看到端口不再存在:

ubunt@ubunt-MS-7A94:~$ lsof -np 73599
COMMAND   PID  USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
netcat  73599 ubunt  cwd    DIR  259,2     4096 13895497 /home/ubunt/Downloads
netcat  73599 ubunt  rtd    DIR  259,2     4096        2 /
netcat  73599 ubunt  txt    REG  259,2    31248 28835938 /bin/nc.openbsd
netcat  73599 ubunt  mem    REG  259,2    47600 23990813 /lib/x86_64-linux-gnu/libnss_files-2.23.so
netcat  73599 ubunt  mem    REG  259,2  1868984 23990714 /lib/x86_64-linux-gnu/libc-2.23.so
netcat  73599 ubunt  mem    REG  259,2   101200 23990866 /lib/x86_64-linux-gnu/libresolv-2.23.so
netcat  73599 ubunt  mem    REG  259,2    81040 23990710 /lib/x86_64-linux-gnu/libbsd.so.0.8.2
netcat  73599 ubunt  mem    REG  259,2   162632 23990686 /lib/x86_64-linux-gnu/ld-2.23.so
netcat  73599 ubunt    0u   CHR 136,19      0t0       22 /dev/pts/19
netcat  73599 ubunt    1u   CHR 136,19      0t0       22 /dev/pts/19
netcat  73599 ubunt    2u   CHR 136,19      0t0       22 /dev/pts/19

GDB 可用于 MacOS,因此它也应该适用于您的情况。

【讨论】:

  • 这行得通,只需在最新的 OSX 中使用 lldb 而不是 gdb(我猜是安装了 XCode)并对 call close 命令进行微调(需要转换为 int )。如果我有时间,我会用在 OSX 上运行的确切命令写一个答案。谢谢!
  • 只是为了完成@ktorn 评论——在lldb 你必须使用call (int)close(3u)
  • 这不起作用,因为lldb 只是(正确地)通知我该进程不存在。
  • 这个答案没有它可能有用。它关闭 netcat 端口,而不是卡住的进程。尝试gdb到卡住的进程不起作用,原来的监听socket还是打开的。
【解决方案3】:

系统可能会保持套接字打开,直到 I/O 进程仍在进行中。即使进程死亡但没有明确关闭套接字。如果您的套接字在几个小时内没有关闭,很可能您错过了一些东西。尝试使用低级内核调查而不是像 netstat 或 lsof 这样的顶级实用程序。

免责声明

我不是 OS X 专家,而且大多数命令都适用于 linux。如果其他人也有同样的问题,我还是把它留在那里。

1.试试看socket是否还活着(可选)

我可能会建议检查套接字通信。

 tcpdump -A -s0 port 8080  and tcpdump -A -s0 -ilo port 8080

如果您看到任何通过套接字传输的数据,则可以确定该进程处于活动状态。或者可能是它的孩子之一。稍后您可以使用 strace 捕获 pid

2。检查进程及其状态

Linux 有很棒的 procfs。你可以从那里得到很多东西。并确保您可以看到所有打开的文件描述符

ls -al  /proc/47205/fd

如果您看到输出并且 /proc/47205 存在,则 pid 未释放,但 ps 显示。您将看到所有打开的文件及其 fds。它看起来像

133 -> 套接字:[32242509]

其中 133 是 fd 编号

不幸的是 OS X 没有 /proc 文件系统。我找到的替代命令。

procexp 47205 fds

但我不确定它是否 100% 正常工作。

3.在另一个进程中关闭文件描述符(socket)

在 linux 中有不错的命令

fuser -k -n udp 8080

这将显式关闭所有阻塞端口的进程。好像是 OS X may have fuser too

另一个真正的黑客方法是使用 gdb 连接进程并在进程内运行命令,因为文件描述符编号仅在进程环境中有效,正如@Mindaugas Bernatavičius 所写:

gdb -p 47205
>call shutdown([fd_number],2)
>call close([fd_number])

还有第三种方式,如果可能的话,你可以重启整个网络。请注意,down 和 up 只是环回接口是不够的。在linux中运行

systemctl restart network  

4.如何防止socket卡在系统中

在程序退出之前,您应该始终确保 socked 关闭。 I seen many issues with nodejs 套接字保持打开状态。调用 Socket.destroy() 即可解决问题

在退出应用程序之前,可以将您的套接字销毁代码放在这里:

app.on('close', 函数(代码){

// 用户关闭了应用。杀死宿主进程。

process.exit();

});

【讨论】:

  • -1 因为答案对 macOS 来说 100% 没用,这就是问题所在。即使fuser 的工作方式也不同。此外,OP已经以多种方式表明没有过程,这使情况令人费解;但答案一直假设有一个。
  • 与此相关,即使 procexp 在这里也没用,因为它似乎报告了所有网络连接按进程 - 这正是我们没有的。
【解决方案4】:

您的问题类似于:


如你所说:

最后,我尝试重启回环接口:

$ sudo ifconfig 降低

$ sudo ifconfig lo up

您是否尝试重新启动所有可用的网络接口(lan 或 wlan),而不仅仅是环回)?

除了ifconfig,您还可以使用本机MacOS 命令实用程序(来自here)关闭然后打开设备本身(将en0 调整为your device name):

networksetup -setairportpower en0 off
networksetup -setairportpower en0 on

您最终也可以尝试通过以下方式释放和更新 DHCP:

sudo dhclient -v -r

问候

【讨论】:

【解决方案5】:

一个相关问题:mac改变了SO_REUSEADDR和SO_REUSEPORT的行为:

Behavior of SO_REUSEADDR and SO_REUSEPORT changed?

我是 iptux[1] 的维护者,如果我使用 SO_REUSEPORT,程序可以启动,但我无法从该端口接收 msg,所有消息都像黑洞一样进入未关闭的端口。

[1]https://github.com/iptux-src/iptux

【讨论】:

    猜你喜欢
    • 2011-12-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-15
    • 1970-01-01
    • 2016-08-08
    • 2012-03-22
    • 2016-06-30
    • 2012-08-12
    相关资源
    最近更新 更多