TCP协议全称:传输控制协议,知道数据包丢失和差错的信息,能对数据的传输进行一定的控制,是全双工的。因为有缓冲区的存在,读写不需要11匹配。要明白一个原理,TCP的连接由内核层的网络协议栈来完成。用一个五元组来标识一个连接。五元组是(源ip,源端口,目的ip,目的端口,协议)

TCP报头解析:

image.png
16位源端口号:代表发起数据包的一方使用的端口
16位目的端口号:代表想要发送的目标端口号
32位序号:是一个32位随机数。代表数据发送的起始位置
32位确认序号:在接收方发往发送方的包中,此数据代表上一个报文的32位序号,加上接收到数据的字节数。
4位首部长度:代表TCP头部有多少字节。(最大为154=60字节)
6位保留位:为将来可能用到的位置,目前已经用了三位,分别是nonce,cwr,ece都与拥塞控制有关。
*16位窗口大小
:这个代表的是滑动窗口的大小,表示接收方可以接收的最大数据量。也就是接收缓存池的大小。这个值可以动态变化。
16位校验和:用于tcp头部+应用层数据的差错校验。
16位紧急指针:这个和序列号,urg标识搭配使用,标识那一段数据是紧急的。
选项:为0-40字节。
数据:就是需要传输的数据,最大为1460字节。(这是由于数据链路层的MTU值来确定的
标志位的含义:

  • URG: 标识紧急指针是否有效
  • ACK: 标识确认序号是否有效
  • PSH: 用来提示接收端应用程序立刻将数据从tcp缓冲区读走
  • RST: 要求重新建立连接. 我们把含有RST标识的报文称为复位报文段
  • SYN: 请求建立连接. 我们把含有SYN标识的报文称为同步报文段
  • FIN: 通知对端, 本端即将关闭. 我们把含有FIN标识的报文称为结束报文段

    三次握手四次挥手

    三次握手

    image.png
    目标:
  1. 请画出三次握手和四次挥手的示意图
  2. 为什么连接的时候是三次握手?
  3. 什么是半连接队列?
  4. ISN(Initial Sequence Number)是固定的吗?
  5. 三次握手过程中可以携带数据吗?
  6. 如果第三次握手丢失了,客户端服务端会如何处理?
  7. SYN攻击是什么?(资源分配是在二次握手加入半连接队列的时候分配的),客户端的资源在三次握手时分配的。
  8. 挥手为什么需要四次?
  9. 四次挥手释放连接时,等待2MSL的意义?

三次握手是确保双方的接受能力和发送能力是正常的

  1. 在建立三次连接时,客户端处于close,服务端处于listen状态,这时客户端发送一个SYN数据包,并指明客户端的初始化序列号。然后此时客户端的状态变为syn_sent,这证明了客户端的发送能力是正常的。服务端收到客户端的SYN报文后,会以自己的SYN报文作为应答,并且也会初始化一个ISN序列号,并且会把客户端发送的SYN序列号+1作为自己的ACK的值传回给客户端。这时服务端的状态变为SYN_REC并且会把次连接放入自己的半连接队列。在客户端收到服务端的报文后,确定了服务端的接受和发送能力是正常的。客户端的状态变为established,并且会将服务端的SYN值加1以ACK报文发送给服务端。服务端的状态变为established,并且将客户端放入自己的全连接队列。这时服务端也知道客户端的接受能力是正常的。这样就建立了TCP连接。

注意事项:
当服务端发送了ACK+SYN包时未收到客户端的ACK包则它会重传。在超过了系统规定的重传次数后,就会将它从半连接队列中删除。
全连接队列和半连接队列的长度能够影响应用程序的效率, 因为在全连接队列和半连接队列满了之后,就不会接收新的连接请求。
全连接队列的长度:
即全连接队列长度 = min(backlog, 内核参数 net.core.somaxconn),net.core.somaxconn 默认为 128。
半连接队列长度:
即半连接队列长度 = min(backlog, 内核参数 net.core.somaxconn,内核参数 tcp_max_syn_backlog)。

四次挥手


四次挥手才能结束连接、这是是由于TCP的半关闭造成的,所谓的半关闭,就是TCP提供了连接的一端在结束它的发送后还能接受到另一端数据的能力。当服务端收到客户端的FIN报文后,会先回复一个你的FIN报文我收到了,但是此时服务端可能还有数据没有发完,所以不会直接断开连接,当服务端将数据传完后再发送报文去关闭连接。
image.png

  1. 第一次挥手,客户端会发送一个FIN报文,并且会指定一个序列号,此时客户端处于,FIN_WAIT1状态。第二次挥手,服务端收到报文后,会发送ACK报文,值为序列号+1,并且会发送自己的seq值。表明已经收到了客户端的信息。此时服务端处于close_wait状态,客户端处于fin_wait2状态。第三次挥手。等服务端确定自己没有要传的数据的时候,发送一个FIN报文并且指定一个seq,且回复ACK=客户端的seq+1此时客户端处于Last_ack状态。第四次挥手,客户端在收到服务端的ACK报文后,会发送一个ACK报文,值为w+1和seq序列号值为服务端发送的ACK的值。服务端在收到了这个数据包后就调用closed函数,将连接关闭此时客户端处于TIME_WAIT状态。等待2MSL后才将状态变为Close
    MSL:报文最大生存时间。

为什么会等待2MSL:
为了保证客户端的最后一个ACK报文能到服务端。确保服务端的正常关闭。倘若没有这个2MSL等待时间,客户端直接关闭的话。但是服务端可能一直没有收到客户端的ACK报文。就会导致服务端的连接一直不能关闭,这个2MSL是客户端ACK到达服务端的时间+服务端的报文再次返回的时间。
MSL的值可以查看 /proc/sys/net/ipv4/tcp_fin_timeout 为60

可靠性原理:

确认应答机制:

image.png
TCP将每个字节的数据都进行了编号,即为序列号。image.png
每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了那些数据,下次你要从哪里开始发,比如客户端向服务器发送了1005字节的数据,服务器返回给客户端的确认序号是1003,那么说明服务器只收到了1-1002的数据,1003,1004,1005都没收到。此时客户端就会从1003开始发。

超时重传机制:

image.png
在一定的时间间隔中,没有收到主机B的ACK报文,则会重传之前的数据包。
时间间隔太短了没法保证ACK数据包能到达主机A,时间间隔太长了又会影响整体的重传效率。
时间间隔如何计算呢?
TCP为了保证任何环境下都能保持较高的通信性能,它会动态计算这个时间值,以500ms为一个单位进行控制,是单位的整数倍进行设置时间。当超过了一定次数后,此连接就会被认为异常,被服务端主动关闭。

滑动窗口:

每一次发送一个数据包,并且主机B回复一个ACK这样效率不高,那么能不能一次发送多个数据包,来提高整体的传输效率呢?是可以的,滑动窗口就是为了解决传输效率的问题。
image.png
一个概念:窗口
窗口大小就是无需等待确定应答就可以发送数据的最大值
上图的窗口就是4000个字节
收到第一个ACK确认后,窗口向后移动,继续发送数据。
因为这个窗口可以不断向后滑动,所以叫做滑动窗口。
操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还又那些数据没有应答,只有ACK确认应答过的数据才能从缓冲区删掉。
image.png
如果出现了掉包,那么如何进行重传呢?
此时分两种情况:

  1. 数据包已经收到了,但确认应答ACK丢了
  2. image.png

这种情况并没有大碍,因为可以从后面的数据包发送的ACK来保证前面的数据包是已经收到了的

  1. 数据包丢失

image.png

当没有收到某个数据包时,会终止在此数据包上,回复ACK下一个是1001,当收到三次这样的重传请求时,主机A会重传所需要的数据包。但是在同一个窗口后面发的数据包时不受影响的,因为数据包在主机B的接收缓冲区中,所以会从发送没有接受到的数据包。
这种机制称为高速重发控制(快重传)

流量控制

接受端处理数据的速度是有限的,如果发送端发的太快,导致接受缓冲区被填满,这个时候如果发送端继续发送,就会造成丢包,进而引起丢包重传等一系列连锁反应。
因此TCP支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制
接收端将自己可以接受的缓冲区大小放入TCP首部的窗口大小字段,通过ACK通知发送端,窗口大小越大说明网络的吞吐量越高,接收端可以根据自己缓冲区的大小,自动的调节发送的窗口大小的数值,此时发送端就会减慢自己的发送速度,如果接受端缓冲区满了,就会将窗口设置为0,发送端不再发送数据,而是会发送一个窗口探测数据段,让接收端把窗口大小再告诉发送端
image.png
那么接收端如何把窗口大小告诉发送端呢?
再TCP的首部中,有一个16位的窗口大小字段,就存放了窗口大小的信息,
但是16位数字最大表示65536那么TCP窗口最大就是65536个字节吗?
实际上,TCP首部40字节选项中,包含了一个窗口扩大因子M

拥塞控制

几种拥塞控制方法:

  1. 慢启动
  2. 拥塞避免
  3. 快重传
  4. 快恢复

快恢复:
与快重传配合使用的还有快恢复算法,其过程有以下两个要点:
<1>. 当发送方连续收到三个重复确认,就执行“乘法减小”算法,把慢开始门限ssthresh减半。这是为了预防网络发生拥塞。请注意:接下去不执行慢开始算法。
<2>. 由于发送方现在认为网络很可能没有发生拥塞,因此与慢开始不同之处是现在不执行慢开始算法(即拥塞窗口cwnd现在不设置为1),而是把cwnd值设置为 慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。

虽然TCP有了滑动窗口这个大杀器,能够高效可靠地发送大量数据
但是如果在刚开始就发送大量的数据,就可能引发大量问题,例如因为网络堵塞数据包大量丢失,就需要重新发布,浪费资源。在不清楚当前网络的情况下,贸然发送大量数据,很有可能会学上加霜。
因此TCP引入了慢启动机制,先发少量的数据探探路,摸清楚当前的网络状态后,再决定按照多大的速度传输数据。
image.png
再引入一个概念,拥塞窗口(cwnd)

  • 发送开始的时候,定义拥塞窗口的值为1
  • 每次收到一个ACK应答,拥塞窗口的值加1
  • 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口

像上面这种拥塞窗口的增长速度是指数级别的
慢启动只是初始慢,但是增长速度非常快
为了不增长那么快,就引入了一个名词叫做慢启动的阈值,当拥塞窗口的大小超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长
image.png

  • 当TCP开始启动的时候,慢启动的阈值等于窗口的最大值
  • 在每次超时重发的时候,慢启动阈值(ssthresh)会变为原来的一半,同时拥塞窗口置回1

少量的丢包,我们仅仅是触发超时重传
大量的丢包,我们就认为是网络拥塞
当TCP通信开始后,网络吞吐量会逐渐上升
随着网络发送拥塞后,吞吐量会立即下降。
拥塞控制:归根结底就是TCP想快速把数据发送给对方,但又要避免给网络造成太大压力的折衷方案。

延迟应答

如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小.
假设接收端缓冲区为1M. 一次收到了500K的数据;
如果立刻应答, 返回的窗口大小就是500K;
但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了; 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
如果接收端稍微等一会儿再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M

窗口越大, 网络吞吐量就越大, 传输效率就越高.
TCP的目标是在保证网络不拥堵的情况下尽量提高传输效率;

那么所有的数据包都可以延迟应答么?
肯定也不是
有两个限制

  1. 时间限制
  2. 数量限制

    捎带应答

    面向字节流

    创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;
    调用write时, 数据会先写入发送缓冲区中;
    如果发送的字节数太大, 会被拆分成多个TCP的数据包发出;
    如果发送的字节数太小, 就会先在缓冲区里等待, 等到缓冲区大小差不多了, 或者到了其他合适的时机再发送出去;
    接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
    然后应用程序可以调用read从接收缓冲区拿数据;
    另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区,
    那么对于这一个连接, 既可以读数据, 也可以写数据, 这个概念叫做 全双工

由于缓冲区的存在, 所以TCP程序的读和写不需要一一匹配

粘包问题:

首先要明确, 粘包问题中的 “包”, 是指应用层的数据包.
在TCP的协议头中, 没有如同UDP一样的 “报文长度” 字段
但是有一个序号字段.
站在传输层的角度, TCP是一个一个报文传过来的. 按照序号排好序放在缓冲区中.
站在应用层的角度, 看到的只是一串连续的字节数据.
那么应用程序看到了这一连串的字节数据, 就不知道从哪个部分开始到哪个部分是一个完整的应用层数据包.
此时数据之间就没有了边界, 就产生了粘包问题

那么如何避免粘包问题呢?
归根结底就是一句话, 明确两个包之间的边界
对于定长的包
- 保证每次都按固定大小读取即可
例如上面的Request结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可
对于变长的包
- 可以在数据包的头部, 约定一个数据包总长度的字段, 从而就知道了包的结束位置
还可以在包和包之间使用明确的分隔符来作为边界(应用层协议, 是程序员自己来定的, 只要保证分隔符不和正文冲突即可)

保证可靠性的机制:

  • 校验和
  • 序列号(按顺序到达)
  • 确认应答
  • 超时重传
  • 连接管理
  • 流量控制
  • 拥塞控制

提高性能的机制:

  • 滑动窗口
  • 快速重传
  • 延迟应答
  • 捎带应答

定时器:

  • 超时重传定时器
  • 保活定时器
  • TIME-WAIT定时器

基于TCP的应用层协议:

  • HTTP
  • HTTPS
  • SSH
  • Talnet
  • FTP
  • SMTP

https://blog.csdn.net/sinat_36629696/article/details/80740678


[

](https://blog.csdn.net/sinat_36629696/article/details/80740678)