传输层协议之TCP

        TCP协议是传输控制协议,所谓的传输控制指的是,所有的发送本质上是拷贝,我们应用层调用的一切的网络IO接口,其实是把数据拷贝给了传输层,说白了就是操作系统,后续的数据什么时候发,发多少?出错了怎么办,完全是由TCP协议自主控制。就好比我们对文件进行读写时,使用write将数据写到操作系统内,而不是直接写道磁盘上,至于后续的动作操作系统有自己的刷新策略。

1、TCP协议的报头(了解相关字段)

        上面TCP报文的格式中的数据指的上层拷贝下来的应用层的报文,包含应用层的报头和有效载荷。报头部分中,16位源端口和目的端口含义与UDP中一样,分别标识报文从远端客户端的哪一个进程来,到服务器上面的哪一个进程去,是传输层报头中不可或缺的部分。

        这里我们不考虑报头中选项部分的数据,因此可以默认TCP报头的标准长度为20个字节,报头中的4位首部长度代表TCP报头的总长度,这个总长度包括标准报头的20字节加上选项长度。由于是是4个比特位,取值范围为0~15,表征报头总长度时要乘以4(字节),根据标准报头长度为20字节,因此TCP报头的长度范围为20~60字节。

        关于TCP报文的解包和分用问题也就一目了然了,传输层拿到一个TCP数据,系统首先读取前20个字节,一定可以将它转化成一个结构化的数据,通过4位首部长度可以进一步读取选项数据,再与有效载荷分离,通过16位目的端口号交付到指定的上层协议中去。

        不同于UDP,TCP的报头中没有用于标注有效载荷长度的字段(在UDP中有16位报文总长度,减去报头的8个字节可以得到有效载荷长度),原因是TCP协议是面向字节流的,传输层收到报文后,直接将报头和有效载荷分离再将有效载荷推送到接收缓冲区中,它只需要考虑数据按发送顺序可靠地到达,而不需要通过传输层报头对有效载荷做任何地解释,至于缓冲区中的数据如何解析,怎么保证报文完整性,这些完全由应用层决定。

        通过解答一个问题进一步理解网络协议栈和文件是什么关系。服务端的传输层接受到一个报文,是如何找到目的端口的进程的?系统是有许多的场景需要快速定位一个进程的,在操作系统内,要为进程维护各种数据结构,事实上在内核中,将所有的PCB整体以双链表的形式组织起来,但并不仅限于此,双链表形式可以保证我们的进程不丢失,可以很好地对它做管理,但有时为了快速地寻找一个进程,我们还需要将每一个进程的PCB添加到其他的数据结构里,操作系统还会以端口号为key值以进程的PCB指针为value来维护一张哈希表,通过端口号和哈希表,就可以找到指定进程的所有内容,所以我们在写代码时调用bind给进程绑定端口号,系统会将我们的进程添加到这个端口号映射进程的哈希表中。通过端口号定位到特定进程之后,数据又是怎么发送给进程的呢?定位到PCB之后,PCB中的文件描述符表(files_struct)中会新打开一个文件描述符,也就是我们上层自己维护的socket,同时系统也会为传输层创建struct_file结构体,结构体中包含一系列的读写方法,也就是我们上层调用的网络IO接口,struct_file结构体中是维护了读写缓冲区的,这也是我们TCP协议向上交付时把数据放入的缓冲区,上层调用read、write接口也是对相应的缓冲区进行操作,以文件的方式读取网络数据。

        传输层所谓的报头其实是一个结构体,里面结构化地包含了一系列数据,报头是一个类型,是类型就可以定义变量,添加报头就是定义一个报头对象,使用源端口、目的端口等数据对报头对象进行填充,再将其添加到有效载荷前面就形成传输层的完整报文了。

2、TCP的可靠性(确认应答机制)以及提高传输效率的策略(也会穿插讲报头中的相关字段)

        为什么数据在网络传输过程中会存在不可靠问题呢?在传输过程中信号可能会发生衰减,中间经过某些出现异常的设备发生了数据丢失,可能是某些设备因为信号的问题无法识别01了,我们的数据经过多次的数据包转发,可能会出现各种奇奇怪怪的问题,一切的问题说到底仅仅是因为传输距离长了。

        不可靠问题有许多种,常见的有丢包,乱序(早发的报文阻塞在了某一个路由器上,晚发的可能走的路径比较短,反而更早地到达了),校验错误(数据包经过长距离传输地时候,发生了比特位反转,导致最后做校验和比对地时候匹配不上了),重复(发送方认为自己发送的数据包丢失了,重新又发了一份)。

        关于网络通信,存在绝对的可靠性吗?可以将网络通信类比为人的对话,对话时,我们可以通过对方的回复或者点头示意等行为确认自己的消息被接收到了,同样网络通信也是通过对方的应答响应来确认自己端的消息被接收到了,在通信时,数据是有先后顺序的,那么就一定存在最后一条数据,这个最后一条消息是没有应答的,简单来说,我们通过确认应答来保证可靠性,但是最后一条消息是无法保证其可靠性的。因此绝对的可靠性是不存在的,只存在相对的可靠性。TCP的可靠性也是相对的可靠性,是基于确认应答的机制的。

        基于上面讲的确认应答机制,我们可以知道,数据的类型是有不同的。在数据链路层双方在进行通信的时候,除了正常的数据段(其中包括来自上层的应用层报文),还会有确认数据段,这种数据段的目的是做确认或者响应,可惜不携带任何的有效数据。这是最基本的TCP工作模式,实际上,A给B发送数据后,B可以将自己的应用层报文与确认响应压缩到一条TCP报文中,提高传输的效率,这种方式叫做捎带应答,也是更常见的工作模式。

        进一步扩展真实的工作场景,TCP实际通信过程中,发送端发送一条报文,在接收端的应答到来之前,发送端不会一直等待,它可能又连续发送了多条报文,然后接收端再统一对之前接收的报文做应答,形成发送和接收的并行工作,提高效率,发送每一条报文,不一定会立刻应答,但原则上都必须有应答。确认应答机制是通过报头中的序号和确认序号还有六位特殊字段中的ACK来实现的。通信双方,发送端发送数据是要在带上序号的,这样就算接收方收到数据的顺序与发送方发送数据不符,也可以根据序号判断数据顺序,接收方接收数据之后,在给发送方的确认应答报文中要带上确认序号,确认序号代表此序号之前所有序号的报文都已经接收,接下来请发这个序号的报文,比方说接受方给发送方发送的报文中确认序号是14,要表达的意思是,14号之前所有的报文我都已经全部接收,下一次请发送14号报文。假设发送方给接收方发送11~14号报文,13号报文在网络传输过程中丢包了,接收方只收到了11、12、14号报文,那么接收方最后一次确认应答时确认序号只会填13而不是15,代表他接收到了13号之前的所有报文。也就是说,确认序号不仅仅是对最近一次收到报文的确认,也是对历史报文的确认。这样报头中维护两组序号,类似于使用TCP时,通信双方各自在内核中也会维护两块缓冲区一样,来支持我们的全双工通信,保证双方在都能进行收发,保持数据的有序。

        使用TCP作为传输层协议,我们知道通信流程开始是应用层把数据向下交付到系统维护的发送缓冲区,直到最后接收方应用层从系统维护的接收缓冲区中拿到应用层,是一个不断地从缓冲区到缓冲区的拷贝过程,如果发送方发送数据过快,远远快于接收方上层处理数据的速度,接收缓冲区保存数据的能力是有限的,满了之后,接收方只能丢弃新发来的数据,虽然后续会讲TCP拥有一系列可靠性机制,但这造成了时间和网络资源的浪费。如果发送端发送数据过慢,会影响接收方上层正常的业务处理速度。所以TCP发送数据不能过快也不能过慢。那么发送方是如何得知自己发送的数据量是合适的?发送方需要得到对方接收缓冲区的剩余空间大小。在TCP报头中的16位窗口大小表示的就是接收缓冲区剩余空间的大小,不论是发送方还是接收方,不论发送的是有效数据还是确认响应数据,发送时都会在16位窗口大小中填充自己的接收缓冲区的剩余空间大小,以供对方确定自己后续发送报文的数据量,这也是所谓的流量控制的概念。那通信双方怎么做到还没有发送数据就得知对方的接收缓冲去接收能力呢?在三次握手期间,双方就已经交换了各自的16位窗口大小,已经得知了对方的接收能力,可以做到一开始就向对方发送最合适的数据量。假如接收方的接收缓冲区满了,发送方的上层IO接口会处于阻塞状态,系统也会轮询式地进行接收方的窗口探测,假如接收方的窗口大小更新了,会向发送方发送通知,保证数据收发的效率。

        TCP报文是有类型的,网络通信过程中,接收方会受到各种各样的TCP报文,接受方要根据报文的类型,进行不同的动作。如果报文是一个常规的数据报文,那么接收方要做的是把数据读到,并把它放到对应的接收缓冲区中,再把数据拷贝到struct_file的文件缓冲区中,如果是一个连接请求的报文,那么接收方要做的不是读数据,而是要进入和发送方三次握手的流程,如果是一个断开连接的请求,那么也不应该读数据,而是进入和发送方四次挥手的流程。

        TCP报文的类型是根据报头中六个标记位确定的,下面讲讲这六个标记位。SYN标志位是连接请求标志位,传输常规的数据报文时,这个标志位一般是置0,如果置1代表这一个报文是连接请求报文,接收方收到之后会会与发送方进入三次握手的流程。对应地,还有FIN标志位,如果置1代表这个报文是请求断开连接的报文,接收方收到这个报文之后,会与发送方进入四次挥手的流程。ACK标志位,接收方给发送方进行确认时,发送的报头里面不光要填写确认序号,还要将ACK标志位置1,在通信过程中,不论报文是一个独立的确认报文(不携带任何应用层数据),还是一个常规的数据报文,只要这个报文具有对对方报文确认的能力,这个标志位都应该被置1,通信双方的三次握手建立好之后,基本上发送的所有报文中这个ACK标志位都会被置1,报文基本上都承担着对历史报文的确认工作PSH标志位,服务器端由于上层处理数据过慢,导致接收缓冲区的数据越来越多,最后满了,就会导致发送端发送不了数据,我们调用的write或者send接口会阻塞,发送端会轮询式地申请接收方的接收缓冲区大小,如果接收方上层迟迟没有拿取接收缓冲区的数据,那么发送端会向接收方发送PSH位被置1的报文,意思是催促接收方尽快拿去接收缓冲区的数据(当然如果接收方不执行任何动作发送方也没办法)。URG标志位,因为TCP报文中有序号,在接收缓冲区中可以保证数据按照发送顺序排列,那如果有特殊的数据想要插队呢?这就是URG标志位的用途,如果一段报文中涵盖了需要被尽快读取的数据,可以将URG标志位置1,注意,将URG标志位置1的报文中的有效载荷并不全是紧急数据,仅代表其中包括了紧急数据,那么这段数据的位置在哪里?查看报头中的16位紧急指针,有效载荷开头往后偏移16位紧急指针字节数就是这段有效载荷的位置,并且长度只有1字节。

        这里了解一下URG的使用场景。极大多数情况下URG标志位和16位紧急指针都不会被用到,一般使用这两个字段都是运维场景下,通过客户端查看服务端是否正常运行,有没有挂掉,如果通过常规的报文进行访问,此时服务端正在处理之前的报文,响应时已经过了很长的时间了,因此需要通过发送将URG置1的报文进行访问,这种数据叫做带外数据,带外数据不需要经过TCP流,服务端会优先处理这条数据,客户端也能最快地知道服务端的健康状态。一般这种带外数据都不是用于很复杂的业务,因此一个字节也就够用了。

        RST标志位。我们知道TCP建立连接的策略是三次握手,但是完成了三次握手,通信双方的连接不一定是建立好的,只是系统会认为连接建立好了,同样四次挥手也一样,完成了四次挥手,只是系统认为连接断开了,但是不一定真的断开了。上面这段话听起来有点抽象,举个例子,假设通信双方通过三次挥手建立好了连接,这个时候把服务器端主机的电源拔了,再次重启,打开服务器,此时服务器与客户端之间是没有连接的,而站在客户端的角度,双方是没有进行四次挥手的,客户端此时依旧认为连接还存在。在这种场景下,客户端会正常的给服务器发送报文,也不会将SYN标志位设置为1,服务器收到报文之后就很奇怪,我们没有建立过连接,你怎么就发送数据报文给我了呢?此时它会向客户端响应一个报文,这个报文中将RST标志位置1,通知对方,连接出现异常,需要重新建立连接,因此这个标志位也叫做复位标志位。

        进一步理解报头中的序号与确认序号。传输层工作的整体流程,上层调用write、send接口,将应用层缓冲区中的数据拷贝到发送缓冲区,再由传输层决定发送数据的大小,从发送缓冲区中提取数据再添加报头,发送到网络中,接收方收到来自网络中的带有报头的数据,将数据去报头,发送到接收缓冲区中,上层再调用read、recv接口将接收缓冲区中的数据拷贝到应用层缓冲区中。从应用层到达了发送缓冲区,缓冲区就是一块字符数组,每一个字节都有自己的数组下标,这个数组下标天然就是每一个字节的序号,发送数据时假如发送序号为1~1000的数据,TCP会直接拷贝这部分数据,添加一个报头,将报头中的序号设置为1000,构成完整报文发送到网络中去。

        这里讲一下TCP的超时重传机制。超时重传,一定是在数据丢包的场景下引发的,因此先讲讲丢包的集中常见情况。因为前面讲过TCP的流量控制策略,因此这里不考虑由于接收方接收缓冲区满了导致的丢包。这里考虑两种丢包的情况。(1)数据包在网络传输过程中真的丢失了,接收方没有收到过任何报文,所以接收方也不会主动地向发送端发送应答,发送方没有接收到应答,在经历一段特定的时间间隔之后,发送方会认为丢包了,然后进行超时重传,也就是重新发送相同的报文。(2)数据收发过程中,接收方成功收到了报文,也向接收方发送了应答,但是传输过程中,应答报文丢包了,站在发送方的角度来看是和第一种情况一样的,它会认为丢包了,继续进行超时重传。所以说,发送方进行超时重传动作其实并不在意数据是否真的丢包,站在它的角度,只要在特定时间内没有收到接收方的应答,就会进行超时重传。基于第二种情况的存在,接收方是可能重复收到同一份报文的,重复也是传输中不可靠的现象,TCP接收方也会根据报文的序号进行去重。为了支持超时重传,发送方发送了的数据,在一段时间内并不会直接移出发送缓冲区,会维持一段时间。那么发送端超时重传之前等待的时间是固定的吗?不是的,如果这段时间设置的过长,而网络情况又很好,有可能发送出去的数据早就丢包了,发送端还在一直傻等着,如果这段时间设置的太短,而网络情况又比较拥挤,数据可能还正在路上传输,发送端就进行超时重传了,这两种情况都是不合理的,因此这段时间一定是根据网络情况动态变化的。超时重传的具体策略一般是,以500ms为单位进行控制,每次判定超时重传的时间都是500ms的整数倍,如果重发一次之后,仍然得不到应答,等待2*500ms后再进行重传,如果仍然得不到应答,等待4*500ms进行重传,以此类推,累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接。

3、TCP连接管理机制

        关于三次握手的动作其实很简单,发送端发送将SYN置1的报文,接收端收到之后向发送端发送将SYN和ACK置1的报文,发送端收到之后在发送将ACK置1的报文做确认,三次握手完成。发送端和接收端在三次握手过程之中,也是由自己的状态表述的,在一开始双方都是closed状态,发送端发起第一次握手之后,更改为SYN_SENT状态,接收端收到并发起第二次握手之后,状态更改为SYN_REVD状态,发送端收到并发起第三次握手之后,状态更改为ESTABLISHED,接收端收到后状态也更改为ESTABLISHED。

        三次握手并不是我们站在上帝视角看到三次握手完成,认为通信双方连接同时建立成功,而是站在各自的视角看到不同的现象,站在发送端的视角,只要它接收到来自接收端的SYN/ACK报文,向接收端发送了ACK响应,就算这个响应正在路上没有到达接收端或者是直接丢包了,它也会认为连接建立成功。同理,接收端只要收到发送端的最后一次ACK响应就会认为连接建立成功。至于连接是否成功,连接能否继续保持,这个由TCP的一系列可靠性机制来支持。前两次的握手是需要应答的,因为有超时重传机制的存在,前两次握手出现丢包情况是不用担心的,而第三次握手是没有应答的,如果发送端发送第三次握手出现丢包,经过一段时间后接收端会超时重传,重新进行第二次握手,而发送端认为连接建立成功,会向接收端发送报文,接收端认为连接没有建立成功,会触发发送RST报文,重新建立连接,因此三次握手中不论哪一次出现异常都是由解决方案的。

        一款服务器,它可能会收到很多的连接请求,也一定会同时存在很多的连接,服务器端需要知道,哪些连接已经连上了,哪些正在通信,哪些正在重传,哪些正在RESET,哪些正在断开连接等等。所以操作系统对网络连接本事是要有一定的管理机制的。怎么管理?先描述,再组织,为连接设计对应的结构体,再通过特定的数据结构将其管理起来,因此维护连接是有成本的(时间和内存)。

        这里探讨一下,TCP建立连接设计成一次握手行不行?答案是当然不行,在这种情况下,服务器只要收到来自客户端的SYN报文,就认为连接建立好了,随即为连接建立内核数据结构并管理起来,这注定了客户端只需要一台机器就可以频繁地向服务器不断SYN请求,不断地蚕食着服务器的内存资源,消耗着服务器的时间,意味着一台机器就可以不断地攻击服务器直到把它搞垮。那么将TCP建立连接设计成两次握手行不行呢?跟上面的情况一样,服务端收到客户端的报文请求之后,向客户端发送SYN/ACK,就认为连接建立好了,接着为连接创建内核数据结构并管理起来,并不考虑客户端有没有应答,这也是不合理的。上面是一种SYN洪水的问题。

        根据上面的SYN洪水问题验证一下TCP建立连接设计为三次握手的合理性。(1)首先,三次握手是证明全双工通信信道是通常的的最小成本,用最少的握手次数,确定了通信双方都具有发送和接收消息的能力。(2)三次握手的最后一次握手是客户端向服务端发送ACK报文,服务端收到ACK报文之后才确认连接建立成功,在此之前客户端已经确认连接建立成功了,也就是说,在服务端为建立好的连接申请资源之前,客户端要先为自己建立好的连接申请资源,这样依赖,如果有不法分子通过一台主机多次向服务端发起连接建立请求,他的主机本身也要承受建立连接导致的消耗,大大提高了不法分子犯罪的成本。

        TCP面向连接,连接本质上是由实体的结构体承载的,每一个连接通信双方都需要为其维护相关的连接结构体,TCP保证可靠性,哪些报文丢失,当前的连接是处于新建状态还是通信状态还是断开状态,哪些报文丢失了,哪些包围已经超时准备重传,这些信息都是要维护在TCP连接结构体里面的,包括三次握手期间双方的CLOSED、SYN_SENT、SYN_RECV状态都是由连接结构体维护的,是由位图和宏支持的。连接结构体是TCP保证可靠性的数据结构基础,而三次握手是创建连接结构体的基础。

        关于四次挥手的通信流程其实很简单,先是客户端向服务器发送断开连接请求FIN,服务器收到之后发送ACK确认,之后发送FIN报文给客户端(这个动作可能与ACK合二为一),后续收到来自客户端的ACK确认之后,四次握手完成,双方断开连接。

        客户端和服务器双方,任何一方都可以主动断开连接。假设客户端主动断开连接,只要它把FIN报文发出,它对应地连接结构体里的连接状态就会自动变成FIN_WAIT_1,服务器收到FIN报文并发送ACK之后,服务器的连接状态就会置为CLOSE_WAIT,服务器发出FIN后连接状态置为LAST_ACK(最后确认),客户端收到报文并且发送ACK之后状态置为TIME_WAIT状态,后续服务器收到ACK后服务器状态为CLOSED。主动断开连接的一方,最终状态是TIME_WAIT状态,被动断开连接的一方,两次挥手完成,会进入CLOSE_WAIT状态

        这里谈一下TIME_WAITCLOSE_WAIT两种状态。客户端发起断开连接求,即客户端代码调用close接口,而服务器不调用close接口,那么断开连接这个动作只进行了两次挥手,服务器会一直处于CLOSE_WAIT状态。如果服务器出现了大量的处于CLOSE_WAIT状态的连接,一般是一下两种原因导致的,(1)服务器有bug,没有做close文件描述符的动作。(2)服务器有压力,可能一直在向客户端推送消息,导致来不及close。主动断开连接的一方,即使已经完成了四次挥手,但会在一段时间内维持在TIME_WAIT状态下。四次挥手中的前三次挥手,不论通信双方的连接状态如何,其实连接都是存在的,如果发生报文丢包,由于确认应答和超时重传机制的存在,其实不会造成什么影响,如果客户端发起最后一次挥手之后,系统直接将连接状态更改为CLOSED,那么最后一次挥手假如丢包,站在服务器的角度会重新发送第三次挥手,此时客户端收不到来自服务器的任何报文,服务器收不到第四次挥手,也就无法断开连接,这是一种故障,所以TCP设计了客户端发起第四次挥手之后连接状态处于TIME_WAIT,这个状态下是没有断开连接的,客户端依然可以收到来自服务器的报文。TIME_WAIT维持的时间一般是2*MSL(MSL是通信时消息从一段到达另一端的最大时间)这个2*MSL设置的含义是最后一次挥手的传输时间和假如丢包了服务器重新发送的第三次挥手的传输时间,尽可能地保证连接正常关闭。还有一点,客户端发起最后一次挥手时,网络中可能还有滞留的报文,设置TIME_WAIT时间也是保证网络中报文能被正常收到。这也解释了我们自己写的服务器,有时候关闭之后再立即启动会失败,是因为,如果服务器是发起断开连接的一方,那么在四次挥手完成之后,连接会维持一段时间的TIME_WAIT状态,在这段时间里连接依旧存在,端口依旧被占用,因此无法打开绑定相同端口号的相同的服务器进程。

4、TCP滑动窗口

        之前有讲,为了支持超时重传,任何一方发送数据,在收到应答之前,必须将自己已经发送的数据暂时保存到发送缓冲区中。一发一收的方式性能较低,实际发送时发送方可一次会发送多条数据(没有收到确认应答就发送),就可以大大提高性能,实际上时将多个数据段的等待时间重叠到一起了。

        发送方的发送缓冲区,从使用的角度来讲,可以分为三部分,已经发送且已经收到应答的部分,已经发送但是没有收到应答的部分,数据尚未发送的部分。应用层拷贝数据到发送缓冲区,实际上时拷贝到数据尚未发送的部分后面的空白部分。其中我们将已经发送但没有收到应答的部分称为滑动窗口。发送缓冲区本质上是一个字符数组,滑动窗口只是字符数组中的一部分,它是由数组下标来维护的,假设滑动窗口的起始下标是win_start,结尾是win_end,滑动窗口的移动本质就是两个下标数值的更新。假如滑动窗口的win_start是1001,此时发送方收到接收方的确认序号2001,代表序号2001以前的所有数据已被接收,那么滑动窗口整体向右移动1000。

        滑动窗口的大小和对方的接受能力有关,我们目前可以认为滑动窗口大小=对方通告给我的自己的接收能力大小,即win_end=win_start+tcp_win(tcp_win指对方发送报文中16位窗口大小)。win_start更新依据接收方发送的确认序号ACK_SEQ,收到确认序号之后,win_start=ACK_SEQ,win_end=win_start+tcp_win,在对方发送的ACK报文中,确认序号和16位窗口大小是同步更新的。如果接收方上层不处理数据而发送方又一直发送数据,最终接收方的接收缓冲区剩余空间会逐渐变为0,并且接收方一直给发送方发送确认,滑动窗口的win_start会一直右移,直到滑动窗口大小逐渐变为0。发送缓冲区通过特定的算法被内核组织成了环形结构,所以我们不用担心滑动窗口在一直向后滑动的过程中发生滑到头的问题。

5、TCP网络拥塞控制

        TCP通信时,如果发送一万条报文,有一两条丢包了,这是正常现象,发送方会基于确认应答和超时重传机制重新发送报文,如果丢失报文过多,系统会判定这是路上网络的问题。TCP的可靠性不仅考虑了双方主机的问题,也考虑了路上网络的问题。如果丢包过多,TCP不会重传,此时网络状态可能就已经比较拥堵,重传会加重网络的故障问题。

        TCP引入慢启动机制,先发少量的数据,探探路,摸清当前网络的拥堵状态,再决定按照多大的速度传输数据。此处引入一个概念叫做拥塞窗口,拥塞窗口时发送端主机定的一个数字,当发送报文大小超过拥塞窗口时,可能就会发生网络拥塞的问题,拥塞窗口用于表征网络的吞吐能力。

        引入了拥塞窗口的概念,这里要更正一下滑动窗口的概念,之前再滑动窗口的讲解中提到,发送端滑动窗口的大小等于接收端发来报文的16位窗口大小,这是在没有考虑到拥塞窗口的情况下的理解。现在发送端不仅要考虑接收端的接收能力,也要考虑网络的接收能力,滑动窗口大小=min(拥塞窗口,16位窗口大小)。

        慢启动的策略是指拥塞窗口前期比较小,但是增长速度非常快。具体策略是拥塞窗口前期按照指数方式增长,达到某一个阈值之后按照线性方式增长,拥塞窗口不会无限增长下去,在未来某个时间点可能就会发生网络拥塞,从慢启动开始到发生网络拥塞这段时间称为一次拥塞窗口增长的周期,发生网络拥塞时的窗口大小的0.5倍会作为下一个周期内拥塞窗口增长的阈值。

6、延迟应答机制

        假设接收端缓冲区为1M,一次收到了500K的数据,如果立刻应答,返回的窗口就是500K,但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区处理掉了,在这种情况下,如果接收端稍微等一会再应答,可能返回的窗口大小就是1M。窗口越大,网络吞吐量就越大,传输效率就越高,TCP的目标就是在保证网络不拥塞的情况下尽量提高传输效率。当让也不是所有的包都可以延迟应答,延迟应答是有时间和数量限制的,每隔N个包就应答一次,超过最大延迟时间就应答一次,一般N取2, 超时时间取200ms,注意延时应答的时间是不可以超过超时重传的时间,一旦超过这个时间,发送端会认为报文丢失,这本身是有bug的。

7、TCP连接队列

        有些餐厅会在外面摆上许多凳子,供顾客进行排队,这要有哪一桌的人吃完了,就会安排排队的人进去吃饭。排队的本质是让我们资源有空闲的时候,可以立马使用,提高资源的利用率。但是队列不能设置的太长,没有客户会为了服务等待过久的时间。

        TCP协议同样为上层维护了一个全连接队列。这个队列里全都是建立好的连接,但是没有参与上层业务,等上层处理业务的连接走了,就会有新的连接进入上层,参与相关业务。写代码时,我们调用的accept接口,就是执行将连接带入上层的动作,从侧面也说明了,即使不调用accpt,连接也早已建立好了,accept只是获取连接的接口。

  • 23
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值