在 OS X 上释放死进程使用的 UDP 端口

Release UDP port used by dead process on OS X

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

该程序在其运行期间通常还会生成几个辅助 child 进程,但端口由 parent 进程绑定。

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

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

我很难相信我无法在不重新启动的情况下释放端口。

这是我 运行 到目前为止的一些诊断(我 运行 所有这些有和没有 sudo):

查找使用端口 8008lsof:

的进程
$ lsof -i -n -P | grep UDP | grep 8008

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

然而,我更幸运 netstat:

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

所以,端口确实绑定了,罪魁祸首是pid 47205,但是:

$ ps aux | grep 47205

return 什么都没有。 PID 4720647207 也是一样(最肯定的是分配给 children 的 PID)。我还尝试了 grep 的其他变体(程序名称、路径等)。

我还寻找任何进程报告 47205 作为其 parent:

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

所以 children 进程显然也死了。

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

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

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

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

$ sudo ifconfig lo down
$ sudo ifconfig lo up

但还是没有效果。

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

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

编辑:

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

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

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

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

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

流程:

  • 打开一个 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)
 = 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,因此它也适用于您的情况。

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

免责声明

我不是 OS X 专家,大多数命令都是 linux。如果其他人有同样的问题,我仍然把它留在那里。

1.尝试查看套接字是否仍然存在(可选)

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

 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.It 看起来像

133 -> socket:[32242509]

其中 133 是 fd 编号

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

procexp 47205 fds

但我不确定它是否 100% 有效。

3。关闭另一个进程中的文件描述符(套接字)

在linux中有一个很好的命令

fuser -k -n udp 8080

这将明确关闭所有阻塞端口的进程。好像是OSXmay 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.What 防止套接字卡在系统中

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

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

app.on('close', function (code) {

// User closed the app. Kill the host process.

process.exit();

});

您的问题类似于:


如你所说:

Lastly, I tried to restart the loopback interface:

$ sudo ifconfig lo down

$ sudo ifconfig lo up

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

您也可以使用本机 MacOS 命令实用程序(来自 here)而不是 ifconfig 来关闭然后打开设备本身的电源(将 en0 调整为 your device name ):

networksetup -setairportpower en0 off
networksetup -setairportpower en0 on

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

sudo dhclient -v -r

此致

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

Behavior of SO_REUSEADDR and SO_REUSEPORT changed?

并且我是iptux[1]的维护者,如果我使用SO_REUSEPORT,程序可以启动,但是我无法从这个端口接收到消息,所有的消息都去未关闭的端口作为黑洞。

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