TCP 和 UDP 可以同时绑定相同的端口吗?
4.18 TCP 和 UDP 可以使 用同一个端口吗?
关于端口的知识点,还是挺多可以讲的,比如还可以牵扯到这几个问题:
多个 TCP 服务进程可以同时绑定同一个端口吗?
重启 TCP 服务进程时,为什么 会出现“Address in use” 的报错信息?又该怎么避免?
客⼾端的端口可以重复使用吗?
客⼾端 TCP 连接 TIME_WAIT 状态过多,会导致端口资源耗尽而无法建立新的连接吗?
所以,这次就跟大家盘一盘这些问题。
TCP 和 UDP 可以同时绑定相同的端口吗?
其实我感觉这个问题「TCP 和 UDP 可以同时监听相同的端口吗?」表述有问题,这个问题应该表
述成「TCP 和 UDP 可以同时绑定相同的端口吗?」
因为「监听」这个动作是在 TCP 服务端网络编程中才具有的,而 UDP 服务端网络编程中是没有
「监听」这个动作的。
TCP 和 UDP 服务端网络相似的一个地方,就是会调用 bind 绑定端口。
给大家贴一下 TCP 和 UDP 网络编程的区别就知道了。

TCP 网络编程如下,服务端执行 listen() 系统调用就是监听端口的动作。
UDP 网络编程如下,服务端是没有监听这个动作的,只有执行 bind() 系统调用来绑定端口的动作。

TCP 和 UDP 可以同时绑定相同的端口吗?
答案:可以的。
在数据链路层中,通过 MAC 地址 来寻找局域网中的主机。在网际层中,通过 IP 地址 来寻找网络
中互 连的主机或路由器。在传输层中,需要通过端口进行寻址,来识别同一计算机中同时通信的
不同应用程序。
所以,传输层的「端口号」的作用,是为了 区分同一个主 机上不 同应用程序的数据包。
传输层有两个 传输协议分别 是 TCP 和 UDP ,在内核中是两个 完全独立的软件模块。
当主机收到数据包后,可以在 IP 包头的「协议号」字段知道该数据包是 TCP/UDP ,所以可以根据
这个信息确定送给哪个模块(TCP/UDP )处理,送给 TCP/UDP 模块的报文根据「端口号」确定送
给哪个应用程序处理。

因此, TCP/UDP 各自的端口号也相互独立,如 TCP 有一个 80 号端口,UDP 也可以有一个 80 号
端口,二者并不冲突。
验证结果
我简单写了 TCP 和 UDP 服务端的程序,它们都绑定同一个端口号 8888 。

运行这两个 程序后,通过 netstat 命令可以看到,TCP 和 UDP 是可以同时绑定同一个端口号的。

多个 TCP 服务进程可以绑定同一个端口吗?
还是以前面的 TCP 服务端程序作为例子,启动两个 同时绑定同一个端口的 TCP 服务进程。
运行第一个 TCP 服务进程之后,netstat 命令可以查看,8888 端口已经被一个 TCP 服务进程绑定
并监听了,如下图:

接着,运行第二个 TCP 服务进程的时候,就报错了“Address already in use” ,如下图:

我上面的测试案例是两个 TCP 服务进程同时绑定地址 和端口是:0.0.0.0 地址 和8888 端口,所以才
出现的错误。
如果两个 TCP 服务进程绑定的 IP 地址 不同,而端口相同的话,也是可以绑定成功的,如下图:

所以,默认情况下,针对「多个 TCP 服务进程可以绑定同一个端口吗?」这个问题的答案是:如
果两个 TCP 服务进程同时绑定的 IP 地址 和端口都相同,那么执行 bind() 时候就会出错,错误是“Address already in use” 。
注意,如果 TCP 服务进程 A 绑定的地址 是 0.0.0.0 和端口 8888 ,而如果 TCP 服务进程 B 绑定的地址是 192.168.1.100 地址 (或者其他地址 )和端口 8888 ,那么执行 bind() 时候也会出错。这是因为 0.0.0.0 地址 比较特殊,代表任意地址 ,意味着绑定了 0.0.0.0 地址 ,相当于把主机上的所有 IP 地址 都绑定了。
TIP
如果想多个进程绑定相同的 IP 地址 和端口,也是有办法的,就是对 socket 设置
SO_REUSEPORT 属性(内核 3.9 版本提供的新特性),本文不对 SO_REUSEPORT 做具体介绍,感兴趣的同学自行去学习。
重启 TCP 服务进程时,为什么 会有“Address in use” 的报错信息?
TCP 服务进程需要绑定一个 IP 地址 和一个端口,然后就监听在这个地址 和端口上,等待客⼾端连
接的到来。
然后在实践中,我们可能会经常碰到一个问题,当 TCP 服务进程重启之后,总是碰到“Address in use” 的报错信息,TCP 服务进程不能很快 地重启,而是要过一会才能重启成功。
这是为什么 呢?
当我们重启 TCP 服务进程的时候,意味着通过服务器端发起了关闭连接操作,于是就会经过四次
挥手,而对于主 动关闭方,会在 TIME_WAIT 这个状态里停留一段时间,这个时间大约为 2MSL 。

当 TCP 服务进程重启时,服务端会出现 TIME_WAIT 状态的连接,TIME_WAIT 状态的连接使用的
IP+PORT 仍然被认为是一个有效的 IP+PORT 组合,相同机器上不 能够在该 IP+PORT 组合上进行
绑定,那么执行 bind() 函数的时候,就会返回了 Address already in use 的错误。而等 TIME_WAIT 状态的连接结束后,重启 TCP 服务进程就能成功。
重启 TCP 服务进程时,如何避免“Address in use” 的报错信息?
我们可以在调用 bind 前,对 socket 设置 SO_REUSEADDR 属性,可以解决这个问题。
int on = 1;
setsockopt (listenfd , SOL_SOCKET , SO_REUSEADDR , &on , sizeof (on ));因为 SO_REUSEADDR 作用是:如果当前启动进程绑定的 IP+PORT 与处于TIME_WAIT 状态的连接
占用的 IP+PORT 存在冲突,但是新启动的进程使用了 SO_REUSEADDR 选项,那么该进程就可以
绑定成功。
举个 例子,服务端有个监听 0.0.0.0 地址 和 8888 端口的 TCP 服务进程。

有个客⼾端(IP 地址 :192.168.1.100 )已经和服务端(IP 地址 :172.19.11.200 )建立了 TCP 连
接,那么在 TCP 服务进程重启时,服务端会与客⼾端经历四次挥手,服务端的 TCP 连接会短暂处
于 TIME_WAIT 状态:
客户端地址:端口 服务端地址:端口 TCP 连接状态
192.168.1.100:37272 172.19.11.200:8888 TIME_WAI
如果 TCP 服务进程没有对 socket 设置 SO_REUSEADDR 属性,那么在重启时,由于存在一个和绑
定 IP+PORT 一样的 TIME_WAIT 状态的连接,那么在执行 bind() 函数的时候,就会返回了 Address already in use 的错误。如果 TCP 服务进程对 socket 设置 SO_REUSEADDR 属性了,那么在重启时,即使存在一个和绑定
IP+PORT 一样的 TIME_WAIT 状态的连接,依然可以正常绑定成功,因此可以正常重启成功。
因此,在所有 TCP 服务器程序中,调用 bind 之前最好对 socket 设置 SO_REUSEADDR 属性,这不
会产生危害,相反,它会帮助我们在很快 时间内重启服务端程序。
前面我提到过这 个问题:如果 TCP 服务进程 A 绑定的地址 是 0.0.0.0 和端口 8888 ,而如果 TCP 服
务进程 B 绑定的地址 是 192.168.1.100 地址 (或者其他地址 )和端口 8888 ,那么执行 bind() 时候也会出错。
这个问题也可以由 SO_REUSEADDR 解决,因为它的另外一个作用:绑定的 IP 地址 + 端口时,只
要 IP 地址 不是正好(exactly) 相同,那么允许绑定。
比如,0.0.0.0:8888 和192.168.1.100:8888 ,虽然逻辑意义上 前者包含了后者,但是 0.0.0.0 泛指所有本 地 IP ,而 192.168.1.100 特指某一IP ,两者并不是完全相同,所以在对 socket 设置SO_REUSEADDR 属性后,那么执行 bind() 时候就会绑定成功。客⼾端的端口可以重复使用吗?
客⼾端在执行 connect 函数的时候,会在内核里随机选择一个端口,然后向 服务端发起 SYN 报
文,然后与服务端进行三次握手。

所以,客⼾端的端口选择的发生在 connect 函数,内核在选择端口的时候,会从
net.ipv4.ip_local_port_range 这个内核参数指定的范围来选取一个端口作为客⼾端端 口。
该参数的默认值是 32768 61000 ,意味着端口总可用的数量是 61000 - 32768 = 28232 个。当客⼾端与服务端完成 TCP 连接建立后,我们可以通过 netstat 命令查看 TCP 连接。
$ netstat -napt
协议 源ip地址:端口 目的ip地址:端口 状态
tcp 192.168.110.182.64992 117.147.199.51.443 ESTABLISHED
那问题来了,上面客⼾端已经用了 64992 端口,那么还可以继续使用该端口发起连接吗?
这个问题,很多同学都会说不可以继续使用该端口了,如果按这个理解的话, 默认情况下客⼾端
可以选择的端口是 28232 个,那么意味着客⼾端只能最多建立 28232 个 TCP 连接,如果真是这样
的话,那么这个客⼾端并发连接也太少了吧,所以这是错误理解。
正确的理解是,TCP 连接是由四元组(源IP 地址 ,源端口,目的IP 地址 ,目的端口)唯一确认的,
那么只要四元组中其中一个元素发生了变化 ,那么就表示不同的 TCP 连接的。所以如果客⼾端已
使用端口 64992 与服务端 A 建立了连接,那么客⼾端要与服务端 B 建立连接,还是可以使 用端口
64992 的,因为内核是通过四元祖信息来定位一个 TCP 连接的,并不会因为客⼾端的端口号相
同,而导致连接冲突的问题。
比如下面这张图,有 2 个 TCP 连接,左边是客⼾端,右边是服务端,客⼾端使用了相同的端口
50004 与两个 服务端建立了 TCP 连接。

仔细看,上面这两条 TCP 连接的四元组信息中的「目的 IP 地址 」是不同的,一个是 180.101.49.12
,另外一个是 180.101.49.11 。
多个客⼾端可以 bind 同一个端口吗?
bind 函数虽然常用于服务端网络编程中,但是它也是用于客⼾端的。
前面我们知道,客⼾端是在调用 connect 函数的时候,由内核随机选取一个端口作为连接的端
口。
而如果我们想自己指定连接的端口,就可以用 bind 函数来实现:客⼾端先通过 bind 函数绑定一
个端口,然后调用 connect 函数就会跳过端口选择的过程了,转而使用 bind 时确定的端口。
针对这个问题:多个客⼾端可以 bind 同一个端口吗?
要看多个客⼾端绑定的 IP + PORT 是否都相同,如果都是相同的,那么在执行 bind() 时候就会出错,错误是“Address already in use” 。
如果一个绑定在 192.168.1.100:6666 ,一个绑定在 192.168.1.200:6666 ,因为 IP 不相同,所以执行
bind() 的时候,能正常绑定。
所以, 如果多个客⼾端同时绑定的 IP 地址 和端口都是相同的,那么执行 bind() 时候就会出错,错误是“Address already in use” 。
一般而言,客⼾端不建议使用 bind 函数,应该交由 connect 函数来选择端口会比较好,因为客⼾
端的端口通常都没什么 意义。
客⼾端 TCP 连接 TIME_WAIT 状态过多,会导致端口资源耗尽而无法建立新的连接吗?
针对这个问题要看,客⼾端是否都是与同一个服务器( 目标地址 和目标端口一样)建立连接。
如果客⼾端都是与同一个服务器( 目标地址 和目标端口一样)建立连接,那么如果客⼾端
TIME_WAIT 状态的连接过多,当端口资源被耗尽,就无法与这个服务器再建立连接了。
但是,因为只要客⼾端连接的服务器不同,端口资源可以重复使用的。
所以,如果客⼾端都是与不 同的服务器建立连接,即使客⼾端端 口资源只有几万个 , 客⼾端发起
百万级连接也是没问题的(当然这个过程还会受限于其他资源,比如文件描述符、内存、CPU
等)。
如何解决客⼾端 TCP 连接 TIME_WAIT 过多,导致无法与同一个服务器建立连接的问题?
前面我们提到,如果客⼾端都是与同一个服务器( 目标地址 和目标端口一样)建立连接,那么如
果客⼾端 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 传输协议,在内核中是由两个 完全独立的软件模块实现的。
当主机收到数据包后,可以在 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 连接的。
所以,如果客⼾端已使用端口 64992 与服务端 A 建立了连接,那么客⼾端要与服务端 B 建立连
接,还是可以使 用端口 64992 的,因为内核是通过四元祖信息来定位一个 TCP 连接的,并不会因
为客⼾端的端口号相同,而导致连接冲突的问题。
客⼾端 TCP 连接 TIME_WAIT 状态过多,会导致端口资源耗尽而无法建立新的连接吗?
要看客⼾端是否都是与同一个服务器( 目标地址 和目标端口一样)建立连接。
如果客⼾端都是与同一个服务器( 目标地址 和目标端口一样)建立连接,那么如果客⼾端
TIME_WAIT 状态的连接过多,当端口资源被耗尽,就无法与这个服务器再建立连接了。即使在这
种状态下,还是可以与其他服务器建立连接的,只要客⼾端连接的服务器不是同一个,那么端口
是重复使用的。
