← 返回标准版

身为前端,你不得不懂的一些HTTP知识(附赠3道面试题)

2020年02月04日 12:57

全文阅读大致3分钟,学习本文可以掌握以下知识:

  1. netcat、ss、lsof命令的使用
  2. tcp协议的三次握手和四次挥手
  3. udp协议的基本表现过程以及icmp报文发送的原因
  4. tcpdump、nc命令的使用
  5. 三道关于TCP/IP协议的面试题答案

1、从查看系统端口监听说起

在平时的开发中,出现listen EADDRINUSE: address already in use :::3000这种错误的频率很高,尤其在windows系统下,杀死个进程都杀不彻底。当遇到这种问题的时候,我们第一反应就是查看系统是哪个进程也在监听同样的端口。于是引出了我们要介绍的以下三个命令。

以下三个命令只在类UNI\系统上,系统之间的命令参数有一些细微差异,以系统提示为准,下面说的都是指在linux系统上*

1.1、netstat

netstat命令提供了一些关于网络连接的信息,可以用它来罗列所有监听的TCP端口或UDP端口,以及对应的套接字状态,如下:

netstat -tunlp

输出大致如下:

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。

1.2、ss

ss命令没有了netstat的一些特性,不过它暴露出更多的TCP状态并且它更加轻量快速。该命令的选项和netstat大致一样,所以很容易上手:

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))

1.3、lsof

lsof是一个强大的命令行工具,提供了进程打开的文件的一些信息。因为在Linux,一切皆文件。所以一个打开的套接字也可以认为是一个文件。

罗列所有监听的TCP端口:

lsof -nP -iTCP -sTCP: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连接处于什么阶段吗?

这个问题你自己心中有数的话,可以跳过下一小节

2、TCP状态的转移

下图是从wiki上引用的TCP状态转移图:

图片来自:wiki

2.2、面试题:为什么需要三次握手?

此题需要从两个点回答:

关于第一个原因可以参考下图(截图自RFC793的3.4节):

为什么是2MSL?因为TCP/IP协议规定了超过这个时间的数据包都是会被废弃掉的,也就是一个数据包在网络中存活的最大时间

2.4、面试题:为什么需要四次挥手?

答:第二次和第三次无法整合起来变成三次挥手是因为服务端接收到FIN报文之后,手上可能还有数据需要发送给客户端,所以ACK和FIN不能同时发送。

3、UDP协议探析

探究UDP我们使用netcat这个工具,我们先用netcat来新建一个UDP服务器:

nc -u -l 0.0.0.0 3000

然后使用nc来新建一个客户端:

nc -u -p 3001 localhost 3000

我们还需要使用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

因为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报文从服务端发送出来: