全文阅读大体3分钟,学习本文能够掌握如下知识:html
netcat
、ss
、lsof
命令的使用tcp
协议的三次握手和四次挥手udp
协议的基本表现过程以及icmp报文发送的缘由tcpdump
、nc
命令的使用在平时的开发中,出现listen EADDRINUSE: address already in use :::3000
这种错误的频率很高,尤为在windows系统下,杀死个进程都杀不完全。当遇到这种问题的时候,咱们第一反应就是查看系统是哪一个进程也在监听一样的端口。因而引出了咱们要介绍的如下三个命令。node
如下三个命令只在类UNI*系统上,系统之间的命令参数有一些细微差别,以系统提示为准,下面说的都是指在linux系统上linux
netstat命令提供了一些关于网络链接的信息,能够用它来罗列全部监听的TCP端口或UDP端口,以及对应的套接字状态,以下:nginx
netstat -tunlp
复制代码
-t
显示TCP端口-u
显示UDP端口-n
显示IP地址而不是域名-l
只显示正在监听的端口-p
显示监听端口的进程ID输出大体以下:面试
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:27017 0.0.0.0:* LISTEN 1889/mongod
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 786/nginx -g daemon
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 884/sshd
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 786/nginx -g daemon
tcp6 0 0 :::8080 :::* LISTEN 23087/node
tcp6 0 0 :::10000 :::* LISTEN 4988/node
tcp6 0 0 :::80 :::* LISTEN 786/nginx -g daemon
tcp6 0 0 :::8054 :::* LISTEN 11915/node
udp 0 0 172.16.179.237:123 0.0.0.0:* 750/ntpd
udp 0 0 127.0.0.1:123 0.0.0.0:* 750/ntpd
udp 0 0 0.0.0.0:123 0.0.0.0:* 750/ntpd
udp6 0 0 :::123 :::* 750/ntpd
复制代码
netstat命令现在已通过时了,由于有新的命令替换-ss
。windows
ss命令没有了netstat
的一些特性,不过它暴露出更多的TCP状态而且它更加轻量快速。该命令的选项和netstat
大体同样,因此很容易上手:bash
ss -tunlp
复制代码
输出大体以下:服务器
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
udp UNCONN 0 0 172.16.179.237:123 *:* users:(("ntpd",pid=750,fd=19))
udp UNCONN 0 0 127.0.0.1:123 *:* users:(("ntpd",pid=750,fd=18))
udp UNCONN 0 0 *:123 *:* users:(("ntpd",pid=750,fd=17))
udp UNCONN 0 0 :::123 :::* users:(("ntpd",pid=750,fd=16))
tcp LISTEN 0 128 *:27017 *:* users:(("mongod",pid=1889,fd=7))
tcp LISTEN 0 128 *:80 *:* users:(("nginx",pid=11173,fd=10),("nginx",pid=786,fd=10))
tcp LISTEN 0 128 *:22 *:* users:(("sshd",pid=884,fd=3))
tcp LISTEN 0 128 *:443 *:* users:(("nginx",pid=11173,fd=9),("nginx",pid=786,fd=9))
tcp LISTEN 0 128 :::8080 :::* users:(("node",pid=23087,fd=10))
tcp LISTEN 0 128 :::10000 :::* users:(("node",pid=4988,fd=10))
tcp LISTEN 0 128 :::80 :::* users:(("nginx",pid=11173,fd=11),("nginx",pid=786,fd=11))
tcp LISTEN 0 128 :::8054 :::* users:(("node",pid=11915,fd=12))
复制代码
lsof
是一个强大的命令行工具,提供了进程打开的文件的一些信息。由于在Linux,一切皆文件。因此一个打开的套接字也能够认为是一个文件。网络
罗列全部监听的TCP端口:ssh
lsof -nP -iTCP -sTCP:LISTEN
复制代码
-n
不要转换端口号为端口名称-p
不要解析域名,显示其IP地址-iTCP -sTCP:LISTEN
显示TCP状态为LISTEN的网络文件输出以下:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 786 root 9u IPv4 13574 0t0 TCP *:443 (LISTEN)
nginx 786 root 10u IPv4 13575 0t0 TCP *:80 (LISTEN)
nginx 786 root 11u IPv6 13576 0t0 TCP *:80 (LISTEN)
sshd 884 root 3u IPv4 14458 0t0 TCP *:22 (LISTEN)
mongod 1889 root 7u IPv4 21178 0t0 TCP *:27017 (LISTEN)
node 4988 root 10u IPv6 40123 0t0 TCP *:10000 (LISTEN)
nginx 11173 www-data 9u IPv4 13574 0t0 TCP *:443 (LISTEN)
nginx 11173 www-data 10u IPv4 13575 0t0 TCP *:80 (LISTEN)
nginx 11173 www-data 11u IPv6 13576 0t0 TCP *:80 (LISTEN)
node 11915 root 12u IPv6 7200966 0t0 TCP *:8054 (LISTEN)
node 23087 root 10u IPv6 5497007 0t0 TCP *:8080 (LISTEN)
复制代码
查找指定端口能够这样:lsof -nP -iTCP:8054 -sTCP:LISTEN
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 11915 root 12u IPv6 7200966 0t0 TCP *:8054 (LISTEN)
复制代码
好了,三个命令介绍到此为止。这个时候问一下你们一个问题:
上述过滤的状态都是LISTEN,那么TCP有多少种状态?状态与状态之间的变化是怎样的?你能从某个状态中就能推断出当前TCP链接处于什么阶段吗?
这个问题你本身心中有数的话,能够跳过下一小节
下图是从wiki上引用的TCP状态转移图:
看着有点复杂,咱们将其拆分红最热门的两个步骤:三次握手、四次挥手。后面附赠面试题答案哦~
CLOSED
变为SYN_SENT
,其中包含主机A的初始序列号seq(A)=x。(其中报文中同步标志位SYN=1,ACK=0,表示这是一个TCP链接请求数据报文;序号seq=x,代表传输数据时的第一个数据字节的序号是x);LISTEN
变为SYN_RECEIVED
,(其中确认报文段中,标识位SYN=1,ACK=1,表示这是一个TCP链接响应数据报文,并含服务端的初始序列号seq(B)=y,以及服务端对客户端初始序列号的确认号ack(B)=seq(A)+1=x+1)ESTABLISHED
,服务端收到这个ACK后,状态也转为ESTABLISHED
;此题须要从两个点回答:
RST
消息告诉服务端断掉这个链接,若是不是的话,就返回ACK创建链接。关于第一个缘由能够参考下图(截图自RFC793的3.4节):
FIN
标志位置为1的包,表示本身已经没有数据能够发送了,可是仍然能够接受数据。发送完毕后,客户端进入FIN_WAIT_1
状态FIN
包,发送一个确认包,代表本身接受到了客户端关闭链接的请求,但尚未准备好关闭链接。 发送完毕后,服务器端进入CLOSE_WAIT
状态,客户端接收到这个确认包以后,进入FIN_WAIT_2
状态,等待服务器端关闭链接。FIN
置为1。发送完毕后,服务器端进入LAST_ACK
状态,等待来自客户端的最后一个ACK。TIME_WAIT
状态,等待可能出现的要求重传的ACK
包。 服务器端接收到这个确认包以后,关闭链接,进入CLOSED
状态。 客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)以后,没有收到服务器端的ACK
,认为服务器端已经正常关闭链接,因而本身也关闭链接,进入CLOSED
状态。为何是2MSL?由于TCP/IP协议规定了超过这个时间的数据包都是会被废弃掉的,也就是一个数据包在网络中存活的最大时间
答:第二次和第三次没法整合起来变成三次挥手是由于服务端接收到FIN报文以后,手上可能还有数据须要发送给客户端,因此ACK和FIN不能同时发送。
探究UDP咱们使用netcat这个工具,咱们先用netcat
来新建一个UDP服务器:
nc -u -l 0.0.0.0 3000
复制代码
而后使用nc
来新建一个客户端:
nc -u -p 3001 localhost 3000
复制代码
-u
指定udp协议-l
指定监听的端口和ip-p
指定客户端的源端口咱们还须要使用tcpdump
工具来dump数据包,或者可使用wireshark
来抓包:
─$ sudo tcpdump -ni lo0 'udp port 3001 or icmp' 1 ↵
Password:
tcpdump: data link type PKTAP
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on pktap, link-type PKTAP (Apple DLT_PKTAP), capture size 262144 bytes
复制代码
-n
不用将Ip地址解析为域名-i
指定抓包的网卡,咱们这里指定抓的是回环口由于UDP是无链接的,因此这会看不到任何的数据包
可是真的彻底没有“链接”吗?其实并非彻底正确的,至少在客户端这边有这么一个链接存在,
咱们使用上面提到的命令lsof
:
╰─$ lsof -nP -iUDP | grep 3000
nc 60738 linxiaowu 3u IPv4 0x8ea59d14b13d38bf 0t0 UDP *:3000
nc 60744 linxiaowu 6u IPv4 0x8ea59d14b13d208f 0t0 UDP 127.0.0.1:3001->127.0.0.1:3000
复制代码
从上面能够看出,客户端已经有了链接的概念,服务端尚未意识有这么一个链接存在。接着咱们从客户端发送一条消息:hi
,此时咱们再使用lsof
能够看到服务端也有此链接了:
❯ lsof -nP -iUDP | grep 3000
nc 60738 linxiaowu 3u IPv4 0x8ea59d14b13d38bf 0t0 UDP 127.0.0.1:3000->127.0.0.1:3001
nc 60744 linxiaowu 6u IPv4 0x8ea59d14b13d208f 0t0 UDP 127.0.0.1:3001->127.0.0.1:3000
复制代码
因此从这里能够看到UDP的链接彻底创建是在第一个数据包发送以后。tcpdump
能够看到数据包:
17:18:17.419352 IP 127.0.0.1.3001 > 127.0.0.1.3000: UDP, length 3
复制代码
这个时候咱们关掉服务器,若是是TCP,那么会有一系列的协商报文发送出去,而UDP就不会,再看端口:
lsof -nP -iUDP | grep 3000
nc 60744 linxiaowu 6u IPv4 0x8ea59d14b13d208f 0t0 UDP 127.0.0.1:3001->127.0.0.1:3000
复制代码
客户端此时并不知道服务器down掉了,接着咱们从客户端发送消息hi?
,此时netcat
命令会自动退出,这个时候,它才知道链接断开了,而且咱们发现有个ICMP
报文从服务端发送出来:
ICMP
报文提示端口不可达,也就是服务端的端口关掉监听了。
根据TCP/IP协议的规定,若是对应的服务不可用,那么系统内核根据协议类型发送对应的响应报文,对于UDP应该发送一个“端口不可达”的ICMP报文,对于TCP应该发送一个TCP RST消息
因此UDP的链接断开会延迟到其中一方发送报文收到端口不可达的时候:
17:22:05.710012 IP 127.0.0.1.3001 > 127.0.0.1.3000: UDP, length 4
17:22:05.710047 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 udp port 3000 unreachable, length 36
复制代码
这个问题实际上是个伪命题。使用udp协议是之前旧有的规范定义的,如今的RFC是将TCP协议也一块儿写进去的。由于之前的网络带宽不高,使用UDP协议会比TCP协议的数据包小不少,而且之前的DNS包体通常都很小,不多超过512字节的,可是如今的DNS支持Ipv六、https,包体也变大了,这个时候若是仍是使用udp协议,很容易由于mtu之类的限制致使传输失败,由于tcp能够分包传输,因此对于大的包体,就大部分都是使用tcp协议。