我们知道,在计算机网络中,客户端和服务器端总是成对出现的。既然有 Web 服务器,就要有 Web 浏览器;既然有邮件服务器,就要有用于收发邮件的邮件客户端……没有服务器端,客户端发出的请求就得不到处理;反过来,没有客户端,再强大的服务器功能也没机会发挥作用。

在这个人人互联、万物互联的网络时代,可能只有去一些小型餐饮店吃饭时,点菜依然不需要客户端——因为饭菜有餐馆的伙计端。只是一个冷笑话。

客户端和服务器端就像一对情侣,两人之间总是你有来言我有去语。虽然一个 HTTP 请求未必总能收到满载希望的 200 OK 响应——就像“Do you marry me?”,不一定能得到一个甜美的“YES”答复;但当感情破裂、撕破脸的时刻,那句“你大爷的”肯定会引来对方一声“你大爷的”,就像 TCP 连接关闭四次挥手过程中的那两个 FIN 包,直接互送一个“再见”。

360_F_623010338_7gwfgduLTJgZhnqkL1vXd3xg8OAdUrNL

但是,客户端一定要连接服务器吗?有没有可能客户端能够自己连接自己?下面,我们放下传统的“客户端-服务器”模式,来一个特别的网络小实验。

要验证客户端能否自己连接自己,最简单的办法就是使用一些现成的网络命令,比如使用有“网络界的瑞士军刀”之称的 nc 命令:

$ nc -h
OpenBSD netcat (Debian patchlevel 1.218-4ubuntu1)
usage: nc [-46CDdFhklNnrStUuvZz] [-I length] [-i interval] [-M ttl]
...

$ nc -v -w 1 -p 56789 127.0.0.1 56789
Connection to 127.0.0.1 56789 port [tcp/*] succeeded!

看!“succeeded!”,这说明,客户端真的成功地自己连接上自己了!

不过,在苹果的 macOS 上这条命令会报错:nc: connectx to 127.0.0.1 port 56789 (tcp) failed: Invalid argument

我们刚刚使用 nc 命令进行了一次从源端口 56789 连接到目标端口 56789 的尝试,各参数的含义如下:

  • -v:显示详细信息,告诉我们连接是否成功
  • -w 1:设置超时时间为 1 秒,避免连接长时间挂起
  • -p 56789:指定源端口为 56789,通常 nc 在建立连接时会随机选择临时源端口,这里强制使用 56789 作为源端口。这是关键所在
  • 127.0.0.1:目标 IP 地址是本机
  • 最后的 56789:目标端口也是 56789

请注意 -p 56789 这个参数,如果没有该参数,nc随机选择一个临时端口(ephemeral port)作为源端口,而不是指定的、刚好等于目标端口的 56789。如果不带 -pnc 命令大概率会失败,因为网络中并不存在监听了 56789 端口的服务器。

$ nc -v -w 1  127.0.0.1 56789
nc: connect to 127.0.0.1 port 56789 (tcp) failed: Connection refused

也就是说,只有当源端口和目标端口相同时,客户端才能自己连接自己。而让源端口等于目标端口最直接的方法就是通过 -p 参数固定源端口,不让 nc 自动随机选择。

既然源端口是随机的临时端口,那么理论上只要反复不断尝试,就有可能随机到等于目标端口的数字作为源端口。总有一次,撞上了大运,客户端自己连上了自己。

$ while true; do nc -v -w 1 127.0.0.1 56789 && break; done
nc: connectx to 127.0.0.1 port 56789 (tcp) failed: Connection refused
nc: connectx to 127.0.0.1 port 56789 (tcp) failed: Connection refused
nc: connectx to 127.0.0.1 port 56789 (tcp) failed: Connection refused
...

不过,nc 命令似乎做了特殊处理,防止随机选择的源端口与目标端口相同。从 Wireshark 的抓包结果来看,每次请求源端口的数字都会增加 1,但唯独跳过了 56789 这个数字。

image-20250212161235458

nc 这样处理(随机不到目标端口)也是非常有道理的,因为 nc 常用于检查服务器的健康状况。对于连接异常的服务器,如果刚好因为随机选择的源端口等于目标端口,就会导致被误识别为连接正常,进而误导接下来的排查方向。


通常,我们可以将客户端和服务器端之间的网络连接想象成一根管道,铁的塑料的都行,入口在客户端,出口在服务器端(反过来也可以),数据就像水一样在里面流动,而且可以双向流动。

而当源端口号等于目标端口号时,客户端仿佛通过一个想象中的克莱因瓶自己连接上了自己,从“入口”倒入客户端的数据,又从作为出口的入口原样流出来了,就像这样:

$ echo "Hello World!" | nc -v -w 1 -p 56789 127.0.0.1 56789
Connection to 127.0.0.1 56789 port [tcp/*] succeeded!
Hello World!


da_miao_zi
1 声望0 粉丝

软件工程师、技术图书译者。译有《图解云计算架构》《图解量子计算机》《计算机是怎样跑起来的》《自制搜索引擎》等。