问题
前几天在项目上遇到一个问题,启动一个java服务,提示端口被占用,然后查询改端口被谁占用了,发现如下情况
tcp6 0 0 127.0.0.1:44446 127.0.0.1:44446 ESTABLISHED 13154/java
13154进程发生了自连接,端口被自己占用了,要想释放端口,只能重启该进程。
分析
经验证,可以很轻易的重现这个问题,最简单的方式,如下脚本就可以实现:
nc -p 28888 127.0.0.1 28888
上面的脚本就把28888端占用了,并发生了自连接。刚开始以为发现了一个大BUG,查询才发现,这种情况是TCP协议支持的。也就是说不是bug,详见Establishing a connection
发生这种现象,主要是操作系统分配给client的端口正好和server监听的端口一致,且client和server在同一台机器上。server监听的端口是固定的,那么就需要看一下操作系统分配给client临时端口的逻辑了。
操作系统有一个虚端口的概念,这个虚端口就是操作系统分配给client用来发起连接用的端口。详情见ephemeral_ports不同操作系统虚端口的范围是不一样的:
- AIX : 32768 – 65535
- BSD/OS : 49152 – 65535
- linux : 32768 – 61000
- windows :49152-65535
而我们的server监听端口正好在linux虚端口的范围内,所以就会导致tcp自连接的问题。
结论
经过分析发现,发生自连接,需要满足以下几个条件:
-
server和client在同一机器(同一IP)上
-
client先于server启动,或者server关掉了
-
client在connect失败时不断重试,我们的服务就是这样
-
cli在connect成功后一直持有连接,不主动断开
解决方案
- 参照ephemeral_ports,针对相应的操作系统,修改虚端口的范围,是的server监听的端口被排除在虚端口范围内
- 修改server监听的端口,避开虚端口范围。可以去iana查询常用端口占用情况