问题

前几天在项目上遇到一个问题,启动一个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查询常用端口占用情况