传输层基本概念:
传输层负责端与端之间的数据传,主要有两大知识点:TCP和UDP
五元组
在TCP/IP协议中, 用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过netstat -n查看);
端口号范围
0 - 1023: 知名端⼝口号, HTTP, FTP, SSH等这些广为使⽤用的应⽤用层协议, 他们的端⼝口号都是固定的
1024 - 65535: 操作系统动态分配的端⼝口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的。
UDP(无连接不可靠面向数据报)
UDP协议格式
源端口、目的端口、数据报长度、校验和
检验和:二进制反码求和
最大长度
UDP数据包最大长度64K(包含报头),如果用户发送的长度大于64K-8就会报错,因为UDP在传输层不会自动进行数据分段,这就意味着如果传输的数据大于64K,就需要用户在应用层就进行数据的分段,但是因为传输层UDP并不保证数据的有序到达,就需要用户在应用层进行包序的管理
UDP缓冲区
UDP没有真正意义上的发送缓冲区,调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作;
UDP具有接收缓冲区,但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃;
UDP粘包问题
面向数据报不会产生粘包问题,因为UDP数据包中定义了数据包的长度
基于UDP的应用层协议
- NFS: 网络文件系统
- TFTP: 简单文件传输协议
- DHCP: 动态主机配置协议
- BOOTP: 启动协议(用于无盘设备启动)
- DNS:域名解析协议
TCP(传输控制协议)
TCP协议段格式
- 源/目的端口:表示数据从哪个进程来,要到哪个进程中
- 32位序号/32位确认序号:用来标识数据的顺序
- 4位TCP报文长度:表示该TCP头部有多少个32位bit,以为就是有多少个4字节,所以TCP头部最大的长度时15*4=64字节
- 6位标志位:
URG:紧急指针是否有效
ACK:确认号是否有效
PSH:提示接收端应用程序立刻从TCP缓冲区把数据取走
RST:对方要求重新建立连接,我们把携带RST标识的称为复位报文段
SYN:请求建立连接,我们把携带SYN标识的称为同步报文段
FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段 - 6位窗口大小:双方约定的滑动窗口的大小
- 16位校验和:发送端填充,CRC校验,接收端校验不通过,则认为数据是有问题的,此处的校验和不止包含TCP首部,也包含TCP的数据部分
- 16位紧急指针:标识那部分数据是紧急数据
tcp的连接管理
为什么TIME_WAIT的时间是2MSL?
MSL是TCP报文的最大生命周期,因为TIME_WAIT持续在2MSL就可以保证在两个传输方向上的尚未接收到或者迟到的报文段已经消失,否则服务器立即重启,可能会收到来自上一个进程迟到的数据,但是这种数据很可能是错误的,同时也是在理论上保证最后一个报文可靠到达,假设最后一个ACK丢失,那么服务器会再重发一个FIN,这是虽然客户端的进程不在了,但是TCP连接还在,仍然可以重发LAST_ACK。
TIME_WAIT功能
如果没有TIME-WAIT,主动关闭方就会直接进入CLOSED,如果立即重启客户端使用相同的端口,在最后一次ACK丢失时,服务端重复FIN请求,就会被重新启动的客户端接收到,或者新启动的客户端向服务端发起请求的时候,因为服务端正在等待最后一次ACK,因此新连接请求发送的SYN就会被认为请求码错误,服务端就会回复RET重置连接,因此需要主动关闭方发送最后一次ACK之后进入一个TIME_WAIT状态,等待一段时间—2MSL(两个报文最大生命周期),等待这段时间就是为了如果接收到了重发的FIN请求能够进行最后一次ACK回复,让在网络中延迟的FIN/ACK数据都消失在网络中,不会对后续连接造成影响
为什么是三次握手,四次挥手?
TCP可靠传输
确认应答机制/超时重传机制、协议中的序号/确认序号、校验和
超时重传机制
主机A发送数据给B后,可能是因为网络拥塞或者数据丢失等原因倒是数据无法到达主机B,所以如果主机A心思一个特定的时间间隔中没有受到B发来的确认应答,就会进行数据重发。因此主机B就会收到很多重复的数据,利用序列号就可以很容易的做到去重的效果
时间间隔
最理想的情况下,找到一个最小的时间,保证“确认应答一定可以在这个时间段中返回”,但是这个时间的长短随着网络环境的不同而不同。如果时间设置的太长会影响整体的效率,时间设置的太短了可能会频繁发送重复的包。
时隔方案
TCP为了保证无论任何环境下都能高性能的通信,采取了动态计算这个最大超时时间,Linus、BSD、Unix和Windows中的超时时间以500ms为一个单位进行控制,每次重发的时间都是500ms的整数倍,如果一次重发后得不到应答,就会在2*500ms后继续,依次类推,以2的指数形式递增。但是累积到一定次数的重传,TCP认为网络或者主机出现了异常,会强制关闭连接。
滑动窗口
通信双方通过协议中的窗口字段,来协商能都一次发送的最多数据,然后连续发送多条数据,在socket中使用两个指针维护窗口的前沿和后沿。
发送端:发送的起始位置–后沿,前沿就是发送的结束位置,如果窗口中后沿数据没有接收到ack确认,后沿不能向前移动,数据不能从缓冲区中移除,接收到ack确认后窗口的前后沿向后移动
接收端:接收数据的起始位置–后沿 接收数据的结束位置–前沿,当接收数据时没有收到第一条数据,后沿就不能向后移动,知道收到数据后,才可以向以后移动
滑动窗口机制通过窗口大小来确定两虚发送多条数据,但是一开始通信的时候,因为不了解网络状态,有可能造成发的越多丢的越多,超时重传就越多,降低了效率。
拥塞窗口
发送窗口=拥塞窗口,但是发送窗口不是一直等于拥塞窗口的,在网络情况好的时候,拥塞窗口不断的增加,发送方的窗口自然也随着增加,但是接受方的接受能力有限,在发送方的窗口达到某个大小时就不在发生变化了。
TCP拥塞控制
慢启动
&emsp主机开发发送数据报时,如果立即将大量的数据注入到网络中,可能会出现网络的拥塞。慢启动算法就是在主机刚开始发送数据报的时候先探测一下网络的状况,如果网络状况良好,发送方每发送一次文段都能正确的接受确认报文段。那么就从小到大的增加拥塞窗口的大小,即增加发送窗口的大小。
开始发送方先设置cwnd(拥塞窗口)=1,发送第一个报文段,接收方接收到后返回确认信息,发送方接收到接收方的确认后,把cwnd增加到2,接着发送方发送报文段,发送方接收到接收方发送的确认后cwnd增加到4,慢启动算法每经过一个传输轮次(认为发送方都成功接收接收方的确认),拥塞窗口cwnd就加倍。
快重传
快重传要求接收方收到一个失序的报文段后就立刻发出重复确认,而不要等待自己发送数据时才进行捎带确认。接收方成功的接受了发送方发送来的1-1001并且分别给发送了ACK,现在接收方没有收到1001-2000,而接收到了2001-3000,显然接收方不能确认2001-3000,因为2001-3000是失序的报文段。如果根据可靠性传输原理接收方什么都不做,但是按照快速重传算法,在收到其他报文段的时候,不断重复的向发送方发送1001-2000的ACK,如果接收方一连收到三个重复ACK(防止网络延时又收到数据),那么发送方不必等待重传计时器到期,由发送方尽早重传未被确认的报文段。这时返回的ACK是6001,因为之前的2001-6000已经成功接收,只是存储在了系统的接收缓冲区中。
快恢复
当触发了快重传算法时,接下来就执行乘法减小算法,把慢启动开始门限(ssthresh)减半,但是接下来并不执行慢开始算法,而是把cwnd设置为ssthresh的一半, 然后执行拥塞避免算法,使拥塞窗口缓慢增大。
拥塞避免
为了防止cwnd增加过快而导致网络拥塞,所以需要设置一个慢开始门限ssthresh状态变量。拥塞避免激素hi是让cwnd缓慢的增加而不是加倍的增长,每经历过一次往返时间就使cwnd增加1,而不是加倍,这样使cwnd缓慢的增长,比慢启动要慢的多。
- 当cwnd < ssthresh,使用慢启动算法
- 当cwnd > ssthresh,使用拥塞控制算法,停用慢启动算法。
- 当cwnd = ssthresh,这两个算法都可以。
ack确认丢失的情况
每条数据都要进行回复,并且应该按序逐条回复,如果没有收到第一天,但是都到了第二条,第二条就不能先回复,应该先回复第一条。这样做的好处就是第一条ack丢失后,如果发送端收到第二条回复,也会认为第一条正常接收,第一条就不需要重传了。
延迟应答机制
尽可能的保持窗口的大小,保持网络传输的吞吐率,如果接收端收到数据就立即返回ACK回答,这是后返回的窗口可能比较小,比如说接收端的接收缓冲区是1M,一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了.在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放⼤大一些, 也能处理过来;如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;窗口越大,传输效率就越高。但是不是所有的包都可以延迟应答,有数量限制,每隔N个包就应答一次,还有时间限制,超过最大延迟时间就应答一次,一般N为2,超时时间为200ms。
捎带应答机制
尽量减少不必要的确认应答包,接收端在发送数据的时候顺便对上一次的请求进行一个ack确认,比如说客户端给服务器说了 “How are you”, 服务器也会给客户端回一个 “Fine, thank you”;那么这个时候ACK就可以搭顺风车, 和服务器回应的 “Fine, thank you” 一起回给客户端
面向字节流
创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区和一个接收缓冲区。调用write时, 数据会先写入发送缓冲区中,如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去。接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区,然后应用程序可以调用read从接收缓冲区拿数据.
TCP的连接中, 既有发送缓冲区, 也有接收缓冲区, 那么对于这⼀一个连接, 既可以读数据,也可以写数据. 这个概念叫做 全双工。由于缓冲区的存在, TCP程序的读和写不需要一一匹配, 比如说写100个字节数据时, 可以调用一次write写100个字节, 也可以调⽤用100次write, 每次写一个字节。读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次.
Tcp粘包问题
缓冲区中的数据对于Tcp来说没有边界;造成数据粘连问题---->数据没有边界
解决粘包问题
对于定长的包, 保证每次都按固定大小读取即可;
对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;
对于变长的包, 还可以在包和包之间使⽤用明确的分隔符
TCP与UDP对比
TCP用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景;
UDP用于对高速传输和实时性要求较高的通信领域, 例如, 早期的QQ, 视频传输等. 另外UDP可以用于广播;