本文借助wireshark抓包详细的讲解常用的网络协议。涉及的主要协议包括但不限于http协议
、tcp协议
、ip协议
。为了表述的准确性,严重的参考了参考了谢希仁的《计算机网络》这本教程。
一、http请求抓包
通过如下命令请求一次百度的首页。
curl -v -i www.baidu.com
通过wireshark抓包如下:
image.png其中
红色框:tcp三次握手
蓝色框:http请求与应答
绿色框:tcp四次挥手
本文会以上文的数据包来展开分析
在正式介绍之前,我们现在一张图,请求是如何一步一步封装的。以http请求为例
(图一)http请求在各个五层网络体系中的封装情况
在数据链路层中有一个MTU的东西,表示上层payload最大的大小(单位byte) 有了这个东西就意味着如果上层的报文太大,必须要分割。在ip协议里叫分片
;在tcp协议里叫分段
,同时会涉及到tcp建立连接时(三次握手中的前两次握手),客户端和服务端会协商tcp header中MSS(最大数据报文段长度)字段。具体的我们后面会说道。
二、IP协议(RFC 791)
IP数据报的格式如下图二
(图二)ip数据报格式
Ip数据报分为首部和数据部分两个部分。其中IP首部又分为固定首部
+可变部分
,固定部分长度固定为20个字节。
wireshark抓包如下:
2.1 IP header 固定部分
- 版本(4位):IP协议的版本,目前的IP协议版本号为4,下一代IP协议版本号为6。
- 首部长度(4位):IP报头的长度,注意
单位为4个字节
。固定部分的长度(20字节)和可变部分的长度之和。共占4位。最大为1111,即10进制的15,代表IP报头的最大长度可以为15*4=60字节,除去固定部分的长度20字节,可变部分的长度最大为40字节。 - 区分服务(8位): 用来获得更好的服务,现在基本不用,可以忽略
- 总长度(16位):IP报文的总长度。报头的长度和数据部分的长度之和。所以一个IP报文的的最大长度为65535个字节。受MTU限制,最大只能为1500字节
- 标识(16位):唯一的标识主机发送的每一分数据报。通常每发送一个报文,它的值加一。当IP报文长度超过传输网络的MTU(最大传输单元)时必须分片,这个标识字段的值被复制到所有数据分片的标识字段中,使得这些分片在达到最终目的地时可以依照标识字段的内容重新组成原先的数据。
- 标志(3位):共3位。R、DF、MF三位。目前只有后两位有效,DF位:为1表示不分片,为0表示分片。MF:为1表示“更多的片”,为0表示这是最后一片。
- 片位移(13位):
单位为8字节
,指当前分片在原数据报(分片前的数据报)中相对于用户数据字段的偏移量,即在原数据报中的相对位置。(需要再乘以8) - 生存时间(8位):TTL(Time to Live)。该字段表明当前报文还能生存多久,现在指跳数。每经过一个网关,TTL的值自动减1,当生存时间为0时,报文将被认为目的主机不可到达而丢弃。TTL 字段是由发送端初始设置一个 8 bit字段.推荐的初始值由分配数字 RFC 指定,当前值为 64。发送 ICMP 回显应答时经常把 TTL 设为最大值 255。
- 协议(8位):指出IP报文携带的数据使用的是那种协议,以便目的主机的IP层能知道要将数据报上交到哪个进程(不同的协议有专门不同的进程处理)。和端口号类似,此处采用协议号,TCP的协议号为6,UDP的协议号为17。ICMP的协议号为1,IGMP的协议号为2.
- 首部校验和(16位):用于检验IP
报文头部
(不包含数据部分)在传播的过程中是否出错,检查IP报头的完整性。 - 源IP地址(32位):源ip地址
- 目的IP地址(32位):目标ip地址
2.2 IP header 不固定部分
就因为ip header存在不固定部分,所以在固定部分才需要字段首部长度
。ip header中不固定部分主要是用来增加ip数据报的功能的(1-40字节)。但是该部分很少使用,所以IPv6中没有该部分。所以可以忽略这部分的内容。
2.3 关于IP数据报分片
前面有提到过,在数据链路层有MTU限制即IP数据报的最大报文长度为1500字节,那么当ip数据长度超过1500时候,就需要分片
。举例如下:
假设一个数据报总长度为3820字节,其中数据部分为3800字节(使用固定首都20字节,无可变部分)。显然3820超过MTU,需要分片。假设分片长度不超1420字节,出去20字节首都,那每个分片数据部分最长为1400。所以需要将数据部分分为三个数据报片(1400、1400、1000)。那么分片后,如何重新组装回一个完整的IP数据报呢?就需要上面提到的标识
和片位移
两个字段。分成3个数据报片的标识字段是一样的。再加上片位移字段就能计算出该分片在原数据报中的位置。上面三个分片的片位移分别为(0/8=0; 1400/8=175; 2800/8=350)。注意片位移单位为8字节
三、TCP协议(RFC 793)
TCP协议是比较复杂的,要是搞明白TCP协议,就需要回答三个问题。(1)TCP如何保证可靠性传输
;(2)TCP如何做流量控制
;(3)TCP如何做拥塞控制
。我们先从简单的TCP报文段格式开始介绍。
TCP报文段的格式如下图三
3.1 TCP header固定首部
- 源端口和目的端口(16位):见名知意
- 序号(32位):序号范围, 当序号到达最大值后, 下一个序号就是0。TCP是面向
字节流
的,在一个TCP链接中传送的每一个字节都是按循序编号。整个要传送的字节流的起始序号必须在建立连接的时候设置。首部中的序号字段值指的本报文段所发送数据的第一个字节的序号。 - 确认号(32位):期望收到对方下一个报文段的第一个数据字节的序号。比如B收到A发过来的一个报文段,其序号字段值为500,数据长度为200字节,即序501-700(注意是A的数据长度,不包括TCP头的长度)。那B发给A的报文段中确认号为701。这表明B已经正确收到了A发送的序号到700的数据。因此B希望收到A下一个数据序号为701。
- 数据偏移(4位):指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。
- 保留(6位):保留今后使用,但目前全部为0
- 紧急URG(1位):当URG为1时,表明该报文段有紧急数据。需要优先传送,而不要按原来的排队顺序来传送。
- 确认ACK(1位):仅当ACK位为1时,
确认号
字段才有效。 - 推送PSH(1位):当两个应用进程进行交互式通信时候,有时候在一端的应用进程信息网当键入一个命令后立即就能收到对方的响应。在这种情况下,TCP就可以使用推送(push)操作。这时,发送方TCP把PSH置为1,并立即创建一个报文段发送过去。接收方收到PSH=1的报文段,就尽快的交付给结束应用进程,而不是等整个缓存都填满了以后再向上交付。
- 复位RST(1位):当RST=1时,表明TCP连接中出现严重差错(如主机崩溃或者其他原因),必须释放连接,然后再重新建立连接。RST=1时还用来拒绝一个非法的报文或者拒绝打开一个连接。
- 同步SYN(1位):建立连接时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文段。若对方同意建立连接,则在响应报文段中使SYN=1和ACK=1.
- 终止FIN(1位):用来释放连接。当FIN=1时,表明此报文段的发送方的数据已经发送完毕,并要求释放连接、
- 窗口(16位):指的接收窗口。窗口指告诉对方:从本报文段的首部的确认号算起,接收方允许对方发送的数据量。之所以有这个限制,是因为接收方的数据缓存空间是有限的。总之,窗口指作为接收方让发送方设置其发送窗口的依据
- 检验和(16位):检验和字段的检验范围包括首部和数据两个部分。IP首部中的检验和只检验头部
- 紧急指针(16位):当URG=1时才有效。指出报文段中的紧急数据的字节数。
3.2 TCP header可变部分
长度可变,最长可达到40个字节。当没有使用选项时,TCP首部首部长度是20个字节。
TCP最初只规定了一种选项,即报文段最大长度MSS(maximum Segment Size)。MSS: 指数据部分的最大长度,数据字段加上TCP的首部才是整个TCP报文段。还记得文章开头部分提到MTU吗?因为数据链路层有MTU的限制。就会导致在IP层,如果数据报对较大,就会分片。那IP分片就分片呗,为什么TCP需要MSS用来现在最大报文长度呢?因为TCP层要保证数据的可靠性,如果数据丢失,TCP层会重新传送数据。如果发送方数据在IP层分片,比如分成3片,接收方需要在IP层把3个分片组装好再交付TCP层。如果TCP层发现数据少了一个分片,那么3个分片都重传,浪费了网络的资源。但是如果在TCP层分片,只要重传丢失的那个分组就好了,这就是TCP建立连接时候需要协商MSS字段值的原因
下面我们结合抓包看看MSS字段对数据传输的影响:
图中Length表示物理层最终发送出的frame的数据大小
- 绿色框表示TCP建立连接(3次握手):请求建立连接请求中的MSS=1460。确认建立连接中MSS=1440。那么在以后的TCP连接中的报文段数据部分的最大长度就是min{1460, 1440}=1440 Bytes。
- 蓝色框表HTTP请求与响应:具体步骤已在图中描述,重点看标红的部分,wireshark出现[TCP sgment of a reassembled PDU]的描述,表示这里出现分段了。由于百度的http响应报文长度超过MTU,所以造成了这里的数据段分段了。详细看下这条记录
TCP数据部分的长度确实是1440 bytes,与上面的MSS分析一致。
这里还有一个问题,HTTP的响应报文分成了3个报文段。那么接受方是这么知道这3个报文段表示一个完整的信息呢?3个报文段是顺序又是怎样的?
我先HTTP请求这条记录:
其中,发送方Seq=1, Len=77。那么响应放下次发送过来的数据段中
确认号
字段的值为1+77 = 78;发送方的确认号
字段=1。那么响应方下次发送来的序号
字段值就是1。 下面开始检验。响应报文段一个分段如下: 第一个分段
响应报文段二个分段如下:
第二个分段
响应报文段三个分段如下:
第三个分段
我们看到,三个分段的确认号字段值都是78, 序号字段分别为1(ACK报文段如果不携带数据,则不消耗序列号,所以一下个序列号任然为1),1,1441。所以当发现接受到是数据段中确认号字段数值一样,就表明这是一个大段数据的分段。序号的大小表明循序。所以回到我们刚刚的问题,就知道数据段是被分成了3个段,且根据序号可以再组合会原始的数据大段。
3.3 TCP建立连接(3次握手)
TCP三次握手示意图如下
实际上三次握手指的建立连接需要发送三个数据包。TCP的三次握手的实际意义是确认双方建立连接的初始序号。
这里有一个问题请大家思考,如果只是为了确认初始序号。那么两次握手就够了,第一次客户端将自已的初始序列号告诉服务端,第二次服务端确认客户端的序列号,并告诉客户端自已的序号。那么就可以建立连接了。那么为什么还需要第三次的确认呢?
主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。
如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端是知道自已并没有发出建立连接的请求,所以不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
wireshark抓包分析如下:
- 第一次握手(SYN=1)
其中序号seq=0,wireshark显示的相对序号,真实的序号如图中箭头所示8f 6c c8 9c
- 第二次握手(SYN=1,ACK=1)
- 第三次握手(ACK=1)
注意当ACK报文段中数据部分长度为0时,是不消耗序号的。即下一次的序号和上一次的序号是一样的
3.4 TCP释放连接(4次挥手)
TCP四次挥手示意图如下
四次挥手
示意图已经描述的非常清楚了,不再赘述。这里重点说一下为什么客户端发送完最后一个ACK报文段后要等2MSL(TIME_WAIT timer, 时间等待计时器)。一个MSL等于2分钟。但是现在网络条件比较好,2分钟通常太长了,所以允许TCP不同实现使用更小的MSL值。关于为什么要等2MSL,原因有两个
- 保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
- 防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
如果已经建立了连接,但是客户端挂了,这么办?
这种情况如果服务端一直保持这个连接,那么白白浪费了网络资源,所以TCP设置有一个保活计时器(keepalive timer)。注意tcp的keepalive和http的keepalive是不同的。TCP的保活计时器,当服务端两个小时没有收到客户端的数据,就会发送一个探测报文段,以后每隔75秒发送一次。如果一连发送10个探测报文段,客户端都没有响应,服务端就会认为客户端出了故障,接着就会关闭这个连接。
3.5 TCP的可靠性、流量控制、拥塞控制
TCP的可靠性和流量控制都是通过窗口滑动机制实现的。
实际上没有窗口滑动TCP协议也是能够保证数据的可靠性的。TCP报文段中有确认号字段,如果没有收到确认,那么发送方就认为数据丢失,一直重传数据知道收到确认。但是这种一问一答的方式效率很低。所以就有了窗口滑动机制。还记得TCP头中的窗口
字段吗?就是用来实现窗口滑动机制的。只要在窗口允许的范围内,就可以一直发送数据。
窗口滑动机制作用:防止发送数据发送太快,接收方来不及处理
关于拥塞控制,注意与流量控制的区别
流量控制是作用于接收者的,它是控制发送者的发送速度从而使接收者来得及接收,防止丢失数据包的。
拥塞控制 拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况
在拥塞控制中,发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。
发送窗口取拥塞窗口和接收端窗口的最小值,避免发送接收端窗口还大的数据。
拥塞控制使用了两个重要的算法: 慢启动算法, 拥塞避免算法。
- 慢启动算法的思路是,不要一开始就发送大量的数据,先试探一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小。慢算法中,每个传输轮次后将 cwnd 加倍。
- 拥塞避免算法也是逐渐的增大 cwnd 的大小,只是采用的是线性增长 而不是像慢启动算法那样的指数增长。
四、HTTP协议(RFC 1495 2068 7540)
HTTP协议报文分为请求报文和响应报文。
4.1 请求报文
请求报文4.2 响应报文
响应报文HTTP请求报文和响应报文都是由三个部分组成。两种报文格式的区别就是开始行不同。
- 开始行:用于区分是请求报文还是响应报文。在请求报文中开始行叫请求行,在响应报文中叫状态行。
- 首部行:用来说明浏览器、服务器或者报文主体的一些信息。注意格式为key:value
- 实体主体: 在请求报文中一般不用这个字段,在响应报文中也可能没有这个字段。
请求方法:
HTTP协议的请求方法有GET、POST(更新、创建)、HEAD、PUT(创建)、DELETE、OPTIONS、TRACE、CONNECT。
响应状态码:
状态代码由服务器发出,以响应客户端对服务器的请求。
1xx(信息):收到请求,继续处理
2xx(成功):请求已成功接收,理解和接受
3xx(重定向):需要采取进一步措施才能完成请求
4xx(客户端错误):请求包含错误的语法或无法满足
5xx(服务器错误):服务器无法满足明显有效的请求
参考文献
- [计算机网络]第五版,谢希仁
- rfc 文档查询
网友评论