滑动窗口
滑动窗口在自己的发送缓冲区里。
- 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,上图的窗口大小就是4000个字节(四个段)
- 发送前四个段的时候,不需要等待任何ACK,直接发送
- 收到第一个ACK时,滑动窗口向后移动,继续发送第五个段的数据,依次类推
- 操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答;只有确认过应答过的数据,才能从缓冲区删掉
- 窗口越大,则网络的吞吐率就越高
如果出现丢包,如何进行重传?
情况1:数据包已经抵达,ACK丢了
这种情况下,部分ACK丢了,可以通过后续的ACK进行确认
情况2:数据包就直接丢了
- 当某一段报文段(假如1001-2000)丢失后,发送端会一直收到1001这样的ACK;
- 如果发送端主机连续三次收到同样一个“1001”这样的ACK,就会将1001-2000重新发送;
- 这时候接收端收到了1001之后,再次返回的ACK就是7001了(因为2001-7000)接收端其实之前就已经收到了,被放到了接收端操作系统内核的接收缓冲区内。
这种机制被称为“高速重传机制”(也叫“快重传”)
流量控制
接收端处理数据的速度是有限的,如果发送端发的太快,导致接收端的缓冲区被发满,这时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应
TCP支持接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制。
- 接收端将自己可以接收缓冲区大小放入TCP首部中的“窗口大小”字段,通过ACK端通知发送端;
- 窗口大小字段越大,说明网络的吞吐量越高;
- 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端;
- 发送端接收到这个窗口之后,就会减慢自己的发送速度;
- 如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送一个窗口 探测数据段,使接收端把窗口大小告诉发送端
拥塞控制
TCP引入慢启动机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据
每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小作比较,取较小的值做实际发送的窗口
拥塞窗口的增长速度,是指数级别的。“慢启动”只是指初始时慢,但是增长速度非常快
- 慢启动的阈值:当拥塞窗口超过这个阈值时,不再按照指数方式增长,而是按照线性方式增长
- 当TCP开始启动时,慢启动阈值等于窗口最大值
- 在每次超时重发时,慢启动阈值会变为原来的一半,同时拥塞窗口置回1
少量的丢包,我们仅仅是触发超时重传;大量的丢包,认为是网络拥塞;
TCP通信开始时,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降;
延时应答
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能较小
- 数量限制:每个N个包就应答一次(N取2)
- 时间限制:超过最大延时时间就应答一次(超时时间取200ms)
捎带应答
面向字节流
创建一个TCP的socket,同时在内核中创建一个发送缓冲区和一个接收缓冲区
- 调用write时,数据会先写入发送缓冲区中;
- 如果发送的字节数太长,会被拆分成多个TCP的数据包发出;
- 如果发送的字节数太短,就会先在缓冲区里等待,等到缓冲区长度差不多了,或者其他合适时机发送出去;
- 接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区;
- 应用程序可以调用read从接收缓冲区拿数据;
- TCP既有发送缓冲区,也有接收缓冲区,对于这么一个连接,既可以读,又可以写,这个概念叫做“全双工”
由于缓冲区的存在,TCO程序的读和写不需要一一匹配。
粘包问题
“包”是指应用层的数据包。
站在传输层的角度,TCP是一个一个报文过来的,按照序号排好序放在缓冲区中;站在应用层的角度,看到的只是一连串的字节数据,不知道从哪部分到哪部分是一个完整的应用层数据包。
如何避免粘包问题?
明确两个包之间的边界。
- 对于定长的包,保每次都按固定大小读取即可
- 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置
- 对于变长的包,还可以在包和包之间使用明确的分隔符。
TCP异常情况
- 进程终止:释放文件描述符,仍然可以发送FIN,和正常关闭没什么区别
- 机器重启:同进程终止
- 机器掉电/网线断开:接收端认为连接还在,一旦接收端有写入操作,接收端发送连接已经不在了,就会进行reset,即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在,如果不在,也会把连接释放