传输层:TCP协议

本文详细介绍了TCP协议的特性,包括其可靠性、报文格式、确认应答机制、超时重传、连接管理、滑动窗口、流量控制和拥塞控制。重点讨论了TCP如何通过序号和确认序号确保数据的正确传输,以及通过滑动窗口优化传输效率。此外,还提到了TCP与UDP的区别,以及如何在UDP中实现可靠传输的基本策略。
摘要由CSDN通过智能技术生成

传输层中有两个重要的协议:TCP协议和UDP协议。本博文分享的是TCP协议,不仅分享其协议格式,特点等等,还有应答机制、超时传送机制、连接管理机制、滑动窗口、阻塞控制等等。

TCP协议

TCP全称为 "传输控制协议(Transmission Control Protocol")。人如其名, 要对数据的传输进行一个详细的控制。

tcp的特点

可靠性,面向连接等等。

TCP协议格式

在TCP协议格式中,其数据报包含了:

①16位源端口号、16位目的端口号

②32位序号,32位确认序号

③四位TCP报头长度

④六位标志位

⑤16位窗口大小

⑥16位校验和

  

⑦16位紧急指针

⑧40字节的头部选项

⑨数据

16位源端口号、16位目的端口号

表示数据是从哪个进程来, 到哪个进程去。

32位序号,32位确认序号/确认应答机制

TCP具有可靠性的特点,这个可靠性该如何去理解?对于TCP的可靠性,其核心的机制是基于序号的确认应答机制!接下来我们好好分析一下确认应答机制是怎么样的。

当客户端向服务器发送消息后,服务端需要返回消息表示已经收到,当客户端收到来自服务器的消息(表示服务端已经收到了客户端的消息了),那么客户端就再发一条应答信息回去......这样一来一回的向对方发送一条确认收到信息的行为,便是确认应答机制。

通过应答,发送方就可以保证自己的信息被对方百分之百的收到了。

但是这样的行为,我们都可以理解想明白,tcp的这种可靠性,并不是百分之百的可靠,但是有一点我们必须清楚的认识到:只要一条消息有应答,我们就能确认该消息被对方百分之百收到了

在一般情况下,客户端会向服务端发送多条数据(报文),而服务端收到的数据,其顺序不一定是按照客户端发来的顺序是一致的,为了能够让客户端发送的信息和服务端接收到信息后能够返回对应的应答,就需要依靠报头中的32位序号,32位确认序号!

序号是表示这个是第几个,按顺序嘛。而确认序号是用于接收方给发送方一个应答,无论接收来的顺序是否跟发送的顺序一致不一致,有了确认序号就可以保证匹配得上相应的数据应答。

确认序号是使用方法是对历史确认报文的序号+1。比如说:客户端向服务端发送了序号为10,11和12的数据报,服务端接收后,就给客户端发送确认序号为13的数据报(不管有没有带有效载荷),表示13号之前的报文我服务端全部收到啦!

在tcp的报头中为什么要把序号和确认序号分开呢?因为无论是数据还是应答,本质都是TCP的完整报文,这个报文可以不带数据,但是必须具有一个完整的tcp报头。而tcp是一个全双工通信协议!客户端可以向服务端发送数据,带上序号的,同样的是,服务端在应答客户端的某条数据时,也能带上自己的序号,带上自己的数据发送给客户端!

那么序号从哪里来的呢?TCP将每个字节的数据都进行了编号,即为序列号。其实对于发送缓冲区和接收缓冲区来说,缓冲区是一个数组,因为TCP是面向字节流的,所有数据会拷贝到这个数组中,因此其下标便是序列号。
 

四位TCP报头长度

4位tcp的报头长度,即4位首部长度,转化为二进制即【0000,1111】-->【0,15】的范围。而首部的单位是4字节,tcp标准长度是20字节,因此,长度最大x的时候是x*4 = 20,即x = 5。因此,tcp报头长度为5,即0101。

16位窗口大小

tcp协议是自带发送缓冲区和接收缓冲区的,因为读写的接口比如write/read,recv/send我们可以理解为拷贝函数,比如在应用层中我们进行send的时候,并没有把数据直接发送到网络中,而是把数据拷贝到了tcp的发送缓冲区中了。

这样的好处:

①提高应用层的效率。

②做到应用层和TCP的解耦。只有tcp协议才可以知道网路,对方的状态明细,因此也只有tcp协议能处理数据如何发,怎么发,发多少,出错了怎么办等等的问题,真正意义上做到传输控制!而因为缓冲区的存在,应用层只需把数据交给缓冲区即可,其它的事情不需要去管!就好像我们寄快递,只需将快递写好单子交给快递公司即可,快递公司怎么寄过去不需要我们来管了。

缓冲区是有大小的,等满了就会刷新,那么在还没有满之前,发送方需要得知接收方的接收缓冲区目前的剩余空间大小,来决定如何去发送数据,因此在tcp报头中,就可以使用16为窗口大小来填入接收方目前的接收缓冲区剩余的空间大小,然后作为应答返回给发送方

六个标志位

tcp是面向连接的,那么在通信之前就需要connect连接,而连接的操作,便是三次握手,关闭连接的操作便是四次挥手。而且,对于服务器来说,服务端有可能在任意时刻,都有成千上万的报文向其发送,因此服务端面临的是需要知道如何将这些成千上万的报文进行区分。

对于三次握手四次挥手,区分大量的报文类型,所用到的便是数据报中的标志位!所用到的标志位,就将其置为1,用不到置为0

六个标志位:URG、ACK、PSH、RST、SYN、FIN

①ACK:ACK即是应答,几乎在所有的TCP通信过程中,ACK标志位都会被设置起来。

②SYN:请求建立连接; 我们把携带SYN标识的称为同步报文段。

请求建立连接的动作:三次握手。第一次握手:客户端向服务器发起请求SYN;第二次握手:服务器应答客户端,并且也向客户端请求建立连接;第三次握手:客户端应答服务端。

③FIN:通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段。

关闭连接的动作:四次挥手。第一次挥手:客户端通知服务端进行连接关闭FIN;第二次挥手:服务端应答(ACK)客户端;第三次挥手:服务端反过来通知客户端进行连接关闭FIN;第四次挥手:客户端应答(ACK)服务端。

④RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段。

在请求建立连接的时候,三次握手不一定会成功。在第一次握手时,服务端会应答,第二次握手时,客户端会应答,第三次握手时不会有应答,因此第三次握手出现错误时,会导致异常连接。只要出现异常连接,那么可以使用RST来重新建立连接。

建立连接时,服务器一般存在大量的连接,因此服务器需要管理这些连接。连接的本质是在三次握手成功之后,在通信双方的操作系统内都创建了对应的用于维护这些连接的数据结构!这说明了双方维护连接都是需要成本的(空间+时间)。

⑤PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走。

⑥URG和16位紧急指针: 紧急指针是否有效。因为TCP是有序到达的,每一个报文,什么时候被上层读取是基本上确定的了,但是如果想要让一个数据尽快被上层读取到,那么可以设置URG,表示该报文携带了紧急数据,需要被优先处理。设置了URG后,16位紧急指针就会指向报文数据中一个字节的数据,因此紧急指针只能传输1个字节。

为什么是三次握手?

三次握手为什么是三次握手,而不是两次四次五次六次等等?

三次握手是为了确保双方建立可靠的连接,说白了就是双方都需要确认对方好着没,对方的主机是否健康,而三次握手是最小化的可靠连接建立过程。如果只进行两次握手,那么会存在一种情况,即客户端发送的SYN包到达服务器后,但是由于某种原因(比如网络延迟等等),服务器没有及时应答(SYN+ACK)客户端,客户端就会认为连接建立失败,会重新发送SYN包,此时服务器就会收到两个SYN包,会误认为客户端想要建立两个连接,并且第二次握手到达客户端后,客户端没有应答给服务端,服务端并不知道自己的这个应答对方有没有收到,因此服务端并不确定自己是否拥有发送数据的能力!如果建立四次或以上的握手,那么会增加连接建立的时间和网络负载,第四次握手的没有必要的。三次握手是最小化的可靠连接建立过程,可以保证连接的可靠性和效率。

因此,三次握手可以:1.确认双方主机是否健康。2.验证双全工(双方都具有发送和接收的能力)

为什么是四次挥手

跟三次挥手一样,四次挥手是协商断开连接的最小次数。

TIME_WAIT和CLOSE_WAIT

一般来说,主动断开连接的那一方,要进入一个TIME_WAIT状态,此时对于主动断开连接的这一方来说,四次挥手已经完成了,但是连接并没有被释放,端口号依然被占用中,这说明在一段时间内这个端口号无法被重启!

理解TIME_WAIT状态

TCP协议规定,主动关闭连接的一方要处于TIME_ WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态。

TIME_WAIT为什么是两个MSL的等待时间?

①尽量历史发送的网络数据在网络中消散。MSL是TCP报文的最大生存时间, 因此TIME_WAIT持续存在2MSL的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的)。

②尽量保证最后一个ACK被对方收到。同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失, 那么服务器会再重发一个FIN. 这时虽然客户端的进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK)。

解决TIME_WAIT状态引起的bind失败的方法(作业)

如果一个端口号在四次挥手后,短时间内无法重启,会造成一些经济上的损失,比如如果某宝在双十一中,有上千万的用户同时连接了某宝的服务器,如果,我是说如果,如果某宝的服务器此时崩了,服务端与客户端的连接断开,但是由于服务端的状态变成了TIME_WIAT,无法立刻马上重启,需要等到2*MSL的时间,要知道,在双十一,一秒钟都是上千万的交易!由此可知,我们需要解决无法立刻重启的问题。

使用setsockopt()设置socket描述符的 选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符。

理解 CLOSE_WAIT 状态

如果在关闭连接的时候,比如客户端发起了关闭连接请求,服务端应答后,但是服务端并没有调用close去关闭连接,此时服务器就会进入CLOSE_WIAT状态,该状态表明四次挥手没有完成。

总结三次握手四次挥手

服务端状态转化:

[CLOSED -> LISTEN] 服务器端调用listen后进入LISTEN状态, 等待客户端连接。

 
[LISTEN -> SYN_RCVD] 一旦监听到连接请求(同步报文段), 就将该连接放入内核等待队列中, 并向客户端发送SYN确认报文。

 
[SYN_RCVD -> ESTABLISHED] 服务端一旦收到客户端的确认报文, 就进入ESTABLISHED状态, 可以进行读写数据了。

 
[ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用close), 服务器会收到结束报文段, 服务器返回确认报文段并进入CLOSE_WAIT。

 
[CLOSE_WAIT -> LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据)。

 

当服务器真正调用close关闭连接时, 会向客户端发送FIN, 此时服务器进入LAST_ACK状态, 等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)。

 
[LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK, 彻底关闭连接。

客户端状态转化:

[CLOSED -> SYN_SENT] 客户端调用connect, 发送同步报文段。

 
[SYN_SENT -> ESTABLISHED] connect调用成功, 则进入ESTABLISHED状态, 开始读写数据。

 
[ESTABLISHED -> FIN_WAIT_1] 客户端主动调用close时, 向服务器发送结束报文段, 同时进入FIN_WAIT_1。

 
[FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认, 则进入FIN_WAIT_2, 开始等待服务器的结束报文段。

 
[FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段, 进入TIME_WAIT, 并发出LAST_ACK。

 
[TIME_WAIT -> CLOSED] 客户端要等待一个2MSL(Max Segment Life, 报文最大生存时间)的时间, 才会进入CLOSED状态。

超时重传机制

当主机A向主机B发送数据时,这个数据丢包了,那么在一定的时间内主机A没有收到主机B的确认应答,那么就会进行重传。

也有可能是主机B发送来的确认应答(ACK)丢包了,此时主机B就会收到很多重复的数据,那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉。这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果。

如果超时的时间如何确定?

最理想的情况下, 找到一个最小的时间, 保证 "确认应答一定能在这个时间内返回"。但是这个时间的长短, 随着网络环境的不同, 是有差异的。如果超时时间设的太长, 会影响整体的重传效率。如果超时时间设的太短, 有可能会频繁发送重复的包。

TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间。

滑动窗口

如果在发送报文的时候,一条一条的发,每发一条,接收方就应答一次,这样相当于串行的方式,效率比较低,解决方法很容易想到,就是一次性发送很多条(实是将多个段的等待时间重叠在一起了),这就用到了滑动窗口!

窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。上图的窗口大小就是4000个字节(四个段)

一次发送多少?

一次性给接收方发送多条数据,就会自然而然地会有一个问题:一次发送多少才合适?

一句话:看对方的接收缓冲区。对方的接收缓冲区还能接收多少数据,那么你就可以一次性发多少数据。

在发送或接收数据的时候,发送数据本质上是将需要发送的数据先保存在自己的TCP发送缓冲区中,接收数据本质上是将接收到的数据保存在TCP的接收缓冲区中。

在发送缓冲区中,发送缓冲区可以分成三个区域:

第一个区域,存放了已经发送和已经确认应答的数据。

第二个区域,存放了已经或者可以发送的,但是还没有确认应答的数据。

第三个区域,存放的是还没被发送,或者还不可以发送的数据。

其中,第二个区域,就称为滑动窗口!因此,滑动窗口是一个名词,是发送缓冲区中的一个区域,本质就是发送缓冲区。

在上文中,我们讲述过16位窗口大小表示的是接收缓冲区中剩余空间的大小。

总之,滑动窗口,与对方的接受能力有关!

滑动窗口怎么滑?

因为TCP的缓冲区是一个数组,那么该区域(滑动窗口)就由两个指针去指着,假设左边指针叫win_start,右边指针叫win_end。

每当收到一个ACK时,需要根据对方接收缓冲区的接收能力,即对方报文中的16位窗口大小来判断该怎么滑动。

①左边指针wen_start往右移动,右边指针win_end不动。出现这种情况,一般就是当发送方发送数据A之后,接收方接收并返回ACK时,16位窗口大小显示对方剩余的空间比发送数据A时的小。比如发送数据A前,对方接收缓冲区还剩4KB,发送后,还剩3KB。那么此时,win_end不需要动了,甚至于,发送数据A后,还剩的空间为0。这样说明了滑动窗口的大小是可以改变的。

②左右指针都移动了。比如说,发送数据A时,对方接收后,缓冲区满了,然后上层应用刷一下,把缓冲区的数据全刷走了,此时接收缓冲区空了!此时,发送缓冲区中,左右指针都往右移动了!

滑动窗口发送数据发生丢包了怎么办?

如果出现了丢包, 如何进行重传? 这里分两种情况讨论。

情况一:数据包已经抵达, ACK被丢了。

这种情况下, 部分ACK丢了并不要紧, 因为可以通过后续的ACK进行确认。因为即使1001,3001和4001的AKC丢了,后续因为5001和6001都确认应答了,而6001ACK的意思是6001序号之前所有数据,我(接收方)都已经成功接收了!

情况二: 数据包就直接丢了。

如果有一个数据包丢了,比如上图中1001-2000的数据丢了,那么在后续的发送数据中,会进行重传,并且在后续的ACK中,确认序号都为1001。即使2001到7000的数据都成功发送,确认序号始终是1001,直到重传后1001-2000成功发送,这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中,这种机制被称为 "高速重发控制"(也叫 "快重传")。

快重传和超时重传的区别

快重传和超时重传都是TCP协议中的重传机制,用于保证数据传输的可靠性。它们的区别在于触发重传的条件和重传的时机。

快重传是当发送方收到了重复的确认应答,就立即重传丢失的数据报。超时重传是当发送方发送了一个数据包后,一段特定的时间内没有接收到确认应答,就重新发送数据包。

总结:快重传是基于接收方的反馈来触发重传的,而超时重传是基于发送方的超时计时器来触发重传的。快重传可以更快地发现数据包的丢失,但是需要接收方发送重复确认应答,增加网络负载。超时重传可以避免网络拥塞,但是需要时间去等待,可能会导致网络延迟。

流量控制

接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应。因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control)。

那么在第一次发送数据的时候,怎么知道接收端的接收缓冲区的接受能力呢?其实在三次握手的时候,就已经协商好了窗口大小了,因此在第一次发送数据的时候,就可以根据这个窗口大小来设置自己的滑动窗口的初始值。

拥塞控制

虽然TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题:因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵,在不清楚当前网络状态下, 贸然发送大量的数据,是很有可能引起雪上加霜的,比如一个场景:

场景:一个服务器,跟许许多多个客户端连接起来,那么假设有一个客户端发送了1千条数据,但是有999条数据发送失败,丢了!那么,这样很大可能,连其他的客户端也是如此!在这种情况下,如果这些客户端进行快重传或者超时重传,一下子有许许多多个客户端同时发送了999条数据,服务器就会崩溃掉!因此,拥塞控制可以出场了!

TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据。

上文中,我们一直讨论的,是两台主机之间的通信问题,但是此时我们需要加上网络,即两台主机之间的网络。发送方发送数据到接收方中,先会通过网络,再到达接收方,那么在网络中,有一个叫做拥塞窗口的东西,每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口。

因此,对于发送方的滑动窗口来说,上文我们说了它是跟接收方的接收缓冲区的接收能力有关,现在,它也跟拥塞窗口有关。

滑动窗口 = min(拥塞窗口大小,16位窗口大小)。

慢启动

在上图中,我们看到,可以发送的数据越来越多,像上面这样的拥塞窗口增长速度, 是指数级别的. "慢启动" 只是指初使时慢, 但是增长速度非常快,但是并不能让它无限增长,因此慢启动有一个阈值,当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长

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

因此,拥塞控制可以控制每次的重传,让其不能马上就重传太多的数据,导致服务器崩溃。

总结一下:

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

延迟应答

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

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

一般而言,不会什么情况都会采用延迟应答,一般是以下两种情况才会采用延迟应答:

数量限制: 每隔N个包就应答一次。
时间限制: 超过最大延迟时间就应答一次。

具体的数量和超时时间, 依操作系统不同也有差异; 一般N取2, 超时时间取200ms。

捎带应答

在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应用层也是 "一发一收" 的。意味着客户端给服务器说了 "How are you", 服务器也会给客户端回一个 "Fine, thank you"。那么这个时候ACK就可以搭顺风车, 和服务器回应的 "Fine, thank you" 一起回给客户端。

面向字节流

创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区。

调用write时, 数据会先写入发送缓冲区中。如果发送的字节数太长, 会被拆分成多个TCP的数据包发出。如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去。

 
接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区,然后应用程序可以调用read从接收缓冲区拿数据。

 
另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据。这个概念叫做 全双工。

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

写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节。

 
读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次。

粘包问题

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

解决粘包问题,本质就是明确两个包之间的边界。

对于定长的包, 保证每次都按固定大小读取即可,例如上面的Request结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可。对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置。对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔符不和正文冲突即可)。

对于UDP协议来说,如果还没有上层交付数据, UDP的报文长度仍然在. 同时, UDP是一个一个把数据交付给应用层. 就有很明确的数据边界,站在应用层的站在应用层的角度, 使用UDP的时候, 要么收到完整的UDP报文, 要么不收,不会出现"半个"的情况。

TCP异常情况

进程终止: 进程终止会释放文件描述符, 仍然可以发送FIN. 和正常关闭没有什么区别。

机器重启: 和进程终止的情况相同。

机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset。 即使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在。 如果对方不在, 也会把连接释放。

另外, 应用层的某些协议, 也有一些这样的检测机制. 例如HTTP长连接中, 也会定期检测对方的状态. 例如QQ, 在QQ断线之后, 也会定期尝试重新连接。

TCP/UDP对比

TCP和UDP是两个极端。TCP是出现了就重发,会尽量地保证其可靠性,而UDP是只负责发,出错了就不要找它。

TCP用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景。

 

UDP用于对高速传输和实时性要求较高的通信领域, 例如, 早期的QQ, 视频传输等. 另外UDP可以用于广播。

一般而言,UDP在视频传输的适合,如果我们网速不好,它一般会直接降低画质,来达到稳定的通信,就好比如我们在看视频,视频画质选择自动的适合,本质上就是选择了UDP协议。

关于使用TCP还是UDP,没有准确的说法,不能认为TCP具有可靠性而UDP没有可靠性,就不去使用UDP。归根结底, TCP和UDP都是程序员的工具, 什么时机用, 具体怎么用, 还是要根据具体的需求场景去判定。

用UDP实现可靠传输

这个问题其实很好回答,我们想想TCP的可靠性是怎么样去实现可靠的,不就是有序列化,保证数据传输有顺序,有确认应答,保证数据被接收,有超时重传,让丢包的数据重新发送,有滑动窗口流量控制,避免网络拥塞等等。

那么,要是UDP想要实现可靠传输,我们可以引入序列号、确认序号、超时重传等等。

全连接队列和半连接队列

实际上,服务器能够同时连接客户端的个数是有限制的,比如在设置服务器监听状态时,我们让listen 的第二个参数设置为 2, 并且不调用 accept,此时服务器最多能够同时连接3个客户端。如果此后再有客户端想要连接的话,其状态会被标志为SYN_RECV,而不是ESTABLISHED 状态。

这是因为, Linux内核协议栈为一个tcp连接管理使用两个队列:

1. 半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求)。

 
2. 全连接队列(accpetd队列)(用来保存处于established状态,但是应用层没有调用accept取走的请求)。

全连接队列的长度会受到 listen 第二个参数的影响,全连接队列满了的时候, 就无法继续让当前连接的状态进入 established 状态了,全连接队列的长度是listen第二个参数+1。

全连接队列能够没有吗?

如果没有全连接队列的话,会让需要连接的客户立马连接上来,不给服务端缓冲的机会,如果服务端满了,或者处于负荷状态,一旦有大量的客户立马连接上路,服务端就会崩溃!

如果有全连接队列,可以有效的进行网络管理,也能让服务器的资源有效地利用起来,不会空缺一个位置出来,白白浪费时间等待客户端的到来,而是可以直接让在全连接队列中排队的客户端直接来使用。

为什么要维护全连接队列?

维护好了全连接队列,可以方便管理和监控网络连接,及时发现和解决问题。

为什么全连接队列不能太长?

如果队列太长,会占用大量的内存资源,导致系统性能下降,还会增加网络延迟。就好比如,我们在排队买东西,买辣条,如果队很长很长,那我们还去买吗?除非是必须要的,否则我就直接走人了,客户端也是如此。因此,全连接队列是一个短队列。

TCP小结

TCP复杂是因为需要保证可靠性的同时,又尽可能地提高性能。

可靠性体现在:

序列号(按序到达):保证数据的传输顺序。

 
确认应答:保证数据能够到达接收端。

 
超时重发:如果发送方在一段时间内没有接收到确认应答,就会重新发送数据报。

 
连接管理:三次握手和四次挥手。

 
流量控制:避免发送方发送过多的数据导致网络拥塞,接收方会发送16位窗口大小,告诉发送方它的接收能力,发送方根据这个信息来控制发送速度。

 
拥塞控制:使用慢启动机制,发送速度一开始是指数增长,后面是线性增长,这样就可以避免一开始发送过多数据导致网络拥塞,同时使用阈值来控制滑动窗口的大小。

 
校验和

提高性能体现在:

滑动窗口:一次发送多条数据,不需要一发一答,可以提高效率。

 
快速重传:发送方发现收到了重复的确认消息,就会马上重传丢失的数据报。

 
延迟应答:上层用户可能在数据到达后,立马清空接收缓冲区或者是缓冲区的数据被拿掉了一大部分。在此之前先不要应答,等接收缓冲区清了后,再应答,可以告诉发送方,接收方有很大的一块空间用于接收数据,以此提高效率。

 
捎带应答:让ACK应答搭上顺风车,在发送数据的适合带上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山雾隐藏的黄昏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值