TCP 和 UDP 可以使用同一个端口吗?( 四 )

 
前面我们提到,如果客户端都是与同一个服务器(目标地址和目标端口一样)建立连接,那么如果客户端 TIME_WAIT 状态的连接过多,当端口资源被耗尽,就无法与这个服务器再建立连接了 。
针对这个问题,也是有解决办法的,那就是打开 net.ipv4.tcp_tw_reuse 这个内核参数 。
因为开启了这个内核参数后,客户端调用 connect 函数时,如果选择到的端口,已经被相同四元组的连接占用的时候,就会判断该连接是否处于 TIME_WAIT 状态,如果该连接处于 TIME_WAIT 状态并且 TIME_WAIT 状态持续的时间超过了 1 秒,那么就会重用这个连接,然后就可以正常使用该端口了 。
举个例子,假设客户端已经与服务器建立了一个 TCP 连接,并且这个状态处于 TIME_WAIT 状态:
客户端地址:端口 服务端地址:端口 TCP 连接状态 192.168.1.100:2222 172.19.11.21:8888 TIME_WAIT
然后客户端又与该服务器(172.19.11.21:8888)发起了连接,在调用 connect 函数时,内核刚好选择了 2222 端口,接着发现已经被相同四元组的连接占用了:
 

  • 如果没有开启net.ipv4.tcp_tw_reuse 内核参数,那么内核就会选择下一个端口,然后继续判断,直到找到一个没有被相同四元组的连接使用的端口,如果端口资源耗尽还是没找到,那么 connect 函数就会返回错误 。
  • 如果开启了 net.ipv4.tcp_tw_reuse 内核参数,就会判断该四元组的连接状态是否处于 TIME_WAIT 状态,如果连接处于 TIME_WAIT 状态并且该状态持续的时间超过了 1 秒,那么就会重用该连接,于是就可以使用 2222 端口了,这时 connect 就会返回成功 。
 
再次提醒一次,开启了 net.ipv4.tcp_tw_reuse 内核参数,是客户端(连接发起方) 在调用 connect() 函数时才起作用,所以在服务端开启这个参数是没有效果的 。
 
客户端端口选择的流程总结
 
至此,我们已经把客户端在执行 connect 函数时,内核选择端口的情况大致说了一遍,为了让大家更明白客户端端口的选择过程,我画了一流程图 。
TCP 和 UDP 可以使用同一个端口吗?

文章插图
 
总结
TCP 和 UDP 可以同时绑定相同的端口吗?
 
可以的 。
TCP 和 UDP 传输协议,在内核中是由两个完全独立的软件模块实现的 。
当主机收到数据包后,可以在 IP 包头的「协议号」字段知道该数据包是 TCP/UDP,所以可以根据这个信息确定送给哪个模块(TCP/UDP)处理,送给 TCP/UDP 模块的报文根据「端口号」确定送给哪个应用程序处理 。
因此,TCP/UDP 各自的端口号也相互独立,互不影响 。
 
多个 TCP 服务进程可以同时绑定同一个端口吗?
 
如果两个 TCP 服务进程同时绑定的 IP 地址和端口都相同,那么执行 bind() 时候就会出错,错误是“Address already in use” 。
如果两个 TCP 服务进程绑定的端口都相同,而 IP 地址不同,那么执行 bind() 不会出错 。
 
如何解决服务端重启时,报错“Address already in use”的问题?
 
当我们重启 TCP 服务进程的时候,意味着通过服务器端发起了关闭连接操作,于是就会经过四次挥手,而对于主动关闭方,会在 TIME_WAIT 这个状态里停留一段时间,这个时间大约为 2MSL 。
当 TCP 服务进程重启时,服务端会出现 TIME_WAIT 状态的连接,TIME_WAIT 状态的连接使用的 IP+PORT 仍然被认为是一个有效的 IP+PORT 组合,相同机器上不能够在该 IP+PORT 组合上进行绑定,那么执行 bind() 函数的时候,就会返回了 Address already in use 的错误 。
要解决这个问题,我们可以对 socket 设置 SO_REUSEADDR 属性 。
这样即使存在一个和绑定 IP+PORT一样的 TIME_WAIT 状态的连接,依然可以正常绑定成功,因此可以正常重启成功 。
 
客户端的端口可以重复使用吗?
 
在客户端执行 connect 函数的时候,只要客户端连接的服务器不是同一个内核,允许端口重复使用 。
TCP 连接是由四元组(源IP地址,源端口,目的IP地址,目的端口)唯一确认的,那么只要四元组中其中一个元素发生了变化,那么就表示不同的 TCP 连接的 。


推荐阅读