当前位置: 首页 > news >正文

【计算机网络】传输层TCP协议

文章目录

  • 认识TCP协议
  • TCP协议的格式
    • 字段的含义
    • 序号与确认号
    • 六个标志位
    • 窗口大小
  • 确认应答(ACK)机制
  • 超时重传机制
  • 连接管理机制
    • 三次握手
    • 四次挥手
  • 滑动窗口
  • 流量控制
  • 拥塞控制
  • 延迟应答
  • 捎带应答
  • 面向字节流
  • 粘包问题
  • TCP异常情况
  • 总结


认识TCP协议

传输控制协议 (TCP,Transmission Control Protocol)是一种面向连接、可靠的、基于字节流的传输层通信协议,由IETF的RFC 792 定
义。

TCP协议旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。

TCP协议是当前应用最广泛的传输层协议,其根本原因在于它具有可靠性,基于TCP应用层协议有HTTP、HTTPS、SSH、Telnet、FTP、SMTP等。

TCP协议最大的特点就是面向字节流,

TCP协议的格式

字段的含义

  • 源端口和目的端口:源端口号表示数据从那个进程来,目的端口号表示数据到哪里去。
  • 32位序号与32位确认序号:32位序号表示报文中各个数据的编号,32位确认序号表示接收方对已经接收数据的确认,二者保证了TCP协议通信的可靠性。
  • 4位TCP头部长度:表示TCP报头的长度,以4字节为单位,一般为20字节,最长为60字节(20字节的固定首部加上加上选项的大小)。
  • 6位保留字段:TCP报头中暂未被使用的6个比特位。
  • 6位标志位:区分报文类型。
  • 16位窗口大小:保证TCP通信的可靠性和效率的字段。
  • 16位校验和:包括TCP报头和TCP数据两部分,由发送端填充,采用CRC校验。如果接收方校验失败,则认为接收到的数据有误。
  • 16位紧急指针:标识紧急数据在报文中的偏移量,需要配合标志位中的URG标识使用。
  • 选项字段:TCP报头中允许额外携带的字段,最大为40字节。

序号与确认号

32位序号:

如果双方在进行数据通信时,只有收到了上一次发送数据的响应才能发下一个数据,那么此时双方的通信过程就是串行的,效率可想而知。

因此双方在进行网络通信时,允许一方向另一方连续发送多个报文数据,只要保证发送的每个报文都有对应的响应消息就行了,此时也就能保证这些报文被对方收到了。

但在连续发送多个报文时,由于各个报文在进行网络传输时选择的路径可能是不一样的,因此这些报文到达对端主机的先后顺序也就可能和发送报文的顺序是不同的。但报文有序也是可靠性的一种,因此TCP报头中的32位序号的作用之一实际就是用来保证报文的有序性的。

TCP将发送出去的每个字节数据都进行了编号,这个编号叫做序列号。

比如现在发送端要发送3000字节的数据,如果发送端每次发送1000字节,那么就需要用三个TCP报文来发送这3000字节的数据。此时这三个TCP报文当中的32位序号填的就是发送数据中首个字节的序列号,因此分别填的是1、1001和2001。

当接收方收到这三个报文之后,就会根据其中的序列号进行排序,排序完成后再放入TCP缓冲区中,这样就能保证发送出去的和接收到的数据顺序保持一致。

32位确认号:

TCP报头当中的32位确认序号是告诉对方,当前已经收到了哪些数据,并且数据下一次应该从哪里开始发送

以上文的例子为例,当主机B收到主机A发送过来的32位序号为1的报文时,由于该报文当中包含1000字节的数据,因此主机B已经收到序列号为1-1000的字节数据,于是主机B发给主机A的响应数据的报头当中的32位确认序号的值就会填成1001。

  • 一方面是告诉主机A,序列号在1001之前的字节数据我已经收到了。
  • 另一方面是告诉主机A,下次向我发送数据时应该从序列号为1001的字节数据开始进行发送。

如果报文在传输过程中丢失了,例如最终只要序号为1和2001的报文被主机B收到,那么当B对报文进行排序的时候,就会发现少了1001 ~ 2000 之间的数据,那么此时向主机A响应的报文中的32位确认序号的值就是1001,告诉主机A下次再次发送1001开始的数据。

【注意】
如果此时主机B在给主机A响应时,其32位确认序号不能填3001,因为1001-2000是在3001之前的,如果直接给主机A响应3001,就说明序列号在3001之前的字节数据全都收到了。因此主机B只能给主机A响应1001,当主机A收到该确认序号后就会判定序号为1001的报文丢包了,此时主机A就可以选择进行数据重传。

TCP报头中有了序号和确认号的机制,一定程度上保证了数据传输的完整性,同时也保证了TCP传输的可靠性。

六个标志位

TCP报文的种类多种多样,除了正常通信时发送的普通报文,还有建立连接时发送的请求建立连接的报文,以及断开连接时发送的断开连接的报文等等。收到不同种类的报文时需要对应执行动作,因此需要用标志位进行区分不同的报文类型。这六个标志位都只占用一个比特位,为0表示假,为1表示真。

URG:

双方在进行网络通信的时候,由于TCP是保证数据按序到达的,即便发送端将要发送的数据分成了若干个TCP报文进行发送,最终到达接收端时这些数据也都是有序的,因为TCP可以通过序号来对这些TCP报文进行顺序重排,最终就能保证数据到达对端接收缓冲区中时是有序的。

虽然TCP的有序到达是我们想要的目的,并且接收方的对端上层也是从接收缓冲区中按顺序读取的,但是有时候发送方也会发送紧急数据,那么就要让接收方的对端上层也要紧急读取该数据,因此就需要使用的URG标志位。

  • 当URG标志位设置位1时,需要使用TCP报头中的16位紧急指针找到紧急数据,因此一般情况下不会使用到报头中的紧急指针。
  • 16位紧急指针表示了紧急数据在报文中的偏移量。

ACK:

  • 报文中的ACK标志设置为1,表示该报文可以对接收到的报文进行确认。
  • 一般除了第一个请求连接的报文没有设置ACK外,其余报文基本上都设置了ACK,因为携带了ACK的报文需要对接收到的报文进行确认。

PSH:

当PSH标志位设置为1时,会提示接收端应用程序立刻从TCP缓冲区读取数据,并交付给上层应用。

一般我们会认为当使用read从缓冲区读取数据时,如果缓冲区中有数据,那么这些数据就会被返回,如果没有数据就会阻塞式的等待write向缓冲区中写入数据再进行读取。

其实这种说法并不准确,因为在缓冲区中都有应该水位线的概念,例如下图:

  • 当缓冲区存储的数据没有达到水位线的时候,read就会进行阻塞等待,只要超过水位线后才会进行读取。因为如果缓冲区中有一点数据就进行读取的话会导致频繁的调用read,势必会造成效率的低下。

  • 当报文当中的PSH被设置为1时,实际就是在告知对方操作系统,尽快将接收缓冲区当中的数据交付给上层,尽管接收缓冲区当中的数据还没到达所指定的水位线。

RST:

  • 报文当中的RST被设置为1,表示需要让对方重新建立连接。
  • 在通信双方在连接未建立好的情况下,一方向另一方发数据,此时另一方发送的响应报文当中的RST标志位就会被置1,表示要求对方重新建立连接。
  • 在双方建立好连接进行正常通信时,如果通信中途发现之前建立好的连接出现了异常也会要求重新建立连接。

SYN:

  • 报文当中的SYN被设置为1,表明该报文是一个连接建立的请求报文。
  • 只有在连接建立阶段,SYN才被设置,正常通信时SYN不会被设置。

FIN:

  • 报文当中的FIN被设置为1,表明该报文是一个连接断开的请求报文。
  • 只有在断开连接阶段,FIN才被设置,正常通信时FIN不会被设置。

窗口大小

当发送端要将数据发送给对端时,本质是把自己发送缓冲区当中的数据发送到对端的接收缓冲区当中。但缓冲区是有大小的,如果接收端处理数据的速度小于发送端发送数据的速度,那么总有一个时刻接收端的接收缓冲区会被填满,这时发送端再发送数据过来就会造成数据丢包,进而引起丢包重传等一系列的连锁反应。

因此TCP报头中就引入的16位窗口大小来加以控制。这个16位窗口中填充的就是自身缓冲区剩余空间的大小,发送给对方后就能让对方知道自己缓冲区的存储能力,从而控制传输的速率。

  • 窗口大小字段越大,说明接收端接收数据的能力越强,此时发送端可以提高发送数据的速度。
  • 窗口大小字段越小,说明接收端接收数据的能力越弱,此时发送端可以减小发送数据的速度。
  • 如果窗口大小的值为0,说明接收端接收缓冲区已经使用完了,此时发送端就不应该再发送数据了。

确认应答(ACK)机制

确认应答机制是保证TCP通信可靠性的机制之一,它是由32位序号和32位确认序号来保证的。

TCP是面向字节流的,它会为每个字节的数据都进行了编号,即序列号:

每一个ACK都带有对应的确认序列号,意思是告诉发送者,当前接收方已经收到了哪些数据,下一次发送方应该发送哪些数据。

超时重传机制

主机A发送数据给主机B之后,可能因为网络拥堵等原因,导致数据无法到达主机B。如果主机A在一个特定时间间隔内没有收到主机B发来的确认应答,就会进行数据重传,这就是超时重传机制。

如果主机A也没有收到来自主机B的确认应答,也可能是因为ACK丢失了。

当ACK发生丢包时,由于存在超时重传机制,主机B就会收到重复的数据,此时主机B就会意识到自己发送的确认应答有可能发生了丢包,导致主机A没有收到,因此就会重新发送确认应答,并且主机B会根据其前面接收到的数据的序号,丢弃掉重复的数据。

那么超时重传的时间该如何设定呢?

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

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

  • Linux中(Windows也是如此),超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。
  • 如果重发一次之后,仍然得不到应答,等待 2*500ms 后再进行重传。
  • 如果仍然得不到应答,等待 4*500ms 进行重传,依次类推,以指数形式递增。
  • 当累计到一定的重传次数,TCP就会认为网络或者对端主机出现异常,最后强制关闭连接。

连接管理机制

在正常情况下,TCP要经过三次握手建立连接,四次挥手断开连接,以下是TCP连接到断开的全部过程:

三次握手

客户端与服务端建立连接的过程称为三次握手。

当客户端要与服务端之间相互通信时,首先就需要建立连接,此时客户端会主动向服务器发送建立连接的请求,然后双方实现三次握手。

第一次握手:客户的给服务端发送SYN报文,初始序列号为x,并且需要消耗应该序号。此时客户端进入SYN_SENT状态。当SYN=1,而ACK=0时,表明这是一个请求连接的报文。注意SYN=1时的报文不能携带数据,因此第一次握手和第二次握手都不会携带数据。因为如果携带数据的话,假如有人想要攻击服务器,只需要每次第一次握手时在SYN报文中携带大量的数据,导致服务器花费大量的缓冲区,造成服务器的崩溃。

通过第一次握手服务器可以知道:客户端发送数据的能力,以及以及自己的接受能力都处于正常状态。

第二次握手:服务端接收到了来的客户端的SYN报文后,对其进行确认,并且会把自己的SYN响应给对方,此时标志位ACK=1就是对第一次握手的报文进行的确认,并且ack = seq + 1,即为x + 1,初始序列号为y。此时服务器进入SYN_RECV状态。

通过第二次握手客户端能够知道:服务端的接收和发送能力正常,客户端自己的发送和接收能力也正常。但是服务器不知道客户端的接收能力是否正常,因此就需要进行第三次握手。

第三次握手客户端收到了服务端的SYN+ACK数据包,此时客户端就会认为自己和服务端都愿意进行连接,因此客户端会进入ESTABLISHED状态,并且会给服务端发送一个ACK报文,表示自己收到了来自服务端的SYN+ACK响应。此时确认序号ack = y + 1,发送给服务器的第二个报文段的seq = x + 1。

通过第三次握手服务端就能够知道:客户端的接收能力以及自己的发送能力都正常,此时双方建立连接成功,可以进行网络通信了。

为什么是三次握手,而不是其他的握手次数呢?

假如是两次握手,举个打电话的例子:

  • 假如在半夜你有一个重要的事情要告诉你的对象,于是打了一个电话,打通了过后,你向电话里面说:“喂,你听得到我说话吗?我有事跟你说。"
  • 对方:“你有毛病啊,大半夜打什么电话,有什么屁快放。”,此时却从电话里面再也听不到你的消息,非常生气,以为你大半夜搞恶作剧呢,便把你拉黑了。

其实可能是你对象麦克风或者网络的原因,导致自己听不到对方说话,引起误会。客户端和服务端之间也是如此,如果只有两次握手的话,服务端就不知道客户端的接收能力以及自己发送数据的能力,所以还需要进行第三次握手才行。

为什么要三次握手,难道四次或更多次不可以吗?

因为三次握手是安全的,并且效率是最高的。如果客户端发送请求时出现了丢包情况,因为自己没发送,又重新传了一遍,然而等数据传输完成后客户端和服务端都释放了连接,重发的传输的数据在释放连接前给服务器传了过去,但第一次传输的数据假如由于网络原因滞留的时间长了,在释放连接后到达了服务端,这个时候服务端就会误以为客户端又发出了一次新的请求,服务端确认了客户端第一次发出的报文段并同意建立了新的连接,发送报文给客户端,此时服务端会一直等待客户端的答复,而客户端此时正处于释放连接状态,所以导致白白浪费了资源。

半连接队列和全连接队列

  • 半连接队列(syn queue)
    客户端发送SYN包,服务端收到后回复SYN+ACK后,服务端进入SYN_RCVD状态,此时双方还没有完全建立连接,这个时候的socket会放到半连接队列。
  • 全连接队列(accept queue)
    当服务端收到客户端的ACK后,socket会从半连接队列移出到全连接队列。当调用accpet函数的时候,会从全连接队列的头部返回可用socket给用户进程。全连接队列中存放的是已完成TCP三次握手的过程,等待被处理的连接,在客户端及服务端的状态均为 ESTABLISHED

四次挥手

当双方结束通信,断开连接的过程称为四次挥手。四次挥手,顾名思义就是客户端和服务端四个步骤的释放连接,断开连接需要发送四个包,别名连接终止协议。因为TCP连接是全双工的,因此每个方向的连接都必须分别断开。断开的基本原则是,双方完成了数据传输的任务之后,由一方先发起一个FIN的报文来终止这个方向上的连接。收到一个FIN只意味着这一方向上没有数据流动,但另一方向上还可以发送数据,因此需要对方再发送一个FIN报文断开另一个方向上的连接。例如:

双方再断开连接前都处于ESTABLISHED状态,

第一次挥手:客户端主动断开连接,向服务端发送一个带FIN的报文。其中包含将FIN标志位置为1,序列号seq = u。发送完毕之后客户端进入FIN_WAIT_1状态,即关闭自己到服务端的连接,等待客户端的回应,但是此时可以接收服务端发来的报文。

第二次挥手:服务端收到FIN后,知道了客户端想要与自己断开连接,因此进入CLOSE_WAIT状态,并且向客户端响应一个带ACK的确认报文,此时客户端收到该报文就知道服务端接收到了自己的断开连接请求,但是此时服务端还可能会发送数据。

第三次挥手:此时服务端要与客户端断开自己这个方向上的连接,向客户端发送一个FIN报文,然后服务端进入LAST_ACK状态,等待来自客户端最后的确认。

第四次挥手:客户端收到 FIN 报文之后,同样会发送一个 ACK 报文作为应答,此时客户端进入TIME_WAIT状态,TIME_WAIT状态是为了等待足够的时间以确保服务器能够接收到到连接中断请求的确认。

注意:

  • 此时由服务端到客户端的 TCP 连接并未释放掉,客户端需要经过时间等待计时器设置的时间 2MSL(一个报文的来回时间) 后才会进入
    CLOSED状态,服务端收到 ACK 报文之后,就关闭连接了,处于 CLOSED 状态。
  • 这样做的目的是确保服务端收到自己的 ACK 报文如果服务端在规定时间内没有收到客户端发来的 ACK 报文的话,服务端会重新发送 FIN 报文给客户端,客户端再次收到 FIN 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK报文给服务端

**为什么是等待2MSL? **

  • 防⽌客户端最后⼀次发给服务器的确认在⽹络中丢失以⾄于客户端关闭,⽽服务端并未关闭,导致资源的浪费。
  • 等待最⼤的2MSL可以让本次连接的所有的⽹络包在链路上都完全传输完毕,以防造成不必要的⼲扰。

为什么客户端需要TIME_WAIT状态?

  • 假设最终的ACK丢失,服务端将重发FIN,客户端必须维护TCP状态信息以便可以重发最终的ACK,否则服务端认为传输中发生错误,导致会发送RST报文进行重新连接。
  • TCP实现必须可靠地终止连接的两个方向(全双工关闭),客户端必须进入TIME_WAIT 状态,因为客户端可能面临重发最终ACK的情形。

同样的全双工,为什么握手是三次,挥手是四次?

  • 因为握手的时候并没有数据传输,所以服务端的 SYN 和 ACK 报文可以一起发送,但是挥手的时候有数据在传输,所以 ACK 和 FIN 报文不能同时发送,需要分两步,所以会比握手多一步。

为什么三次挥手不可行?

  • 因为服务端在接收到FIN,往往不会立即返回FIN,必须等到服务端所有的报文都发送完毕了,才能发FIN。因此先发一个ACK表示已经收到客户端的FIN,延迟一段时间才发FIN,这就造成了四次挥手。
  • 如果是三次挥手会造成: 如果将服务端的两次挥手合为一次,等于说服务端将ACK和FIN的发送合并为一次挥手,这个时候长时间的延迟可能会导致客户端误以为FIN没有到达客户端,从而让客户端不断的重发FIN。
  • 所有只能第二次握手先发送ACK确认接收到了客户端的数据,等服务器发送完了数据,再发送FIN包进行第三次挥手。

滑动窗口

每发送一个数据就发出一个确认应答,直到发送端收到ACK报文再发送下一个数据段,这样的做最大的缺点就是性能比较差,尤其是在数据往返时间较长的时候表现得尤为明显。

既然这样一发一收的方式性能较低,那么就可以考虑一次发送多条数据,就可以大大的提高数据传输的性能。实际上就是是将多个数据段的等待时间重叠在一起,这样TCP就引入了滑动窗口的机制。

滑动窗口的概念:
滑动窗口(Sliding window)是一种流量控制技术。早期的网络通信中,通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道网络拥塞状况,同时发送数据,导致中间节点阻塞掉包,谁也发不了数据,所以就有了滑动窗口机制来解决此问题。滑动窗口协议是用来改善吞吐量的一种技术,即容许发送方在接收任何应答之前传送附加的包。接收方告诉发送方在某一时刻能送多少包(称窗口尺寸)。

滑动窗口原理:
滑动窗口协议的基本原理就是在任意时刻,发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口;同时,接收方也维持了一个连续的允许接收的帧的序号,称为接收窗口。发送窗口和接收窗口的序号的上下界不一定要一样,甚至大小也可以不同。不同的滑动窗口协议窗口大小一般不同。发送方窗口内的序列号代表了那些已经被发送,但是还没有被确认的帧,或者是那些可以被发送的帧。

例如下图:

  • 滑动窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。上图的窗口大小就是4000个字节(四个段)。
  • 发送前四个段的时候,不需要等待任何ACK,直接发送即可。
  • 当主机A收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据。后续数据的发送依次类推。
  • 操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答。只有确认应答过的数据,才能从缓冲区删掉。
  • 滑动窗口越大,则网络的吞吐率就越高。

主机B向主机A发送请求序列号为2001的报文,主机A收到确认报文后,滑动窗口向右移动至2001的位置。

如果出现了丢包的情况,窗口又该如何滑动呢?

情况一:主机A发送的数据包已经被主机B接收了,但是发给主机A的确认报文ACK丢失了。

这种情况下,部分ACK丢失并不会影响数据包的正常传输,因为主机A接收到的对后续数据包的确认,同时也能对前面发出的数据包进行确认

情况二:主机A发送的数据包部分直接丢失了。

  • 在这种情况下,主机A发送的在1001 ~ 2000的数据包丢失,但该滑动窗口中的其他数据包还是继续正常发送,只是由于主机B没有收到1001 ~ 2000的数据包,因此会一直向主机A发送带有1001确认号的响应,表示没有接收的1001 ~ 2000的数据包。
  • 如果主机A连续3次收到同样的带有确认号1001这样的确认应答,就会对 1001 ~ 2000 的数据包进行重传。
  • 当主机B收到了 1001 ~ 2000 的数据包后,就会向主机A响应确认号为7001的确认报文,因为2001 ~ 7000 的数据主机B已经收到了,被放在了其操作系统内核的接收缓冲区中。

这种机制被称为 “高速重发机制”,也叫做 “快重传”。

流量控制

接收端处理数据的速度是有限的,如果发送端发送得太快,就会导致接收端的缓冲区迅速被写满,如果这个时候发送端继续发送数据就会造成丢包,继而引起丢包重传等一系列连锁反应。

因此TCP支持根据接收端的处理能力来决定发送端的发送速度,这个机制就叫做流量控制(Flow Control)
流量控制的基本原理如下:

  • 接收端将自己可以接受数据的缓冲区大小写入TCP首部的 “窗口大小” 字段中,通过发送的ACK确认报文响应给发送端。
  • 窗口大小字段越大,说明网络的吞吐量和接收端的缓冲区大小越大。
  • 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置为一个更小的值响应给发送端。
  • 当发送端知道了这个窗口大小之后,就会减慢自己的发送速度。
  • 如果接收端的缓冲区满了,就会将窗口大小设置为0,此时发送端不再给接收端发送数据,但是需要定期发送一个窗口探测的数据段,让接
    收端把自己的窗口大小告诉发送端。

接收端如何把窗口大小告诉发送端呢?回忆前面的TCP首部中,有一个16位窗口字段,存放的就是窗口大小信息。那么问题来了,16位数字最大表示65535,那么TCP窗口最大就是65535字节么?
实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移 M 位。

拥塞控制

虽然TCP有了滑动窗口机制,能够高效可靠的发送大量数据,但是如果在刚刚传输数据的阶段就发送大量的数据,仍然可能会引起问题。因为当今世界上时时刻刻都存在着大量的计算机在进行网络通信,可能当前的网络状态就比较拥堵了,如果此时在不清楚网络状况的情况下就贸然发送大量的数据,还是可能会造成丢包等情况。

为了解决这个问题,TCP就引入了 “慢启动” 机制,即开始先发送少量的数据,去摸清当前网络的拥堵状况,再决定按照多大的速度发送数据。

此时便为 “慢机制” 引入了一个拥塞窗口的概念,其基本原理如下:

  • 在发送开始时,设置拥塞窗口的大小为1,发送端每次收到一个ACK确认应答,拥塞窗口大小就增长一倍。
  • 每次发送数据包的时候,发送端就会对自己的拥塞窗口和接收端反馈的滑动窗口的大小进行比较,取二者的较小值作为实际的窗口大小。

像上面这样的拥塞窗口大小的增长速度是指数级别的增长,“满启动” 只是指初始传输数据的速度满,但其增长的速度很快。解决方法如下:

  • 为了阻止拥塞窗口增长过快,不能使拥塞窗口大小单纯的只进行加倍
  • 因此引进了一个叫做 “慢启动” 的阈值。
  • 当拥塞窗口超过这个阈值的时候,步骤按照指数方式进行增长,而是以线性的方式增长。

如图:

  • 当TCP开始启动的时候,“慢启动” 的阈值等于窗口的最大值。
  • 每次进行超时重传的时候,“慢启动” 的阈值就会减少为原来的一半,同时拥塞窗口被重置为1。

当数据传输过程中出现少量的丢包,仅仅会触发TCP的超时重传机制,如果出现大量的丢包现象,那么就可以判断为出现了网络拥塞。当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传输数据是面向字节流的,比如创建一个TCP的socket,同时会在系统内核中创建一个发送缓冲区和一个接收缓冲区

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

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

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

粘包问题

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

那么如何避免粘包问题呢?归根结底就是一句话,明确两个包之间的边界

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

对于UDP协议来说,是否也存在 “粘包问题” 呢?

对于UDP,如果还没有上层交付数据,UDP的报文长度仍然在。同时,UDP是把一个一个把数据交付给应用层,因此就有很明确的数据边界。站在应用层的站在应用层的角度,使用UDP的时候,要么收到完整的UDP报文,要么不收,不会出现"半个" 的情况。因此对于UDP而言不会出现 “粘包问题” 。

TCP异常情况

TCP常见的异常情况如下:

  • 进程终止:进程终止会释放文件描述符,仍然可以发送FIN,和正常断开TCP连接没有什么区别。
  • 计算机重启:和进程终止的情况相同。
  • 机器掉电/网线断开:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset。即使没有写入操作, TCP自己也内置了一个保活定时器,会定期询问对方是否还在。如果对方不在,也会释放连接。
  • 另外,应用层的某些协议,也有一些这样的检测机制。比如在HTTP长连接中,也会定期检测对方的状态。例如QQ,在QQ断线之后,也会定期尝试重新连接。

总结

为什么TCP协议会这么复杂?因为既要保证其可靠性,同时又尽可能的提高性能,导致实现TCP就变得很困难。
TCP的可靠性得益于:

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

提供性得益于:

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

其他:

  • 定时器(超时重传定时器、保活定时器、TIME_WAIT定时器等)

相关文章:

【计算机网络】传输层TCP协议

文章目录认识TCP协议TCP协议的格式字段的含义序号与确认号六个标志位窗口大小确认应答(ACK)机制超时重传机制连接管理机制三次握手四次挥手滑动窗口流量控制拥塞控制延迟应答捎带应答面向字节流粘包问题TCP异常情况总结认识TCP协议 传输控制协议 (TCP,T…...

Mysql5.7安装【Windows版】

文章目录一、下载二、添加到环境变量三、添加配置文件my.ini四、安装Mysql 修改密码一、下载 下载地址 滑倒最下面有一个MySQL Community Server 选择要下载的版本 二、添加到环境变量 下载好了之后开始解压 把bin目录添加到环境变量 可以点击进入bin目录,直接复…...

分布式一致性算法Raft原理图释

什么是分布式一致性算法Raft 分布式一致性算法Raft:指在分布式场景下实现集群数据同步的解决方案 掌握了这个算法,就可以较容易地处理绝大部分场景的容错和数据一致性需求 Raft三大角色 跟随者(Follower):普通群众…...

网络安全-字典生成-crunch

网络安全-字典生成-crunch crunch工具,在kali已经集成好了 2是代表最小字符长度 4是最大字符长度 生成了一个2M的文件 还有我们来查看这个密码本 从abcd26个英文字母的2位到4位的组合,他全部排列了一次 还可以自定义数字,特殊字符&#xf…...

闪光桐人の实习日记

2023年2月13日 1,认识了职场礼仪,学习了职场礼仪的重要性 尊重->心情愉悦->建立信任与好感->合作机遇的敲门砖 2,学习了职场礼仪中的邮件礼仪 模板管理中设置自己的名片 部门写到三级部,如果部门名太长要换一行 发送…...

PostgreSQL 常见配置参数

max_wal_size : 两个检查点(checkpoint)之间,WAL可增长的最大大小,即:自动WAL checkpoint允许WAL增长的最大值。该值缺省是1GB。如果提高该参数值会提升性能,但也是会消耗更多空间、同时会延长崩溃恢复所需…...

JAVA 常用类型之String结构

String在java中我们是用来操作字符串的,但它的底层结构确是一个char[]数组,通过数组的方式将每个字符进行保存。 使用时:String str"ABCD",内部存value确是:value[A,B,C,D]; 如下图: 参考String源…...

二三层网络设备封装与解封装原理

1、寻址转发(寻址指的是寻找IP地址) 路由表放在一个公共的地方,比如主控板上,由主控板 的CPU运行路由协议,计算路由,生成和维护路由表。 转发表与路由表: 转发表是根据路由表生成的。路由表中…...

9、MyBatis框架——使用注解开发实现数据库增删改查操作、一级缓存、二级缓存、MyBatis实现分页

目录 一、使用注解开发实现数据库增删改查操作 1、搭建项目 2、使用注解开发操作数据库 二、一级缓存 1、一级缓存失效的情况 三、二级缓存 1、手动开启二级缓存cacheEnabled 2、二级缓存机制 四、MyBatis实现分页 1、配置环境 2、startPage()开启分页 3、PageInfo…...

C++STL剖析(六)—— set和multiset的概念和使用

文章目录🌟 前言🍑 树型结构和哈希结构🍑 键值对1. set的介绍和使用🍑 set的模板参数列表🍑 set的构造🍑 set的使用🍅 insert🍅 find🍅 erase🍅 swap&#x1…...

SpringColud第四讲 Nacos的Windows安装方式和Linux的安装方式

在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码: 目录 1.Windows安装Nacos 1.1.下载 1.2.解压 1.3.修改相关配置: 1.4.启动: 1.5.登录: 2.Linux的安装方式Nacos 2.1.…...

微服务项目【网关服务限流熔断降级分布式事务】

网关服务限流熔断降级 第1步&#xff1a;启动sentinel-dashboard控制台和Nacos注册中心服务 第2步&#xff1a;在网关服务中引入sentinel依赖 <!-- sentinel --> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-…...

【情人节用Compose给女神写个爱心动画APP】

情人节用Compose给女神写个爱心动画APP前言涉及知识点实现思路实现过程绘制爱心创建动画效果Preview预览效果完整源码彩蛋前言 前一阵子看电视里的学霸用代码写了个炫酷的爱心&#xff0c;网上有很多js和python的源码&#xff0c;复制粘贴就能拥有&#xff0c;但是Android的好…...

GUI swing和awt

GUI&#xff08;Graphical User Interface&#xff0c;简称 GUI&#xff0c;图形用户界面&#xff09;是指采用图形方式显示的计算机操作用户界面&#xff0c;与早期计算机使用的命令行界面相比&#xff0c;图形界面对于用户来说在视觉上更易于接受。Java GUI主要有两个核心库&…...

速通Spring

尚硅谷2023最新版Spring6课程_bilibili 1 Spring 【强制】Spring是什么&#xff1f; 1) Spring是一款主流的Java EE轻量级开源框架。 轻量级&#xff1a;体积很小&#xff0c;且不需要依赖于其他组件。 2) 狭义的Spring。 Spring Framework。 3) 广义的Spring。 以Spring F…...

【C++】C++入门

一、 C关键字&#xff08;C98&#xff09; C有63个关键字&#xff08;C语言有32个&#xff09;&#xff0c;如下&#xff1a; asmdoifreturntrycontinueautodoubleinlineshorttypedefforbooldynamic_castintsignedtypeidpublicbreakelselongsizeoftypenamethrowcaseenummutabl…...

Linux网络技术学习(五)—— 网络设备初始化(I)

文章目录什么时候进行的设备初始化&#xff1f;设备注册和初始化NIC&#xff08;网卡 Network Interface Card&#xff09;初始化的基本目标设备与内核之间的交互硬件中断中断类型传送节流方式为了改善效率中断共享IRQ处理函数映射的组织irqaction结构体存储方式什么时候进行的…...

[技术选型] ClickHouse和StarRocks的介绍

文章目录1.ClickHouse介绍2.StarRocks介绍1.ClickHouse介绍 ClickHouse是面向联机分析处理&#xff08;OLAP&#xff09;的开源分析引擎。最初由俄罗斯第一搜索引擎Yandex开发&#xff0c;于2016年开源&#xff0c;开发语言为C。由于其优良的查询性能&#xff0c;PB级的数据规…...

算法刷题打卡第90天:表现良好的最长时间段

表现良好的最长时间段 难度&#xff1a;中等 给你一份工作时间表 hours&#xff0c;上面记录着某一位员工每天的工作小时数。 我们认为当员工一天中的工作小时数大于 8 小时的时候&#xff0c;那么这一天就是「劳累的一天」。 所谓「表现良好的时间段」&#xff0c;意味在这…...

Python语言零基础入门教程(十七)

Python 文件I/O 本章只讲述所有基本的 I/O 函数&#xff0c;更多函数请参考Python标准文档。 #### 打印到屏幕 最简单的输出方法是用print语句&#xff0c;你可以给它传递零个或多个用逗号隔开的表达式。此函数把你传递的表达式转换成一个字符串表达式&#xff0c;并将结果写…...

C语言中大小端问题

目录 一、什么是大小端 二、 举个例子 三、大小端演示 四、解释"二"中举例的问题 ​五、怎么判断是大端还是小端 六、一个题目 一、什么是大小端 大端模式&#xff08;大端字节序存储&#xff09;&#xff1a;就是高位字节数据存放在内存的低地址端&#xff…...

vue2+微前端qiankun从搭建到部署的实践(主子应用切换;集成vue3+vite3子应用)

一、最终效果 二、微前端&#xff08;qiankun&#xff09;介绍及为什么选择用微前端&#xff0c;可以看官网 三、目录结构如下 四、具体配置 一、主应用配置 1、主应用技术栈 Vue-cli4搭建项目Vue2Element-Uiqiankun&#xff1b;Vue2Element-Uiqiankun 2、搭建好主项目&…...

怎么代理微信小程序创业?

随着微信的兴起&#xff0c;小程序已经成为了人们生活中不可或缺的一部分。如果你想要创业的话&#xff0c;那么代理微信小程序是一个不错的选择。本文将为大家介绍怎么代理微信小程序创业。 一、什么是微信小程序 微信小程序是一款专为移动设备使用者而设计的应用。它通过扫…...

今天是情人节呐,我利用Python制作了好多表白的东西,快来吧~

今天是情人节那&#xff0c;有没有现在没有对象的宝子&#xff0c;评论里扣个111哈哈 目录 玫瑰 爱心树 丘比特 多彩气球 阿玥的小课堂 一、情人节的由来 二、情人节的来历和意义 玫瑰 局部代码实现如下&#xff1a; # 花瓣1 turtle.left(150) turtle.circle(-90, 70) …...

【Linux】-- 进程信号(处理、内核)

上篇&#xff1a;【Linux】-- 进程信号&#xff08;认识、应用&#xff09;_川入的博客-CSDN博客 目录 信号其他相关常见概念 pending handler block 信号处理的过程 sigset_t sigset_t使用 系统接口 sigpending sigprocmask 捕捉方法 sigaction struct sigactio …...

C/【静态通讯录】

&#x1f331;博客主页&#xff1a;大寄一场. &#x1f331;系列专栏&#xff1a;C语言学习笔记 &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 前言 往期回顾&#xff1a; C/扫雷 C/N子棋 通讯录作为通讯录地址的书本&#xff0c;当今的通讯录可以涵盖多项…...

万卷书 - 让孩子对自己负责 [The Self-Driven Child]

让孩子对自己负责 The Self-Driven Child - 让你的孩子更加科学合理的掌控自己的生活 简介 《The Self-Driven Child》(2018)解释了我们对孩子的习惯性控制欲,它导致了孩子压力过大、难以合作,以及主观能动性差。本书不提倡这种做法,而是认为我们应该帮助孩子自己做出合适…...

Postman中cookie的操作

在接口测试中&#xff0c;某些接口的调用&#xff0c;需要带入已有Cookie&#xff0c;比如有些接口需要登陆后才能访问。 Postman接口请求使用Cookie有如下两种方式&#xff1a; 1、直接在头域中添加Cookie头域&#xff0c;适用于已经知道请求所用Cookie数据的情况。 2、使用…...

torch.grid_sample

参考&#xff1a; 双线性插值的理论Pytorch grid_sample解析PyTorch中grid_sample的使用方法pytorch中的grid_sample()使用 查阅官方文档&#xff0c;TORCH.NN.FUNCTIONAL.GRID_SAMPLE grid_sample的函数签名如下所示&#xff0c;torch.nn.functional.grid_sample(input, gr…...

前端基于 Docker 的 SSR 持续开发集成环境实践

项目收益 整体开发效率提升20%。加快首屏渲染速度&#xff0c;减少白屏时间&#xff0c;弱网环境下页面打开速度提升40%。 权衡 在选择使用SSR之前&#xff0c;需要考虑以下事项&#xff01; SSR需要可以运行Node.js的服务器&#xff0c;学习成本相对较高。对于服务器而言&a…...

视频剪辑公司/独立站seo是什么

有如下属性 begin 指定遍历的开始下标&#xff0c;从0开始 end指定遍历的终止下标&#xff08;包含在内&#xff09;id同var&#xff0c;表示当前正在遍历的元素对象status通过次可以判断当前元素的属性step迭代的步长&#xff0c;可为负…...

外贸婚纱网站/百度医生在线问诊

mysql增删改查相关操作以前用mysql用的少&#xff0c;对于数据库相关的操作不熟悉&#xff0c;现在开始要接触数据库了&#xff0c;记录一下相关的基础操作吧。1、数据库的授权操作# mysql -u root -pEnter password:mysql> grant all privileges on *.* to root% identifie…...

上虞网站建设公司/线上平台推广方案

《Java技术》第九次作业 &#xff08;一&#xff09;学习总结 1.用思维导图对javaIO操作的学习内容进行总结。 2.下面的程序实现了文件的拷贝&#xff0c;但采用的是一个字节一个字节的读写方式&#xff0c;效率很低。使用缓冲区可以减少对文件的操作次数&#xff0c;从而提高读…...

wordpress 网站迁移/代刷网站推广链接免费

...

建网站空间的详细说明/百度贴吧怎么发广告

概念在Java中&#xff0c;对象的生命周期包括以下几个阶段&#xff1a;创建阶段(Created)应用阶段(In Use)不可见阶段(Invisible)不可达阶段(Unreachable)收集阶段(Collected)终结阶段(Finalized)对象空间重分配阶段(De-allocated)Java对象在JVM中的生命周期当你通过new语句创建…...

网站怎么做后台/网络营销专业代码

进度&#xff1a;简历投递转载于:https://www.cnblogs.com/cy92/p/7594242.html...