Skip to content

4.19 TCP 和 UDP 可以使用同一个端口吗?

TCP 和 UDP 服务端网络相似的一个地方,就是会调用 bind 绑定端口。

TCP 网络编程如下,服务端执行 listen() 系统调用就是监听端口的动作。

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

因此监听是TCP服务端才有的,UDP服务端是没有监听的动作的

TCP 和 UDP 可以同时绑定相同的端口吗?

可以的

  • 在数据链路层中,通过 MAC 地址来寻找局域网中的主机。
  • 在网际层中,通过 IP 地址来寻找网络中互连的主机或路由器。
  • 在传输层中,需要通过端口进行寻址,来识别同一计算机中同时通信的不同应用程序。

所以,传输层的「端口号」的作用,是为了区分同一个主机上不同应用程序的数据包。

传输层有两个传输协议分别是 TCP 和 UDP,在内核中是两个完全独立的软件模块。

当主机收到数据包后,可以在 IP 包头的「协议号」字段知道该数据包是 TCP/UDP,所以可以根据这个信息确定送给哪个模块(TCP/UDP)处理,送给 TCP/UDP 模块的报文根据「端口号」确定送给哪个应用程序处理。

因此,TCP/UDP 各自的端口号也相互独立,如 TCP 有一个 80 号端口,UDP 也可以有一个 80 号端口,二者并不冲突。

多个 TCP 服务进程可以绑定同一个端口吗?

  • 如果两个 TCP 服务进程同时绑定的 IP 地址和端口都相同,那么执行 bind() 时候就会出错,错误是“Address already in use”
    • 可以对 socket 设置 SO_REUSEADDR 属性来解决,其作用是:如果当前启动进程绑定的 IP+PORT 与处于 TIME_WAIT 状态的连接占用的 IP+PORT 存在冲突,但是新启动的进程使用了 SO_REUSEADDR 选项,那么该进程就可以绑定成功
  • 如果绑定的是不同的 IP 地址和相同的端口,那么是可以正常运行的
    • 如果一个 TCP服务绑定了0.0.0.0 和某一个端口,那么这个端口其他的服务都用不了了,无论当前主机是否有多个IP,因为0.0.0.0 地址比较特殊,代表任意地址,意味着绑定了 0.0.0.0 地址,相当于把主机上的所有 IP 地址都绑定了。

当 TCP 服务进程重启之后,总是碰到“Address in use”的报错信息,TCP 服务进程不能很快地重启,而是要过一会才能重启成功。

因为当我们重启 TCP 服务进程的时候,会经过四次挥手,而对于主动关闭方,会在 TIME_WAIT 这个状态里停留一段时间,这个时间大约为 2MSL。这个期间这个IP+端口的绑定依旧是被使用了的,所以需要等 TIME_WAIT 状态的连接结束后,重启 TCP 服务进程才能成功。这也可以通过对 socket 设置 SO_REUSEADDR 属性来解决。

客户端的端口可以重复使用吗?

客户端在执行 connect 函数的时候,会在内核里随机选择一个端口,然后向服务端发起 SYN 报文,然后与服务端进行三次握手。

所以,客户端的端口选择的发生在 connect 函数,内核在选择端口的时候,会从 net.ipv4.ip_local_port_range 这个内核参数指定的范围来选取一个端口作为客户端端口。

该参数的默认值是 32768 ~ 61000,意味着端口总可用的数量是 61000 - 32768 = 28232 个。

当客户端与服务端完成 TCP 连接建立后,我们可以通过 netstat 命令查看 TCP 连接。

bash
$ netstat -napt
协议  源ip地址:端口            目的ip地址:端口         状态
tcp  192.168.110.182.64992   117.147.199.51.443     ESTABLISHED

因为TCP 连接是由四元组(源 IP 地址,源端口,目的 IP 地址,目的端口)唯一确认的,那么只要四元组中其中一个元素发生了变化,那么就表示不同的 TCP 连接的。所以如果客户端已使用端口 64992 与服务端 A 建立了连接,那么客户端要与服务端 B 建立连接,还是可以使用端口 64992 的,因为内核是通过四元组信息来定位一个 TCP 连接的,并不会因为客户端的端口号相同,而导致连接冲突的问题。

比如下面这张图,有 2 个 TCP 连接,左边是客户端,右边是服务端,客户端使用了相同的端口 50004 与两个服务端建立了 TCP 连接。

仔细看,上面这两条 TCP 连接的四元组信息中的「目的 IP 地址」是不同的,一个是 180.101.49.12,另外一个是 180.101.49.11。

客户端可以用 bind 函数来指定 connect 时使用的端口,而不是随机选择,同样客户端多个进程通过 bind 如果绑定的多个相同的 IP 地址和端口,也会出现端口被占用的函数。

一般而言,客户端不建议使用 bind 函数,应该交由 connect 函数来选择端口会比较好,因为客户端的端口通常都没什么意义。

如果客户端都是与同一个服务器(目标地址和目标端口一样)建立连接,那么如果客户端 TIME_WAIT 状态的连接过多,当端口资源被耗尽,就无法与这个服务器再建立连接了。只要客户端连接的服务器不同,端口资源可以重复使用的。所以其实一个客户端可以建立的连接量是非常大的,只是会受限于主机资源。

可以通过打开 net.ipv4.tcp_tw_reuse 这个内核参数。当客户端调用 connect 函数时发现端口已经被相同的四元组占用,会判定当前连接是否处于 TIME_WAIT 状态,如果是且持续时间超过1秒,会重用这个连接,既可以正常使用了。

如果没有开,会选择下一个端口。

注意 net.ipv4.tcp_tw_reuse 是客户端使用 connect 才起作用,服务端用这个参数是没有效果的。


正在精进