网络分层
网络的分层大致如图所示,发送端每层通过加自己的包头,服务端每层拆包。
- 假设应用层是http协议传输数据
- 经过传输层,传输层的作用一般就是确定端口,加上TCP信息的包头
- 经过IP层,IP层就是确定对端的IP地址的,加上IP信息的包头
- 经过网络接口层,加上mac地址相关信息的包头
- 通过mac地址找到接收端,拆除以太网头
- 解析IP报文,找到对应的IP的接收方,拆除IP头
- 解析TCP报文,找到对应的服务(例如qq是80端口,微信是81端口),拆除TCP头
- 服务已找到,通过一开始的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为什么是可靠的?
TCP首部
先来看下TCP首部:
除了最后一行的选项和填充是不确定的,可以看出一个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:四次挥手,断开连接。
面向连接的
通俗来讲就是通信前我们需要握手来确定关系。
三次握手:
- 发送端和接收端刚开始是close状态(假想状态)
- 第一次:发送端发送SYN包,请求握手,并发送初始序列号ISN(c),并且状态为SYN-SENT
- 当接收端收到SYN包时,状态由LISTEN转换成SYN-RCVD
- 第二次:接收端将自己的初始序列号ISN(s),回执的序列号ISN(c)+1,即SYN+ACK发送给发送端
- 发送端收到ACK并且确认了ACK的序列号为自己初始序列号加1,自己的状态为ESTABLISHED
- 第三次:发送端发送自己的下一次数据开始序列号(ISN(c)+1),并且回执ISN(s)+1
- 接收端收到ACK后,确认无误,状态为ESTABLISHED,双方连接建立完毕
几点疑问 :
- ISN初始序列号并非是0,是由内部函数根据某些条件生成的随机的值,防止伪造ack包
- SYN包不携带数据也要消耗一个序列号,我们发现最后一次握手后交换的序列号都为自己的ISN+1,一方面表示SYN消耗一个序列号,另一方面表示下一次我发送数据开始的序列号(比如:刚开始是1,连接完成后,我的下一次数据从2开始了)
- ACK不需要消耗序列号,否则没完没了的确认了,无底洞
- 凡是消耗序列号的包,一定要对端回复ACK,否则会一直重试
四次挥手:
假设主动发起关闭方为A,被动方为B
- A发起FIN包后,自己进入FIN_WAIT1状态
- B收到FIN包后,立马回复ACK,同时自身状态位CLOSE_WAIT
- A收到B的ACK后,自身进入FIN_WAIT2
- B处理完当下的数据也一起发过去
- B发送FIN包,确认关闭,同时自身进入LAST_ACK
- A收到FIN包后,进入TIME_WAIT(2MSL)
基于字节流
为什么说TCP是基于字节流的?
假设现在你向socket里发送了1000字节的数据,但是往往因为发送窗口、接收窗口或者路径MTU的大小的影响,1000字节可以分为多种情况:
可能正好是500、500,也有可能是100、900。但这也反应了数据是分段传输的,既然是分段,那么每个段都有个序列号。TCP为每个字节都分配了序号,这些序号是单调递增的,这种就可以通过序号把数据串起来:
可靠的
为什么说TCP连接是可靠的,一种是由于数据拆包发了,那么就不能保证按顺序到达,那么可以根据序列号,重新拼接数据包,从而保证包的有序性。
滑动窗口
首先得理解一个概念,你发送一个数据块,数据块如果很小并一定立马被发送出去,因为有个发送缓冲区,当发送缓冲区累计到一定量时,操作系统才会真正的通过网卡把数据包发出去,同理对端读数据也是有个缓冲区,上层程序每次是从接收缓冲区里读数据的。那么当接收缓冲区由于上层处理程序的问题,没来得及及时取走,造成接收缓冲区积压,当接收缓冲区没有空间来接收数据时,这时得告诉发送端不要发数据了。
发送端的角度:
- 已发送的数据包,并且得到对端的ACK
- 已发送但是还未得到对端的ACK
- 还没发送,但是还可以发送的数据块
- 还没发送,但是不能发送的数据块,对端已到零窗口
接收端的角度:
- 已接收,已回复
- 未接收,可接收的数据块
- 不能接收的数据块
假设此时32-35已经回复,那么滑动窗口就得向后移动。
拥塞控制
先说概念:
RTT:数据包从发出到回来的时间
MSS:TCP最大段大小,在MTU(链路层允许传输的最大数据块)=1500的情况下,如果超过1500数据就要被分割,IP层会去分割,那么既然数据是从TCP过去的,那还不如TCP层直接分割好数据,MSS = 1500(MTU)- 20(IP 头大小)- 20(TCP 头大小)= 1460。
我们知道接收端有个滑动窗口来限制数据发送,那么发送端就不需要限制吗?当然也要限制,因为网络的问题,假设当前网络很差,发送端无脑的发数据,很多数据包无法到达对端,造成不必要的重传。
拥赛窗口(cwnd):发送端在未收到ack时还能发送的数据量大小,据块大小取决于接收窗口和发送窗口的最小值。cwmd=10的话,表示还能发送10个mss数据
-
慢启动 由于网络的未知性,一开始不能一下大量传输,但是由于效率也不能长时间传输较少数据,最好的办法就是慢启动。
刚开始cwnd比较小,假设为10,那么每经过一个RTT,cwnd翻倍,所以刚开始发送的数据量应该这样:
1RTT:cwnd=10 * 2=20
2RTT:cwnd=20 * 2=40
...
那么当然也不能无止境的翻倍下去。 -
拥赛避免 ssthresh称为慢启动阈值,当慢启动cwnd达到ssthresh时,就不会翻倍增加了。每经过一个RTT,cwnd就会增加1个mss段大小。假设ssthresh=40,那么
1RTT:cwnd=41
2RTT:cwnd=42 -
快速恢复 cwnd也不可能无限的加下去,遇到网络的不好的时候:ssthresh=cwnd/2,对应的cwnd也得变化,分两种情况:
- 引发超时重传:cwnd = ssthresh+3
- 收到3次重复ACK,快速重传 cwnd=1
重传机制与定时器
网络传输不一定总是靠谱的,因此总要留些后手。
ACK总是表示ACK前的数据包全部都收到
假设发了三个数据包101:200(ok)201:300(fail)301:400(ok),发现第二个数据发送失败,那么即使第三个数据包成功接收,回复的ack也是201,在第二个数据包重传成功后,才会回复301。
SACK快速重传 虽然上面发送端会重新传递数据包,但是也是得等定时器到达,这种情况在网络频繁交互的时候显得效率低下,于是就出现了sack,比如说发送端收到了三个一样的ack,就能意识到丢包了,当前ack的下一个数据包需要重传,这样就可以提升效率,不用苦苦的等定时器的到来
- 建立连接定时器
在client发送SYN包的时候,如果因为某些问题SYN包,并不能按照预期的到达server端,于是在每次发送SYN包的时候,client就启动一个定时器,比如2s,当2s内没收到server端的ack,就会再次发送一个SYN包,下次的定时可能是3s,通过这种退避策略,最终如果不通的话,就建立连接失败。
- 重传定时器 在发送数据包后,会启用一个定时器,在定时器时间内如果没收到对端的ack,就会重传
-
延迟应答定时器 在接收端收到数据包后,并不急着回复ACK,会稍微等一下。如果此时数据包要回复的话,一起带过去,提升效率。
-
坚持定时器 当接收端的接收窗口大小为0时,他就会在ACK的时候通知发送端告知我现在不能接收数据了,那么什么时候恢复呢?发送端不能一直干等下去,于是发送端就隔一段时间去询问。
- 保活定时器 在TCP连接建立的时候,如果开启了SO_KEEPALIVE,那么保活定时器就会生效,意义在于,如果双方长时间没数据交互,也就不知道对方是死是活,如果对端已挂,那么这个连接就可以释放,没必要继续维护
-
FIN_WAIT2定时器 在四次挥手的过程中,发送端发送FIN包,接收端回复ACK后,发送端启动定时器,目的是防止接收端没有及时发送FIN包或者FIN包没有达到的情况,发送端不能一直等。如果超时,则主动方自己释放连接。
-
TIME_WAIT定时器 TIME_WAIT是主动关闭后最后进入的一种状态,TIME_WAIT是2MSL的,这里为什么是2MSL?MSL是报文最大的生命周期。
- 1MSL是万一最后一个ACK没到达对端,那么对端要重新发送一个FIN包
- 1MSL保证ACK本身能到达对端 最坏情况:发生ACK消耗一个1MSL刚好超时,然后对端重传FIN,FIN也是极限的消耗了一个MSL