TCP,即Transmission Control Protocol,传输控制协议。人如其名,要对数据的传输进行一个详细的控制。
TCP对数据传输提供的管控机制,主要体现在两个方面:可靠和效率。
这些机制和多线程的设计原则类似:保证数据传输可靠的前提下,尽可能的提高传输效率。
一口气发了 1000 个字节的数据(一个 TCP 数据报,长度是 1000,序号是 1)
应答报文中的确认序号,就是 1001.
应答报文:可视为只有 TCP 报头,没有载荷.
意思就是 < 1001 的数据,B 已经收到了;接下来 A 要从 1001 开始往后发送!!!
在确认应答的情况下,如果没有收到 ACK,就需要通过其他途径来处理!
超时重传:不是说没有收到 ACK,立即就放弃,就需要重新再发一遍!
网络的环境是非常复杂的,尤其是有些时候,网络会拥堵,拥堵就可能会导致丢包!
丢包,是 “无差别” 丢的。任何一个数据报,都有可能会丢包!
普通报文 可能丢失,ACK 也可能丢失!
业务数据丢失
发送方等待一定的时间之后,没有收到 ACK 就会重传!
ACK 丢失
业务数据已经到了 主机 B 了,反馈的 ACK 没有回过去,发送方等待一会之后,就触发了重传!
假设第一次重传失败了,进行第二次重传,又失败了;
第一次丢包超时时间为 t1,第二次丢包超时时间为 t2,此时一定:t1 < t2
丢包超时等待的时间间隔,随着丢包此时的增加,越来越大!
确认应答 和 超时重传 可以认为是保证 TCP 可靠性的 最核心机制!
但是 连接管理 是面试中最高频的问题(网络知识)!
在正常情况下,TCP 要经过 三次握手 建立连接,四次挥手 断开连接!
三次握手:必须是客户端主动,服务器被动
三次握手的意义:(三次握手,认为是一种保证可靠性的机制)
四次挥手:客户端和服务器都可以主动
与 三次握手 不同,四次挥手 看起来也是双方各自给对方发送 FIN,各自给对方发送 ACK。
但是,四次挥手 这里的 中间两次,不能合并!
TCP 的状态:
我们要清楚一点可靠性和 效率是冲突的保证 可靠性 肯定会影响到 效率 的。
TCP 在保证 可靠性 的前提下,尽可能的提高 效率。
滑动窗口机制的本质就是把等待 ACK 的时间重叠起来。
减少等待时间,就相当于提高了效率。
每次传输,都需要等待 ACK,收到 ACK 再发下一题数据,这样效率就很低。
我们一次发送多条数据,就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了)。
窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。上图的窗口大小就是4000 个字节(四个段)。
发送前四个段的时候,不需要等待任何ACK,直接发送;
收到第一个 ACK后,滑动窗口向后移动,继续发送第五个段的数据;依次类推;
操作系统内核为了维护这个滑动窗口,需要开辟 发送缓冲区 来记录当前还有哪些数据没有 应答;只有确认应答过的数据,才能从缓冲区删掉;
窗口越大,则网络的吞吐率就越高;
对于超时重传丢包,这里分两种情况讨论。
情况一:ACK 丢失
这种情况下,部分 ACK 丢了并不要紧,因为可以通过后续的 ACK 进行确认;
情况二:数据包丢失
主机 A 发了半天之后,看到了连续的好几个 1001,它就明白了,是 1001 这个数据丢了!
接下来,A 就会重传 1001 这个数据了。
此处的原则是:哪条丢了,就重传哪条;已经传输到的数据,不必重复传输!
滑动窗口,窗口大小越大,发送速率就越快!流量控制,就是在针对发送速率进行制约!
接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应,反而还降低了速率。
流量控制,就是通过 接收缓冲区 剩余空间大小,来作为下一次发送时候的窗口大小。
流量控制,站在接收方的角度,来控制发送速率;
但是整体的传输,其实不光有发送方和接收方,还有中间的一系列用来转发的设备。
拥塞控制,采取的方法:做实验。
通过实验的方式,找出一个合适的窗口大小。
刚开始按照小的窗口来发送。
如果不丢包,说明网络中间环境 比较畅通,就可以逐渐放大发送窗口的大小。
放大到一定程度,速率已经比较快,网络上就容易出现拥堵,进一步出现丢包;当发送方发现丢包之后,就减小发送的窗口。
反复在 2 和 3 之间循环!
这个过程,就达到了一个 “动态平衡”,发送速率不慢,接近了能承载的极限,同时还可以尽量少丢包,还能适应网络环境的动态变化。
流量控制 和 拥塞控制 都能影响发送方的滑动窗口大小!最终的滑动窗口大小,就取决于两者的较小值。
像上面这样的拥塞窗口增长速度,是指数级别的。“慢启动” 只是指初使时慢,但是增长速度非常快。
为了不增长的那么快,因此不能使拥塞窗口单纯的加倍。
此处引入一个叫做慢启动的阈值
当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长
拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案。
延时应答也是一个用来 提高效率的机制。
延时应答,则是让窗口能够大一些。
在流量控制中,通过 ACK 告知发送方,窗口大小是多少合适,这里的窗口大小是由接收缓冲区的空余空间来决定的。
如果立即返回 ACK,可能接收端缓冲区空余空间为 5KB;
稍等一会(例如 500 ms),在这个过程中,应用程序不断的取走数据,接收端缓冲区空余空间可能为 100 KB 了。
正常来说,ACK 是收到请求之后,内核立即返回的;而响应数据,则是应用程序代码,发送的。二者是不同的时机,就不能把 ACK 和 响应报文 合并。
但是基于延时应答等的基础上,延时一会,就可能和返回 响应数据,时间上重合了。
响应在收到请求之后,多长时间之内返回?不确定:
面向字节流,指的是读写载荷数据的时候,是按照 “字节流” 的方式来读取的。
TCP 数据报,本身仍然是 一个一个 “数据报” 这样的方式来传输的。
(应用程序,是感受不到从哪里到哪里是一个数据报的)
传输数据的时候,数据会先写入 接收缓冲区,应用程序直接从 接收缓冲区 取数据。
此时,应用程序在读取数据的时候,就可以灵活的进行了,可以一次读 M 个字节,分 N 次读。
如果一个 TCP 连接,里边只传一个 应用层数据包,这个时候不会粘包(短链接)。
如果一个 TCP 连接,传输多个应用层数据包,这个时候就容易分不清,从哪到哪是一个完整了应用层数据(长连接)。这就是粘包问题。
不仅仅是 TCP ,只要是面向字节流的传输,都有粘包问题(文件传输)。
粘包问题的解决方案:在应用程序代码中,明确包之间的边界!
按照程序关机,会先杀死所有的用户进程(也就包括我们自己写的 TCP 程序)
杀死进程 => 释放进程 PCB => 释放文件描述符表上对应的文件资源(相当于调用 close)
这个时候就会出发 FIN,开启 四次挥手 的流程!
如果四次挥手挥完了,就继续关机;
如果还没有挥完,就已经关机了,对端重传 FIN 若干次,没有响应,就放弃了。
同上,程序是正常关闭,还是异常崩溃,都会释放 PCB,都会释放文件描述符表(相当于调用 close)
也还是会正常 四次挥手(虽然进程没了,但是本身 TCP 连接也是内核负责,内核仍然会继续完成后续的挥手过程)
笔记本还好(内置电源),如果是台式机之类的,直接没了,肯定来不及挥手。
可靠性:
提高性能:
其他: