目录
一、TCP报头结构
二、确认应答机制
三、超时重传机制
四、连接管理机制
五、滑动窗口
六、拥塞控制
七、应答策略
TCP全称为传输控制协议(Transmission Control Protocol),数据在传输过程需要严格的控制
TCP协议段落格式
4位TCP报头长度:表示该TCP头部有多少个32位bit,TCP报头的最大长度是 15 * 4B = 60B;TCP报头的标准长度是20字节,即在通信时拿到TCP数据后会先读取20个字节,将其转换成一个结构化数据之后,从标准报头中提取4位首部长度,得到完整报头长度(20~60字节)
完整报头 = 标准报头 + 选项
6个标志位:
16位检验和:发送端填充,CRC校验。接收方校验不通过,则数据丢弃。此处的检验和不光包含TCP首部,还包含TCP数据
16位紧急指针:紧急数据(1字节)的偏移量
32位序号&&32位确认信号:发送数据的编号&&应答数据的编号,防止数据丢包和乱序
16位窗口大小:填入自己的接收缓冲区的剩余空间的大小,让对方控制数据发送速度(流量控制)
由于TCP是面向字节流传输,故TCP报头里面不包含有效载荷的长度。
TCP数据解包过程(传输层 ---> 应用层,报头和有效载荷分离)
TCP添加报头和UDP类似,将数据拷贝之后,在数据的前端添加结构化数据。
网络传输和本地传输的本质区别就是数据的传输距离变长,这就引发了数据传输的可靠性问题。
当数据的传输距离变长,就容易出现丢包、乱序、校验错误、重复等情况,这些就是不可靠问题。
在网络通信中,如何确定通信的可靠性?收到应答,才能100%确定对方收到信息!
双方通信中,一定存在最新的消息,没有应答——最新的消息无法保证可靠性!
让最新的信息作为确认信息,保证历史信息的可靠性。
无论是是client向server发数据,还是server向client发数据,每一条数据都需要应答(单一数据应答或者批量数据应答)
数据应答的顺序和数据发送的顺序一样吗?未必一样,故每条数据都需要编号,来使每条应答都能对应到发送的数据(防止乱序和丢包)
确认应答&&确认序号:接收方已经收到了ACK序号之前的所有(连续)的报文
序号&&确认序号,为什么要有两组信号?全双工,从TCP协议角度,client端和server端地位对等,通信本质都是数据的发送和应答(不再是请求和响应)
通信双方都有自己的发送缓冲区和接收缓冲区,以实现全双工通信
接收缓冲区的本质是一个队列queue,保证数据的按序到达
TCP报文也是有类型的!
TCP的报文通过6个标志位区分类型,服务器会收到各种各样的TCP报文,根据报文的不同类型做不同处理。
在发送方发送数据之后,在特定的时间间隔内没有收到确认应答,于是重发数据,这就是超时重传机制。其实发送过程中的数据究竟有没有丢包,发送方并不知道,所以策略就是超时没有收到确认应答就认为是丢包了。
因此接收方很可能会收到很多重复数据,那么怎么处理呢?因此TCP需要能够识别处重复的数据报,并且把重复数据丢弃,这就需要通过数据序号去重。
发送方发出去的数据,不能立刻移除,而是必须维持一段时间(收到确认应答之后再移除),数据维持在哪里?发送缓冲区。(计算里的数据移除,通常是覆盖,通过标志位限制缓冲区的有效性)
在超时重传机制中,特定的时间间隔与网络通信的效率相关联,我们如何设置确认信号的超时等待时间呢?最理想的情况下,找到一个最小的时间间隔,保证正常通信的确认应答信号一定能够在这个时间内返回。但是这个时间的长短,随着网络环境的不同,存在差异。
TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间。
服务端状态转换:
客户端状态转换:
建立连接:三次握手
DDOS攻击:也称肉鸡攻击,黑客将木马病毒大量散播,潜入肉鸡电脑,设置时间在某一时刻同时对某一服务器发起请求,造成服务器资源耗尽而崩溃。
断开连接:四次握手
四次挥手动作完成,主动断开连接的一方为什么会维持一段时间的TIME_WAIT状态?
TIME_WAIT状态一般维持多长时间的TIME_WAIT状态呢?2*MSL,MSL是单向传输数据时消耗的最大时间。
服务器有时候可以立即重启,有时候无法立即重启(bind error),为什么?因为有时候server是主动断开连接的一方,结束通信后进入TIME_WAIT状态。
在server的TCP连接没有完全断开之前不允许重新监听,某些情况下是不合理的:
那么我们怎么解决TIME_WAIT状态引起的bind失败问题呢?
在socket套接字创建之后,使用socketopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但是IP地址不同的多个socket描述符。
int opt = 1;
setsocketopt(listenfd, SOL_SOCKET, SO_REUSERADDR, &opt, sizeof(opt));
对于服务器上出现大量的TIME_WAIT状态连接,原因就是服务器没有正确close(socket),导致四次挥手没有正常完成,这是一个BUG,需要给每次连接加上正确的close()。
流量控制:接收端处理数据的速度是有限的,如果发送端发送太快导致接收端的缓冲区被写满,这个时候如果发送端继续发送数据,就会造成丢包,继而引起丢包重传等等一些列连锁问题。因此TCP支持根据接收端的处理能力,来决定发送端的发送速度。
那么第一次数据的发送,发送端如何知道接收端的窗口大小呢?三次握手。
在TCP首部中,有16位窗口大小字段,那么16位数字表示最大范围就是65535字节吗?实际上,TCP首部40字节选项中还包含了一个窗口大小扩大因子M,实际窗口大小是窗口字段的值左移M位。
TCP协议有确认应答机制,对每一个发送的数据段,都要给一个ACK确认应答,收到ACK应答之后再发送下一个数据段,这样就出现了一个缺陷,就是性能较差,尤其是数据往返的时间较长的情况下。
既然串行一发一收的方式性能较低,那么我们就采用并行发送多条数据,再等待多条数据应答的策略,这样就大大的提高了性能(将多个数据段的等待应答时间重叠在一起)。
在TCP通信过程中,数据分为三种状态:①已经发送并且收到确认应答;②正在发送但是尚未收到确认应答;③没有发送。
滑动窗口里的数据正是第②中状态的数据,已经发送但是尚未收到应答。
窗口的初始大小怎么设置?未来怎么变化? 滑动从窗口的大小与对方的接收能力有关,未来无论怎么滑动,都要保证对方能够正常接收。
窗口一定向右滑动吗?会向左滑动吗?由于左边是已经发送且收到确认应答的数据(需要删除),所以窗口一定不会向左滑动,但是不一定向右滑动,可能长时间保持不动。
窗口大小会一直不变吗?窗口大小是浮动变化的,可能会不变,但不会一直不变,变化的依据是对方的接收能力,当对方的接收缓冲区写满了,窗口大小变为0。
滑动窗口的丢包处理:
序号&&确认序号也用于支持滑动窗口的规则制定!
我们发送的数据在尚未收到应答之前,需要暂时保证起来以支持超时重传,数据保存在哪?滑动窗口之中!
滑动窗口一直向右滑动,空间不够了怎么办?实际上,滑动窗口数组空间是一个循环队列结构。
TCP的可靠性不仅考虑双方主机的问题,还要考虑网络的问题。
如果数据丢包是双方主机的问题,那么会采用超时重传机制;如果数据丢包是网络问题,则不会进行超时重传。
虽然TCP协议通过滑动窗口能够高效可靠的发送大量数据,但是如果在刚开始阶段就发送大量数据仍然可能引发问题。因为网络上有很多计算机,可能当前网络已经进入拥塞状态了,在不清楚当前的网络状态时就贸然发送大量数据,是会加重网络拥塞的。
因此TCP协议引入慢启动机制,在不清楚当前网络状态的情况下,先发送少量数据试探当前网络的拥堵情况,再决定按多快的速度传输数据。
此处引用一个概念是拥塞窗口:
如此可见,拥塞窗口的增长速度是指数级的,慢启动只是初始速度慢,但是增长速度非常快。
为了不让增长速度过快,因此不能使拥塞窗口每次都是单纯的翻倍。于是设置了一个慢启动的阈值,当拥塞窗口超过阈值的时候,不再按照指数方式增长,而是按照线性方式增长。
少量的丢包,触发超时重传机制;大量的丢包,触发拥塞控制机制。
当TCP通信开始后,网络吞吐量会逐渐上升,随着网络发送拥堵,吞吐量立即下降。
拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案。
延迟应答
如果接收主机立刻返回ACK应答,这时候返回的窗口可能比较小
窗口越大,网络的吞吐量就越大,传输效率就越高,我们的目的是在保证网络不拥堵的情况下尽快提高传输效率。
那么所有的数据包都可以延迟应答吗?并不是
由于ACK确认序号的精妙设计,故延迟应答中被上层消费掉的数据不需要应答,可以通过应答下一个数据包的确认序号保证正常通信。
捎带应答
在延迟应答的基础上,我们发现,很多情况下,客户端服务器也是“一发一收”的,客户端和服务器之间互相发送和接收数据。
如此ACK就可以搭发送数据的顺风车,在发送给对方数据的时候完成确认应答的功能,因为ACK只是一个标志位,ACK确认序号存在每一个数据的报头里。