前言
手上的一个项目终于快接近尾声了,鏖战了快3个月了,从各种意义上来讲,这是是目前经手难度最大的项目,没有之一(具体是哪个项目可以看看我之前的文章,哈哈),现在终于有空可以把一直在草稿箱里吃灰的这篇关于网络协议的文章写完发出来了~~
曾经有这么一个尴尬的瞬间,当时我在排查一个接口的通信问题。现象很直接,就是无论如何接口都无法连通。在我抓耳挠腮之时,同事直接灵魂直击:为何不直接抓包看看呢?
我:卧槽…不会…
同事:面试的时候网络协议说的头头是道,怎么一用就不会了呢 = =…
提这件事是因为,当因为通信问题使得系统出现故障时,进行网络抓包是一个非常有效的定位问题的手段,自然而然,我们需要去学习一些抓包的手段帮助我们排查问题。
回顾一下网络协议
当在浏览器中输入URL时,以python系统为例,一般会经历如下过程:
- 浏览器发送请求的时候,首先查询DNS(Domain Name System/域名系统)缓存
- 如果DNS缓存中没有,并且host文件中也没有的对应的DNS的话,就会向本地服务器发起一个DNS查询(DNS查询为一个递归的查询)
- 如果本地服务器没有查询到,则继续向上往匿名服务器和跟服务器进行查询
- 查询到之后,返回DNS对应的IP地址
- 得到IP地址后,浏览器调用socket函数发送TCP请求
- TCP通过三次握手,与服务器建立链接
- 建立链接后,发起HTTP请求
- HTTP请求打到Nginx(一般用作负载均衡及反向代理,系统没有配置Nginx的话建议配置一下…)
- Nginx将请求转发到WSGI服务器(uwsgi / gunicorn)
- WSGI服务器将请求再转发给web框架
- web框架处理请求,执行业务逻辑
- 将结果(response)通过TCP返回给用户
- TCP4次挥手,关闭链接
当某个出现问题时,必然会导致我们的系统不可访问。
尝试去ping/telnet一下
ping
如果页面直接显示浏览器无法打开时,我们可以尝试ping一下,例如:
ping www.baidu.com
其实拿百度做例子是存在一些复杂性的,比如我们ping的域名和输出的域名出现了不一致的现象,想要了解具体原因可以参考:尝试ping百度之后的思考
返回结果如下,我们在返回结果里面加上注释,简单分析一下ping的输出
# ping目标主机的域名和ip: 包的大小(通过设置 -s 展示带包头和不带包头)
PING www.wshifen.com (104.193.88.123): 56 data bytes
# icmp_seq: ping的序列,从1开始,如果数字不是安顺序递增则表示包丢了
# ttl: 一开始以为跟redis的ttl一样,上网查了之后发现其含义是被ping主机那里返回的报文,到了源地址之后,主机报文中预设的TTL减小到还剩下多少
# time: 响应时间,数值越小,联通速度越快
64 bytes from 104.193.88.123: icmp_seq=0 ttl=52 time=171.161 ms
64 bytes from 104.193.88.123: icmp_seq=1 ttl=52 time=155.408 ms
64 bytes from 104.193.88.123: icmp_seq=2 ttl=52 time=157.308 ms
64 bytes from 104.193.88.123: icmp_seq=3 ttl=52 time=154.383 ms
64 bytes from 104.193.88.123: icmp_seq=4 ttl=52 time=173.142 ms
# 这里就是一个包丢了
Request timeout for icmp_seq 5
64 bytes from 104.193.88.123: icmp_seq=6 ttl=52 time=222.386 ms
--- www.wshifen.com ping statistics ---
# 发出去的包数,返回的包数,丢包率
8 packets transmitted, 6 packets received, 25.0% packet loss
# 耗费时间
round-trip min/avg/max/stddev = 154.383/172.298/222.386/23.600 ms
ping命令常用于测试源网络主机到目标网络主机的连通性,同时评估网络连接质量。如果ping的是域名,还能够将域名解析为对应的ip。
OK,虽然有一点点丢包,但是网络基本没啥问题。
telnet
接着我们尝试去telnet一下,与ping不同的是,telnet能够用来探测指定的ip是否开放指定端口,一般服务器默认开放80端口用于http协议通信(https则为443),后端监听端口我们一般使用8000以上。
我们可以使用telnet
去检测一下系统服务开放的指定端口是否能够正常访问,基本语法如下
telnet ip port
举个🌰
telnet baidu.com 80
>>> Trying 39.156.69.79...
# 表示已经连接成功
>>> Connected to baidu.com.
>>> Escape character is '^]'.
# 根据提示 crtl + ] 推出
>>> Connection closed by foreign host.
如何判断哪一方的通信存在问题
TCP三次握手 / 四次挥手
这里就直接放两张图上来,TCP整个流程就不在去细述了(差点跑题)。
三次握手
四次挥手
文章最开始我们简单回顾了一下TCP网络协议,那么如何在网络出现问题时将这些知识学以致用呢?
下面就来实战一波~~
tcpdump直接抓包
实战开始之前,不得不提一下tcpdump
,用来做网络分析简直究极好用,顺便还能巩固一下自己对TCP协议的理解,基本上Unix系统都会默认安装上tcpdump,直接使用即可
我们还是直接用百度来进行测试,为了比较方便的看输出结果,这里我直接使用了百度的其中一个ip 119.75.216.20
先来看看启动监听命令
tcpdump -i en0 host 119.75.216.20 and port 80
简单解析一下命令中用到的一些参数:
-i
表示选择需要捕获的接口,我这里的en0
表示的是我机器上的网络接口,这个接口的详细信息可以通过ifconfig
命令进行查看
host
意思就很明显了,指明你要监听的域名或者ip
port
指明想要监听的端口,一般我们会想要抓取的是80端口上的流量,即HTTP请求
tcpdump
还有着更为复杂的配置及用法,可以满足复杂网络环境下的问题排查,这里就不一一提及了,可自行Google查看。
通过执行上述命令,我们就可以简单的查看端到端之间的网络状况了,那么直接开始执行:
>>> tcpdump -i en0 host 119.75.216.20 and port 80
# 这是一句提醒,我们可以在上述命令上加上 -v / -vv / -vvv 来获取更加详细的流量信息
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
# 监听的网卡为en0,其链路层基于以太网,抓包的大小限制为262144字节
listening on en0, link-type EN10MB (Ethernet), capture size 262144 bytes
...
这时,tcpdump
已经开始监听流量了(输出的这两行信息解释见注释),OK,我们打开浏览器,直接在窗口贴上 119.75.216.20
进行访问,然后耐心等待一下终端的输出…
(3 seconds later…)
好,日志打出来了,我们来看看都输出了啥,这里打印出来的日志比较多,我们根据需求直接来看看里面的一些重点信息
23:22:40.639341 IP 192.168.1.101.58177 > 119.75.216.20.http: Flags [S], seq 2761964636, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 695852145 ecr 0,sackOK,eol], length 0
23:22:40.697115 IP 119.75.216.20.http > 192.168.1.101.58177: Flags [S.], seq 2915547593, ack 2761964637, win 8192, options [mss 1452,nop,wscale 5,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,sackOK,eol], length 0
23:22:40.697182 IP 192.168.1.101.58177 > 119.75.216.20.http: Flags [.], ack 1, win 4096, length 0
这三行是最先打印出来的,这个信息就是大家最最熟悉的TCP三次握手的过程了(经典名场面,还不熟的赶紧回去温习一下文章上面TCP握手的流程图)
我们取其中一行分析一下
23:22:40.639341
表示的是抓包的时间,分别为 时 / 分 / 秒 / 微妙
IP 192.168.1.101.58177 > 119.75.216.20.http
中IP
表示这是一个网络层的IP包,后面紧接着的是源地址ip.源端口 > 目的地址ip.目的端口.协议类型
再往后则是TCP报文的标识Flags
, 通过Flags
我们可以判断当前TCP正在执行的动作,常见的Flags
通常有如下几种:
[S]
: SYN(开始连接)[.]
: 没有 Flag[P]
: PSH(推送数据)[F]
: FIN (结束连接)[R]
: RST(重置连接)
TCP连接完成之后自然就开始进行数据传输
23:22:40.697329 IP 192.168.1.101.58177 > 119.75.216.20.http: Flags [P.], seq 1:484, ack 1, win 4096, length 483: HTTP: GET / HTTP/1.1
23:22:40.753279 IP 119.75.216.20.http > 192.168.1.101.58177: Flags [.], ack 484, win 944, length 0
23:22:40.763540 IP 119.75.216.20.http > 192.168.1.101.58177: Flags [.], seq 1:1453, ack 484, win 944, length 1452: HTTP: HTTP/1.1 200 OK
23:22:40.764813 IP 119.75.216.20.http > 192.168.1.101.58177: Flags [.], seq 1453:2905, ack 484, win 944, length 1452: HTTP
23:22:40.764828 IP 119.75.216.20.http > 192.168.1.101.58177: Flags [P.], seq 2905:4097, ack 484, win 944, length 1192: HTTP
23:22:40.764830 IP 119.75.216.20.http > 192.168.1.101.58177: Flags [P.], seq 4097:4098, ack 484, win 944, length 1: HTTP
23:22:40.764857 IP 192.168.1.101.58177 > 119.75.216.20.http: Flags [.], ack 2905, win 4073, length 0
23:22:40.764858 IP 192.168.1.101.58177 > 119.75.216.20.http: Flags [.], ack 4097, win 4054, length 0
23:22:40.764891 IP 192.168.1.101.58177 > 119.75.216.20.http: Flags [.], ack 4098, win 4054, length 0
...
...
从传输打印的日志上,我们也可以再次直观的了解到,TCP是面向连接的、可靠的、基于字节流的传输层协议
当通信结束,整个页面加载完毕之后,TCP结束当前连接,这里我把日志的最后四行贴出来
23:22:44.708068 IP 192.168.1.101.58200 > 119.75.216.20.http: Flags [F.], seq 607, ack 320, win 4096, length 0
23:22:44.746525 IP 119.75.216.20.http > 192.168.1.101.58200: Flags [.], ack 608, win 948, length 0
23:22:44.746969 IP 119.75.216.20.http > 192.168.1.101.58200: Flags [F.], seq 320, ack 608, win 948, length 0
23:22:44.747012 IP 192.168.1.101.58200 > 119.75.216.20.http: Flags [.], ack 321, win 4096, length 0
这四行就是一次完整的TCP四次挥手(又是一个名场面,四次挥手还不熟的再去回顾一下文章上流程图),输出日志和三次握手差不多,这里就不再赘述了。
至此,一个基本且完整数据传输就被我们清晰的观察到了,可以得出结论,我们本机和百度之前的连接没有任何问题。
那么当你需要排查服务器的网络通信问题时,就可以按照上述方法进行验证,通常从TCP三次握手就可以看出是哪一方的通信存在问题。
上述方法基本可以定位到大多数网络环境问题,针对更加复杂的网络问题排查,我们则可以通过经典的tcpdump
+ Wireshark
组合对当前网络进行更加详尽的分析,有兴趣的可以自行学习一下。