阅读 288

一网打尽 TCP、IP协议那些事

网络分层

image.png 网络的分层大致如图所示,发送端每层通过加自己的包头,服务端每层拆包。

  1. 假设应用层是http协议传输数据
  2. 经过传输层,传输层的作用一般就是确定端口,加上TCP信息的包头
  3. 经过IP层,IP层就是确定对端的IP地址的,加上IP信息的包头
  4. 经过网络接口层,加上mac地址相关信息的包头
  5. 通过mac地址找到接收端,拆除以太网头
  6. 解析IP报文,找到对应的IP的接收方,拆除IP头
  7. 解析TCP报文,找到对应的服务(例如qq是80端口,微信是81端口),拆除TCP头
  8. 服务已找到,通过一开始的http协议解析数据,拿到数据,结束

tips:http协议主要是由请求头请求行请求主体构成

GET /user HTTP/1.1 (http1.1协议 GET方式 请求 /user接口)
HOST:www.juejin.cn (域名)
name=假装懂编程 (参数)
复制代码

分层的好处就是可以把复杂的网络请求,通过分层,抽象的展示出来。

TCP协议

定义:传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的可靠的基于字节流的传输层通信协议。
我们经常拿UDP协议来和TCP对比,UDP协议本身是不可靠的,因为我们发数据过去,不用等待对端回执。那么TCP为什么是可靠的?

image.png

TCP首部

先来看下TCP首部: image.png 除了最后一行的选项和填充是不确定的,可以看出一个TCP首部至少占用5*4=20个字节。

  • 源端口:发送端的端口,比如12345
  • 目标端口:接收端端口,比如80端口
  • 序号:上面说的TCP基于字节流的,所以每个TCP包有个序号
  • 确认号:假设发送端发送了序号是200的包,且数据包的长度是300,接收端收到包后,写入确认号500(200+300)
  • 数据偏移:TCP报文首部长度(20字节+不确定的选项长度)
  • 保留:为今后使用
  • URG:紧急发送,告诉报文段有紧急数据,不要按照原来的顺序,尽快发送
  • ACK:ACK=1时,确认号才有用,建立连接后,ACK始终为1
  • PSH:PSH=1时,表示当接收方收到PSH=1时,立即把数据响应出去,不要等缓存满了才推出去
  • RST:复位连接,出现了严重错误,必须重新建立连接
  • SYN:三次握手时,同步序列号用的
  • FIN:四次挥手,断开连接。

面向连接的

通俗来讲就是通信前我们需要握手来确定关系。
三次握手

image.png

  1. 发送端和接收端刚开始是close状态(假想状态)
  2. 第一次:发送端发送SYN包,请求握手,并发送初始序列号ISN(c),并且状态为SYN-SENT
  3. 当接收端收到SYN包时,状态由LISTEN转换成SYN-RCVD
  4. 第二次:接收端将自己的初始序列号ISN(s),回执的序列号ISN(c)+1,即SYN+ACK发送给发送端
  5. 发送端收到ACK并且确认了ACK的序列号为自己初始序列号加1,自己的状态为ESTABLISHED
  6. 第三次:发送端发送自己的下一次数据开始序列号(ISN(c)+1),并且回执ISN(s)+1
  7. 接收端收到ACK后,确认无误,状态为ESTABLISHED,双方连接建立完毕

几点疑问

  • ISN初始序列号并非是0,是由内部函数根据某些条件生成的随机的值,防止伪造ack包
  • SYN包不携带数据也要消耗一个序列号,我们发现最后一次握手后交换的序列号都为自己的ISN+1,一方面表示SYN消耗一个序列号,另一方面表示下一次我发送数据开始的序列号(比如:刚开始是1,连接完成后,我的下一次数据从2开始了)
  • ACK不需要消耗序列号,否则没完没了的确认了,无底洞
  • 凡是消耗序列号的包,一定要对端回复ACK,否则会一直重试

四次挥手

image.png 假设主动发起关闭方为A,被动方为B

  1. A发起FIN包后,自己进入FIN_WAIT1状态
  2. B收到FIN包后,立马回复ACK,同时自身状态位CLOSE_WAIT
  3. A收到B的ACK后,自身进入FIN_WAIT2
  4. B处理完当下的数据也一起发过去
  5. B发送FIN包,确认关闭,同时自身进入LAST_ACK
  6. A收到FIN包后,进入TIME_WAIT(2MSL)

基于字节流

为什么说TCP是基于字节流的?
假设现在你向socket里发送了1000字节的数据,但是往往因为发送窗口、接收窗口或者路径MTU的大小的影响,1000字节可以分为多种情况:

image.png 可能正好是500、500,也有可能是100、900。但这也反应了数据是分段传输的,既然是分段,那么每个段都有个序列号。TCP为每个字节都分配了序号,这些序号是单调递增的,这种就可以通过序号把数据串起来:

image.png

可靠的

为什么说TCP连接是可靠的,一种是由于数据拆包发了,那么就不能保证按顺序到达,那么可以根据序列号,重新拼接数据包,从而保证包的有序性。

滑动窗口

首先得理解一个概念,你发送一个数据块,数据块如果很小并一定立马被发送出去,因为有个发送缓冲区,当发送缓冲区累计到一定量时,操作系统才会真正的通过网卡把数据包发出去,同理对端读数据也是有个缓冲区,上层程序每次是从接收缓冲区里读数据的。那么当接收缓冲区由于上层处理程序的问题,没来得及及时取走,造成接收缓冲区积压,当接收缓冲区没有空间来接收数据时,这时得告诉发送端不要发数据了。
发送端的角度

image.png

  1. 已发送的数据包,并且得到对端的ACK
  2. 已发送但是还未得到对端的ACK
  3. 还没发送,但是还可以发送的数据块
  4. 还没发送,但是不能发送的数据块,对端已到零窗口

接收端的角度

image.png

  1. 已接收,已回复
  2. 未接收,可接收的数据块
  3. 不能接收的数据块

假设此时32-35已经回复,那么滑动窗口就得向后移动。

image.png

拥塞控制

先说概念:
RTT:数据包从发出到回来的时间
MSS:TCP最大段大小,在MTU(链路层允许传输的最大数据块)=1500的情况下,如果超过1500数据就要被分割,IP层会去分割,那么既然数据是从TCP过去的,那还不如TCP层直接分割好数据,MSS = 1500(MTU)- 20(IP 头大小)- 20(TCP 头大小)= 1460。 我们知道接收端有个滑动窗口来限制数据发送,那么发送端就不需要限制吗?当然也要限制,因为网络的问题,假设当前网络很差,发送端无脑的发数据,很多数据包无法到达对端,造成不必要的重传。
拥赛窗口(cwnd):发送端在未收到ack时还能发送的数据量大小,据块大小取决于接收窗口和发送窗口的最小值。cwmd=10的话,表示还能发送10个mss数据

  1. 慢启动

由于网络的未知性,一开始不能一下大量传输,但是由于效率也不能长时间传输较少数据,最好的办法就是慢启动。
刚开始cwnd比较小,假设为10,那么每经过一个RTT,cwnd翻倍,所以刚开始发送的数据量应该这样:
1RTT:cwnd=10 * 2=20
2RTT:cwnd=20 * 2=40
...
那么当然也不能无止境的翻倍下去。

  1. 拥赛避免

ssthresh称为慢启动阈值,当慢启动cwnd达到ssthresh时,就不会翻倍增加了。每经过一个RTT,cwnd就会增加1个mss段大小。假设ssthresh=40,那么
1RTT:cwnd=41
2RTT:cwnd=42

  1. 快速恢复

cwnd也不可能无限的加下去,遇到网络的不好的时候:ssthresh=cwnd/2,对应的cwnd也得变化,分两种情况:

  • 引发超时重传:cwnd = ssthresh+3
  • 收到3次重复ACK,快速重传 cwnd=1

重传机制与定时器

网络传输不一定总是靠谱的,因此总要留些后手。

ACK总是表示ACK前的数据包全部都收到

image.png 假设发了三个数据包101:200(ok)201:300(fail)301:400(ok),发现第二个数据发送失败,那么即使第三个数据包成功接收,回复的ack也是201,在第二个数据包重传成功后,才会回复301。

SACK快速重传

虽然上面发送端会重新传递数据包,但是也是得等定时器到达,这种情况在网络频繁交互的时候显得效率低下,于是就出现了sack,比如说发送端收到了三个一样的ack,就能意识到丢包了,当前ack的下一个数据包需要重传,这样就可以提升效率,不用苦苦的等定时器的到来

image.png

  • 建立连接定时器

image.png 在client发送SYN包的时候,如果因为某些问题SYN包,并不能按照预期的到达server端,于是在每次发送SYN包的时候,client就启动一个定时器,比如2s,当2s内没收到server端的ack,就会再次发送一个SYN包,下次的定时可能是3s,通过这种退避策略,最终如果不通的话,就建立连接失败。

  • 重传定时器

在发送数据包后,会启用一个定时器,在定时器时间内如果没收到对端的ack,就会重传

image.png

  • 延迟应答定时器

在接收端收到数据包后,并不急着回复ACK,会稍微等一下。如果此时数据包要回复的话,一起带过去,提升效率。 image.png

  • 坚持定时器

当接收端的接收窗口大小为0时,他就会在ACK的时候通知发送端告知我现在不能接收数据了,那么什么时候恢复呢?发送端不能一直干等下去,于是发送端就隔一段时间去询问。

image.png

  • 保活定时器

在TCP连接建立的时候,如果开启了SO_KEEPALIVE,那么保活定时器就会生效,意义在于,如果双方长时间没数据交互,也就不知道对方是死是活,如果对端已挂,那么这个连接就可以释放,没必要继续维护

image.png

  • FIN_WAIT2定时器

在四次挥手的过程中,发送端发送FIN包,接收端回复ACK后,发送端启动定时器,目的是防止接收端没有及时发送FIN包或者FIN包没有达到的情况,发送端不能一直等。如果超时,则主动方自己释放连接。 image.png

  • TIME_WAIT定时器

TIME_WAIT是主动关闭后最后进入的一种状态,TIME_WAIT是2MSL的,这里为什么是2MSL?MSL是报文最大的生命周期。

  1. 1MSL是万一最后一个ACK没到达对端,那么对端要重新发送一个FIN包
  2. 1MSL保证ACK本身能到达对端

最坏情况:发生ACK消耗一个1MSL刚好超时,然后对端重传FIN,FIN也是极限的消耗了一个MSL

image.png

文章分类
阅读
文章标签