网站建设 补充协议系统优化大师免费版

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

网站建设 补充协议,系统优化大师免费版,新型建房有哪几种,深圳办公室软装文章目录认识TCP协议TCP协议的格式字段的含义序号与确认号六个标志位窗口大小确认应答(ACK)机制超时重传机制连接管理机制三次握手四次挥手滑动窗口流量控制拥塞控制延迟应答捎带应答面向字节流粘包问题TCP异常情况总结认识TCP协议 传输控制协议 #xff08;TCP#xff0c;T… 文章目录认识TCP协议TCP协议的格式字段的含义序号与确认号六个标志位窗口大小确认应答(ACK)机制超时重传机制连接管理机制三次握手四次挥手滑动窗口流量控制拥塞控制延迟应答捎带应答面向字节流粘包问题TCP异常情况总结认识TCP协议 传输控制协议 TCPTransmission 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状态。当SYN1而ACK0时表明这是一个请求连接的报文。注意SYN1时的报文不能携带数据因此第一次握手和第二次握手都不会携带数据。因为如果携带数据的话假如有人想要攻击服务器只需要每次第一次握手时在SYN报文中携带大量的数据导致服务器花费大量的缓冲区造成服务器的崩溃。 通过第一次握手服务器可以知道客户端发送数据的能力以及以及自己的接受能力都处于正常状态。 第二次握手服务端接收到了来的客户端的SYN报文后对其进行确认并且会把自己的SYN响应给对方此时标志位ACK1就是对第一次握手的报文进行的确认并且ack seq 1即为x 1初始序列号为y。此时服务器进入SYN_RECV状态。 通过第二次握手客户端能够知道服务端的接收和发送能力正常客户端自己的发送和接收能力也正常。但是服务器不知道客户端的接收能力是否正常因此就需要进行第三次握手。 第三次握手客户端收到了服务端的SYNACK数据包此时客户端就会认为自己和服务端都愿意进行连接因此客户端会进入ESTABLISHED状态并且会给服务端发送一个ACK报文表示自己收到了来自服务端的SYNACK响应。此时确认序号ack y 1发送给服务器的第二个报文段的seq x 1。 通过第三次握手服务端就能够知道客户端的接收能力以及自己的发送能力都正常此时双方建立连接成功可以进行网络通信了。 为什么是三次握手而不是其他的握手次数呢 假如是两次握手举个打电话的例子 假如在半夜你有一个重要的事情要告诉你的对象于是打了一个电话打通了过后你向电话里面说“喂你听得到我说话吗我有事跟你说。对方“你有毛病啊大半夜打什么电话有什么屁快放。”此时却从电话里面再也听不到你的消息非常生气以为你大半夜搞恶作剧呢便把你拉黑了。 其实可能是你对象麦克风或者网络的原因导致自己听不到对方说话引起误会。客户端和服务端之间也是如此如果只有两次握手的话服务端就不知道客户端的接收能力以及自己发送数据的能力所以还需要进行第三次握手才行。 为什么要三次握手难道四次或更多次不可以吗 因为三次握手是安全的并且效率是最高的。如果客户端发送请求时出现了丢包情况因为自己没发送又重新传了一遍然而等数据传输完成后客户端和服务端都释放了连接重发的传输的数据在释放连接前给服务器传了过去但第一次传输的数据假如由于网络原因滞留的时间长了在释放连接后到达了服务端这个时候服务端就会误以为客户端又发出了一次新的请求服务端确认了客户端第一次发出的报文段并同意建立了新的连接发送报文给客户端此时服务端会一直等待客户端的答复而客户端此时正处于释放连接状态所以导致白白浪费了资源。 半连接队列和全连接队列 半连接队列(syn queue) 客户端发送SYN包服务端收到后回复SYNACK后服务端进入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定时器等