$ lsof
memcached 15844 root 28u IPv6 113604 TCP *:11051 (LISTEN)
memcached 15844 root 29u IPv4 113605 TCP *:11051 (LISTEN)
memcached 15844 root 30u IPv6 113609 UDP *:11051
memcached 15844 root 31u IPv4 113610 UDP *:11051
是按 IP、按协议还是按其他维度?
答案1
同一个端口号可以一次用于 TCP,一次用于 UDP,并且它们在 IPv4 和 IPv6 上均存在。
TCP 和 UDP 端口号是完全不同的数字空间,它们恰好是成对分配的,并且需要两者的应用程序通常在每个端口上使用相同的数字。
IPv4 和 IPv6 是有些不同的协议;原则上,IPv6 应用程序可以接受 IPv4 连接,但通常认为拥有两个独立的套接字是一种好的做法。
答案2
netstat
或输出中的每个条目lsof -i
称为插座。
每个插座都是由以下组合而成的:
- 协议系列(例如 IPv4 或 IPv6)
- 协议(例如 TCP 或 UDP)
- 本地地址
- 本地端口
- 远程地址
- 远程端口
这种变量组合称为连接元组。
考虑ssh
。
如果您的机器正在运行,您将在输出中看到在端口上运行的服务器的sshd
条目,该服务器正在监听并等待新的连接。netstat
22
# netstat -tnl | grep 22
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
但您还将看到每个活动连接的条目。
# netstat -tn
tcp 0 0 192.168.x.y:22 192.168.x.a:ppppp ESTABLISHED
tcp 0 0 192.168.x.y:22 192.168.x.b:qqqqq ESTABLISHED
因此上述输出表明端口22
在同一台机器上被使用了3次。
在这种情况下,每个连接都是唯一的,因为每个连接都有不同的远程地址或端口。请注意,即使其中只有一个不同,它仍然使套接字唯一。
但它也可以通过本地地址或端口来唯一。
例如,你可以使用基于 Apache IP 的虚拟主机根据主机名或 IP 地址显示不同的站点:
# netstat -tnlp | grep 80
tcp 0 0 1.2.3.4:80 0.0.0.0:* LISTEN 9876/apache2
tcp 0 0 1.2.3.5:80 0.0.0.0:* LISTEN 9876/apache2
这里有两个套接字在监听端口 80。
在这个例子中,如果流量的目的地址是 1.2.3.4,它将被传送到第一个套接字;如果目的是 1.2.3.5,它将转到第二个套接字。
但是服务器必须明确执行此操作。服务器默认将本地地址设置为0.0.0.0
,也写作*
,这意味着该端口上只有一个监听套接字,它将接受该端口上的所有传入流量。
正如您所发现的,它对于每个协议来说也是唯一的。
例如,BIND DNS 服务器同时使用 TCP 和 UDP:
# netstat -nlp | grep named
tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN 567/named
tcp 0 0 192.168.x.y:53 0.0.0.0:* LISTEN 567/named
tcp 0 0 127.0.0.1:953 0.0.0.0:* LISTEN 567/named
udp 0 0 127.0.0.1:53 0.0.0.0:* 567/named
udp 0 0 192.168.x.y:53 0.0.0.0:* 567/named
每个协议系列的每个连接也是唯一的,这意味着 IPv4 和 IPv6 可以不同。
这取决于程序员要求的是仅 IPv4 套接字、仅 IPv6 套接字还是组合 IPv4/IPv6 套接字。
在 Linux 上,IPv6 套接字也会接受 IPv4 流量,除非程序员告诉它不要这样做,因此它只会在输出中出现一次netstat
。
$ port=55555
$ nc -6 -l $port || echo "Error listening on port $port" &
$ netstat -tnl | grep $port
tcp6 0 0 :::55555 :::* LISTEN
$ nc -z 127.0.0.1 $port && echo "Port $port is accepting IPv4 connections"
Port 55555 is accepting IPv4 connections
但memcached 源代码显示它明确要求仅 IPv6 套接字,这就是为什么 IPv4 和 IPv6 在您的示例中分别出现的原因。
error = setsockopt(sfd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &flags, sizeof(flags));