李子的博客

想想写写

端口重用引发的悲剧

linux | Comments

最近做个性能测试,需要在一台机器上启动很多客户端,连接到同一台服务器,我在一台机器上启动了六万个连接,于是,端口被占用完了。按照我的理解,因为我作用端口是作为客户端,应该不会影响到其它进程,于是我放心大胆地去做测试,结果就引发了悲剧。有服务器程序要用到5191端口,却显示端口被占用了,lsof看了下,居然只有我的进程占用了,完全颠覆我的惯性思想。服务端与客户端都有打开SO_REUSEADDR

我们先来看看SO_REUSEADDR的说明:

SO_REUSEADDR可以用在以下四种情况下。
(摘自《Unix网络编程》卷一,即UNPv1)
1、当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启
动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。
2、SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但
每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可
以测试这种情况。
3、SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个soc
ket绑定的ip地址不同。这和2很相似,区别请看UNPv1。
4、SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的
多播,不用于TCP。

分析了一下,第二种情况比较相似,但第二种情况是针对同一服务器的多个实例(多个进程),很显然跟我的情况不一样。我是在客户端与服务端间争用端口。 所以,我的情况根本就不适合以上任何一种情况,所以设置SO_REUSEADDR为1是无用的。

再接下来分析: 一个TCP连接需要由四元组来形成,即(src_ip,src_port,dst_ip,dst_port)。 如果有客户端建立了连接(src_ip1,src_port1,dst_ip1,dst_port1),那么,如果我们还有listen在(src_ip1,src_port1),那么当(dst_ip1,dst_port1)发送消息过来,系统应该把消息给谁?所以就说明了客户端占用了某一端口时,该端口就不能被其它进程listen了。

那么,对于有些童鞋,可能还有这样的疑问,是否一台机器就只能建立65535个连接了(端口16位限制)?非也,一个连接由四元组(src_ip,src_port,dst_ip,dst_port)形式,那么当(src_ip,src_port)一定时,变化的(dst_ip,dst_port)就可以建立更多连接了。

可能有些童鞋还有疑问,作为一个服务器监控一个端口,比如80端口,它为什么可以建立上百万个连接?首先要明白一点,当accept出来后的新socket,它所占用的本地端口依然是80端口,很多新手都以为是一个新的随机端口。由四元组就很容易分析到了,同一个(src_ip,src_port),它所对应的(dst_ip,dst_port)可以无穷变化,这样就可以建立很多个客户端的请求了。

Comments