公司动态
UDP协议 与 TCP协议
目录UDP报头TCP报头4位首部长度序号与确认序号TCP的两种通信模式16位窗口大小六个标志位可靠性/高效性方案确认应答机制超时重传机制连接管理机制三次握手四次挥手流量控制滑动窗口拥塞控制UDP协议UDP报头UDP 是传输层协议16位目的端口标识要将UDP报文交付给应用层哪一个协议我们之前在实现Socket编程时之所以端口号定义为 uint16_t正是因为内核中的端口号是16位的16位UDP长度表示整个UDP报文的大小因此一个UDP报文最长是64K如果发送长度超过64K就要在应用层手动将一个报文分成多个进行发送而报头长度是固定的因此总长度-报头长度有效载荷长度UDP通过固定报头长度实现报头和有效载荷分离UDP特点无连接知道 ip port 就可以直接通信无需提前建立连接不可靠没有确认机制、没有重传机制因为各种原因丢包了UDP不管面向数据报应用层交给UDP多长的报文UDP原样发送既不会拆分也不会合并;例如UDP传输100个字节的数据如果发送端调用一次sendto发送100个字节那么接收端也必须调用对应的一次recvfrom接收100个字节而不能循环调用10次recvfrom每次接收10个字节;UDP的缓冲区UDP没有真正意义上的发送缓冲区。调用sendto会直接交给内核由内核将数据传给网络层协议进行后续的传输动作UDP具有接收缓冲区。但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致如果缓冲区满了再到达的UDP数据就会被丢弃UDP的socket既能读也能写这个概念叫做全双工。深入理解报文系统中可能会同时存在很多报文需要对报文进行管理先描述再组织描述报文的结构体在内核中叫做 sk_buff注意该结构体和报文的报头结构体是不一样的报头结构体中可能有 ip / port / 各种网络属性字段而 sk_buff 是标识一个报文在内存中的空间布局报文的封装和解包到底在干什么假如现在要发送一个报文需要构建一个 sk_buff 结构体malloc 一块空间(数据区)用于存放实际的报文数据head 指针和 end 指针指向数据区的开始和结束现在应用层数据写完了data指向应用层数据的开始tail指向如图所示到了传输层要封装TCP报头本质就是将 sk_buff-data - sizeof(tcphdr)指针向低地址处移动然后 (struct tcphdr*)- xxx yyy封装其他层的报头也是如此当报文到达对端主机时需要解包提取报头本质就是 (struct mac*)(sk_buff-data)-xxx然后 sk_buff-data sizeof(machdr);我们每次在创建 socket 对象时也是需要创建 struct file 对象分配文件描述符的而 strcut file 中有一个 void* private 的指针指向了 struct socket 对象struct socket 对象中有一个 sk 指针指向了 struct sock 结构该结构中存在一个发送队列和接受队列UDP只有接收队列两个队列都是双链表结构这就是为啥用一个文件描述符既能读又能写的原因TCP协议TCP 全称为传输控制协议Transmission Control Protocol人如其名要对数据的传输进行一个详细的控制TCP报头4位首部长度TCP 报头中的选项属于报头中的变长字段可以有也可以没有。除去选项之外的报头固定是20个字节那怎么知道有没有选项呢也就是如何知道报头读完了呢从而分离报头和有效载荷4位首部长度计算的是整个 TCP 报头的长度该数字 * 4 整个报头的长度最大是1111表示数字1515 * 4 60 个字节选项最大是 60 - 20 40 个字节整个报头长度范围为 [2060]字节问题1如果已知 TCP 报头 不携带选项求4位首部长度大小应该是几x * 4 20x 5 101问题2UDP报头中是带有整个UDP报文的长度的TCP报头中却没有整个TCP报文的长度因为TCP是面向字节流的只要将发送的数据依次放在对端的接收缓冲区即可明确报文之间的边界、解决数据包粘包问题是由应用层来做的序号与确认序号client 给 server 发了一条消息client 知不知道 server 收到还是没收到这条消息呢是不知道的但是如果 server 给 client 进行了回复client 就知道 server 一定收到了自己刚才发送的消息但是 server 知不知道 自己发送的 回复 client 收到了没有呢是不知道的如何知道client 对 server 发来的回复再进行回复可以发现最新的一条消息永远没法进行确认因此互联网中不存在 100% 可靠的协议但只要收到应答就可以确认历史报文 100% 被对方收到了TCP的两种通信模式模式一client 每发一条消息server 进行一次回复当 client 收到了上一条消息的回复之后再发下一条消息这种模式效率是比较低下的模式二client 一次性发送多条消息client 一次性进行回复多条消息这样就可以较为高效的利用时间提高通信效率由于TCP要保证可靠性其中比较重要的一点就是报文的顺序问题发消息的顺序是 1-2-3但是不一定是按照1-2-3顺序到达对端主机的因此对端主机需要对到达报文进行排序因此TCP报头中必须携带序号TCP将每个字节的数据都进行了编号即为序列号报文的序号是该报文段数据部分第一个字节的序号。如下图所示假如发送的每个报文长度是1000个字节那么一次性发送多个报文的序号分别为110012001 ...server 给 client 回复的消息需要编号吗也是需要的因为一次性要回复多条消息client 一定要知道哪个回复对应的是哪条消息的确认因此 TCP 报头中携带了 确认序号注意确认序号的含义是该序号之前的字节流数据我都已经收到了下次发从该序号开始发主机A一次性向主机B发送了4个报文 如果 1~1000、2001~3000、3001~4000 这三个报文都收到了而1001~2000这个报文丢了那么主机B在进行确认应答的时候确认序号应该是 1001主机A收到B的应答后不确定2001~3000以及3001~4000是否丢包了但可以肯定的是1001~2000这个报文一定丢了那就重发1001~2000这个报文问题1 两个主机使用tcp通信的时候发送的是啥呀发送的是 TCP 报文可能不需要携带有效数据但至少是一个 TCP 报头下文中提到的 发送什么 SYN / ACK 等等本质也都是 TCP报头/报文只是相应的字段被设置为有效了而已问题2 为啥序号和确认序号要同时存在主机B给主机A应答的时候也直接使用序号不行吗同时存在肯定意味着可能会被同时使用为啥序号和确认序号可能会同时被使用A给B发消息B要给A应答然后B也可能给A发消息A要给B应答B想着既然我要给你应答后续还要给你发消息那我直接把要给你的应答和要给你发的新消息合并成一条消息提高效率这种机制叫做捎带应答因此某一条报文可能既是对上一条消息的应答也可能是一条新的消息因此序号和确认序号就要被同时用到因此要同时存在16位窗口大小TCP是有发送缓冲区和接收缓冲区的但是缓冲区大小是有限的如果发送的数据过多过快对方来不及接收就会导致大量的丢包问题而TCP要保证可靠必须解决该问题如何解决发送方只要知道接收方的接收能力就可以控制发送的量和速度了对方的接收能力本质就是接收缓冲区剩余空间的大小而双方通信传递的是TCP报文/报头报头中的 16位 窗口大小就是自己接收缓冲区剩余空间的大小有了16位窗口大小就可以调整发送速率这种机制就是流量控制流量控制是两方面的含义不只是说对方接收能力不够了发慢一点接收能力太强了发快一点也叫流量控制发慢一点是防止对方来不及接收导致丢包这是可靠性的体现发快一点是为了提高效率兼顾可靠性效率六个标志位标志位本质就是结构体字段中的位段成员每个标志位就是1个比特位设置为1表示有效设置为0表示无效在双方通信的过程中会存在各种各样的报文有的报文是建立连接的有的报文是断开连接的有的报文是应答消息的有的报文是紧急报文等等因此报文是有类型的类型决定了主机对报文的处理动作六个标志位就是用来区分报文类型的ACKACK设置为1表示该报文是一个确认应答报文而由于捎带应答的存在网络中的大多数报文的ACK都被置为1了SYNTCP是面向连接的在通信之前先要进行三次握手建立连接SYN被设置为1就表明该报文是一个请求建立连接的报文PSH当发送方发送报文的过程中发现对端的接收缓冲区剩余空间越来越少了当对端的接收缓冲区剩余空间为0了发送方就停止发送了但是接收方的上层迟迟不读走数据导致发送方一直没法发送此时发送方就可能发送一个报文PSH(push) 字段被设置提示接收端的应用程序尽快读走TCP接收缓冲区的数据像ssh协议在通信时报文的PSH字段一般都会被设置因为用户输入的指令要尽快得到相应如果接收缓冲区有数据但应用层程序就是不读相当于进程阻塞了而携带PSH的报文本质就是在唤醒阻塞的进程RST作为服务器若干个客户端都可以和该服务器连接因此通信的过程中可能会存在各种各样的连接有的是刚建立好的连接有的是正在进行通信的有的是正在断开的而OS要对连接做管理就要先描述再组织因此维护连接是有成本的tcp通信前要三次握手建立连接三次握手的前两次握手都是有应答的而最后一次 ACK 是没有应答的也就是说最后一次 ACK 可能会丢失导致连接不一定能建立成功会有什么问题呢当 client 将 ACK 发送出去之后就认为自己的三次握手完成了而 server 必须收到 ACK后才认为自己的三次握手完成了如果 ACK丢了server 认为建立连接没有成功而 client 认为 连接建立好了于是直接给 server 发送数据server 收到了数据一看连接还没有建立好呢发啥数据于是就给 client 发送一个报文该报文 RST 被设置表明连接要被重置(reset)client 收到后就会重新三次握手建立连接因此 RST 字段是用来处理各种异常连接的让对方进行连接重置URG发送方一次性可能会发送多个报文而多个报文不一定是按序到达的但最后需要被按照顺序处理因此最终在接收缓冲区中的顺序就是在发送缓冲区的顺序是按序排列的但是如果发送方就是想让某个报文被立即处理呢那就将报头的 URG 字段设置表明该报文是一个 urgent(紧急) 报文该报文就可以进行插队优先被处理比如我们从本地上传数据到云端上传到一半突然不想上传了已经传送到对端接收缓冲区的数也就不需要被上层读取处理了此时我们可以传递URG被设置的报文16位紧急指针上面提到需要传递紧急报文时设置URG就表明该报文需要被优先处理但是该紧急报文要优先做什么呢紧急数据在哪里由16位紧急指针指向该指针本质就是该报文有效载荷部分的一个偏移量该字段只能有1个字节可靠性/高效性方案确认应答机制见上文序号与确认序号部分超时重传机制主机A给主机B发数据主机B收到数据要给主机A应答。现在问题是如果主机A没有收到应答呢主机A知道是发送的数据丢了还是应答丢了根本不知道的并且数据丢和应答丢不可能同时出现的因为如果是数据丢了主机B没收到数据根本不会进行应答的主机A会约定一个特定的时间超过这个时间超过这个时间没有收到应答不管真实情况下是数据丢了还是应答丢了统一判定为丢包问题就要进行重传这种机制叫做超时重传问题1 如果是应答丢了也会进行超时重传对方不就收到了重复报文吗对方是会收到重复报文但报文是带有序号的可以根据序号进行去重如果发现该序号的报文已经收到过了直接丢弃即可问题2 超时重传的时间如何确定超时重传的时间不能太短否则有可能是应答还没来得及到达发送端就进行重传浪费网络资源甚至造成网络拥堵时间也不能太长否则重传效率太低了最理想的情况就是找到一个最小的时间保证确认应答一定能在这个时间内返回但这个时间的长短随着网络环境的不同是有差异的Linux中BSDUnix和Windows也是如此)超时以50oms为一个单位进行控制每次判定超时重发的超时时间都是500ms的整数倍。如果重发一次之后仍然得不到应答等待2*500ms后再进行重传如果仍然得不到应答等待4*500ms进行重传。依次类推以指数形式递增。累计到一定的重传次数TCP认为网络或者对端主机出现异常强制关闭连接。连接管理机制三次握手当客户端调用 connect会自动触发三次握手三次握手由双方OS自动完成accept 是不参与三次握手的三次握手完成后建立了连接客户端和服务器的状态已经是 ESTABLISHED 了accept 只是将获取到的连接返回而已为什么是三次握手1.TCP 是全双工通信协议必须保证双向通信信道都是通畅的而三次握手就是以最小成本验证网络是通畅的保证可靠性由于TCP是全双工的因此服务器和客户端都要验证自己和对方能收能发当客户端收到了 SYN ACK客户端单方面证明了自己以及服务器能收能发。我的发送能力正常服务器收到了我的SYN我的接收能力正常我收到了服务器的回复服务器的发送能力正常我收到了它发的SYN服务器的接收能力正常它收到了我的SYN。当服务器收到了 ACK 后客户端单方面证明了自己以及客户端能收能发。我的发送能力正常客户端收到了我的SYNACK我的接收能力正常我收到了服务器的ACK服务器的发送能力正常我收到了它发的ACK服务器的接收能力正常它收到了我的SYNACK。2. 建立双方要通信的共识双方都知道我要和对方通信了三次握手本质是四次握手只是捎带应答让中间的两次握手合并了四次握手本质就是双方都要确认和对方通信了后续 server 给 client 发消息client 就必须收client 给 server 发消息server 就必须收无法抵赖四次挥手为什么是四次挥手和四次握手类似因此TCP是全双工的双方都要确认和对方断开连接那为什么中间的两次不能合并呢客户端发送 FIN 的本质是 客户端要给服务器发送的数据发送完了自己要和服务器断开连接我要关闭向你发送数据的通道了客户端给服务器数据发完了但不代表服务器不给客户端发了客户端还要接收服务器发送的消息比如 http 服务客户端和服务器建立的是短连接客户端构建了 http request 之后就关闭了连接不再发送数据了而服务器收到 http request 之后还要构建 http response 响应发回客户端因此中间的两次挥手一般都不能直接合并因此就是四次挥手客户端不给服务器发消息了于是客户端 close 了文件描述符但是 服务器还要给客户但发消息呀客户端 close 了 fd还怎么通过 fd 读消息呢close(fd) 不是将读写端都关了嘛系统中存在一个系统调用 shutdown可以选择只关闭读端或者写端或者读端和写端都关闭无论是只关闭读/写端还是都关闭只要客户端/服务器一方任何调用一次shutdown底层都对应了两次握手只是只关闭读端/只关闭写端客户端会保存一定的文件信息后续还能读写四次挥手的状态变化当客户端主动断开连接后会处于 FIN_WAIT_1 状态服务器收到报文后回复 ACK然后处于 CLOSE_WAIT 状态只要服务器不主动 close 文件描述符就会一直处于 CLOSE_WAIT系统中如果存在大量的 CLOSE_WAIT 状态有大量未断开的连接就是发生了 fd 内存泄漏修改 TcpServer 代码子进程不去处理连接而是死循环一直不关闭fdvoid Run() { while(true) { // 你要得到一个sockfd, client addr InetAddr addr; auto sockfd _listensocket-Accept(addr); if(sockfd nullptr) continue; LOG(LogLevel::DEBUG) 获取一个新连接: addr.ToString() , sockfd : sockfd-SockFd(); if(fork() 0) { // _listensocket-Close(); // HandlerRequest(sockfd, addr); while(true) { sleep(1); } exit(0); } sockfd-Close(); } }我们用浏览器客户端访问服务器然后关掉标签页也就是浏览器客户端 close(fd)而服务器不关闭可以在服务器上查看到 CLOSE_WAIT 状态而当四次挥手结束了主动断开连接的一方(客户端)不能直接处于 CLOSED 状态而是要保持一段时间的 TIME_WAIT 状态保持的时间是 2个MSL(最大生存时间)理由一 确保最后的ACK能被对方收到最主要原因四次挥手最后一步是主动关闭方客户端发送ACK给被动关闭方服务端以确认服务端发来的FIN包。如果这个最后一次ACK在网络中丢失了服务端收不到确认会超时重传它之前的FIN包也就是第三次挥手。如果客户端此时已经处于CLOSED状态彻底注销了系统会认为这个连接已经不存在。当服务端的FIN重传包到达时客户端操作系统内核根本找不到对应的连接控制块于是会直接回复一个RST重置连接包。服务端收到RST包会认为连接异常中断而不是正常关闭。这就导致服务端无法进入CLOSED状态资源无法完全释放。而有了TIME_WAIT状态持续2MSL客户端会保留这个连接的信息。如果在这段时间内收到了服务端重传的FIN客户端可以重新发送ACK确保服务端顺利关闭。这是TCP实现全双工可靠关闭的必备条件理由二让网络中迷路的老数据包自然消失假设没有TIME_WAIT客户端立刻关闭端口并重新发起新连接比如重启了程序而且新连接恰好使用了相同的IP和端口。由于网络延迟上一次连接中滞留在网络里的老数据包可能在这个新连接建立后才姗姗来迟。因为端口号、IP都完全一样操作系统内核会误以为这个老数据包属于新连接从而将其接收并交给应用程序导致数据错乱。客户端等待2MSL足以保证本端发出的最后一个ACK能到达对方如果丢失对方重传FIN需要时间。原连接中所有“迷路”的数据包在2MSL内都会被网络丢弃不会存活到新连接中。启动服务器进程当我们直接 ctrl c 终止了服务器进程就可以在一段时间内查看到TIME_WAIT状态过一会就查不到了TIME_WAIT 状态就消失了我们之前在启动服务器时经常会出现 绑定端口号失败的情况。之前的做法是直接换一个端口号进行重启但实际服务器的端口号是不能任意更改的假如服务器最多能挂5000个连接结果来了5001个连接服务器直接挂掉了处于TIME_WAIT 状态还要等2MSL才能重启这不符合实际需求的假如是双十一期间服务器挂了必须要能立即重启即便是处于TIMT_WAIT状态系统中存在 setsockopt 系统调用可以设置 socket 地址进行复用只要调用了该函数服务器即便是处于 TIME_WAIT 状态依旧可以立即使用原来的端口号进行重启只需要在创建完监听套接字后调用 setsockopt就可以实现 socket 地址复用int opt 1; // 地址复用 -- socket addr 重复使用 setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, opt, sizeof(opt));流量控制接收端处理数据的速度是有限的 如果发送端发的太快 导致接收端的缓冲区被打满这个时候如果发送端继续发送就会造成丢包继而引起丢包重传等等一系列连锁反应。因此TCP支持根据接收端的处理能力来决定发送端的发送速度 这个机制就叫做流量控制。接收端将自己可以接收的缓冲区剩余空间大小放入TCP 首部中的 16位窗口大小 字段, 通过ACK端通知发送端• 窗口大小字段越大, 说明网络的吞吐量越高• 接收端一旦发现自己的缓冲区快满了就会将窗口大小设置成⼀个更小的值通知给发送端;• 发送端接受到这个窗口之后就会减慢自己的发送速度;• 如果接收端缓冲区满了, 就会将窗口置为0这时发送方不再发送数据但是需要定期发送⼀个窗口探测数据段, 使接收端把窗口大小告诉发送端。细节1发送方第一次给对方发数据的时候是如何得知对方的接收能力的呢别忘了在正式通信之前先要进行三次握手建立连接在三次握手的时候双方就交换过报头了各自都知道了对方的接收能力了细节2窗口大小固定是16位的那么接收缓冲区的大小最大就是 2^16-1 65535 字节吗实际上, TCP首部40字节选项中还包含了⼀个窗口扩大因子M实际窗口大小是窗口字段的值左移 M 位滑动窗口上文我们讨论了确认应答策略对每⼀个发送的数据段都要给⼀个ACK确认应答收到ACK后再发送下一个数据段这样做有个缺点就是性能较差尤其是数据往返的时间较长的时候。既然这样一发一收的方式性能较低那么我们⼀次发送多条数据就可以大大的提高性能(其实是将多个段的等待时间重叠在⼀起了)滑动窗口大小指的是无需等待确认应答而可以继续发送数据的最大值上图所示滑动窗口的大小就是4000个字节滑动窗口本质就是TCP发送缓冲区的一部分而我们认为TCP发送缓冲区是一个 char buffer[ ] 数组如何标识数组的一部分呢只需要知道 开始位置 start 和 结束位置 end 就可以了滑动窗口将整个TCP发送缓冲区分为了三部分最左侧是已经发送并且已经确认收到的数据意味着后续可以直接被覆盖成新的数据中间就是滑动窗口部分表示不需要等待应答就可以直接发送的数据最右侧是还没有发送的数据/没有数据的空闲空间。暂时不考虑网络状况滑动窗口的大小是由对方缓冲区的接收能力决定的而滑动窗口到底多大只要确定 start 和 end 的值即可start ACK 确认序号end start 16位窗口大小细节1滑动窗口只能向右滑动吗可以向左滑动吗因为滑动窗口左侧是已经发送并且已经确认的数据左侧已经没有有效数据了start 只会滑动窗口整体只会向右滑动不会向左滑动。细节2滑动窗口会变大吗会变小吗可以不变吗都是有可能的以上图为例现在收到了对方的ACK应答16位窗口大小是4000字节ACK确认序号是1001因此发送方的滑动窗口的大小是4000字节范围是1001-5000假如现在将1001-2000范围的数据发给了对方对方收到后a. 上层并没有读取于是对方发来的ACK应答16位窗口大小变成了3000字节ACK确认序号是2001因此发送方此时的start 2001end 2001 3000 5000start 右移了end 没有动因此滑动窗口变小了b. 上层立刻进行了读取于是对方发来的ACK应答16位窗口大小依旧是4000字节ACK确认序号是2001因此发送方此时的start 2001end 2001 4000 6000start 和 end 都右移了但滑动窗口大小没有变c. 对端缓冲区本身就累积了很多数据现在上层一次性把缓冲区的数据都取走了那么ACK时就会返回一个很大的窗口大小此时发送方的滑动窗口就会变大因此滑动窗口是完全随着对端的接收能力大小变化的细节3start 可能 end 吗为什么不可能的end 的运算规则就是 start 16位窗口大小16位窗口大小本身是 0 的因此 end start并且当 start end 时窗口大小是可以为0的细节4丢包了怎么办情况一数据包到了ACK丢了部分ACK丢了不要紧可以通过后续的ACK确认情况二数据包丢了当某一段报文丢失后发送端会一直收到 1001 这样的ACK如果发送端连续三次收到了同样的1001的应答就会将1001-2000这个数据包进行重传这个时候当接收方收到了1001之后再次返回的ACK就是7001了因为2001-7000之前就已经收到了~当接收端连续收到三个ACK相同的应答时进行重传这种机制叫做快重传快重传是用来提高传送速率上限的可能还没到超时时间发送端就收到了三个ACK相同的报文此时已经确认报文丢了就不用等到超时时间就可以直接重传了而超时重传是用来兜底了比如发送端可能就只发送了一个报文然后丢包了那么发送端压根不会收到三个ACK相同的报文此时超时重传就起到作用了滑动窗口内的报文丢失分为三种情况a. 最左侧报文丢失假如现在 1001-2001 报文丢了那么接收端会收到ACK为1001的应答那么 start 的值不变滑动窗口左侧不会移动也就是说数据传输的过程中如果丢包了该数据不能从滑动窗口中删除要支持后续进行重传因此重传机制也和滑动窗口密切相关b. 中间报文丢失假如2001-3001丢失了收到的ACK为2001滑动窗口左侧右移就又转化成了最左侧报文丢失c. 最右侧报文丢失依然转化为最左侧报文丢失问题总结滑动窗口内的丢包问题本质就只有一种情况就是最左侧报文的丢包问题。而确认序号机制定义为ACK之前的序号都收到了可以保证滑动窗口滑动的连续性也就是说确认序号机制也和滑动窗口密切相关细节5滑出去了怎么办发送缓冲区本质就是char buffer[ ]虽然是一个线性数组但是逻辑上可以认为是环形数组因此不会存在滑出去的情况当下标越界时会回归到0细节6上述图片中滑动窗口内的数据划分成了4个报文为啥不直接当成一个整体发送这个和数据链路层有关数据链路层一次能发送的报文大小是有上限的这就倒逼上层无法一次性发送的报文过大拥塞控制网络中传送数据时偶尔有些丢包情况是正常的可能是双方主机的问题但是当网络中出现大量丢包时就不是发送方和接收方的问题了而是整个网络状况出现了问题这是TCP无法解决的当出现少量数据丢包时发送方可以进行重传但当大量数据丢包发送方不敢也不能进行重传因为此时网络状况已经很差了而且网络中存在大量的主机你重传了几个数据包没事那所有主机都重传呢网络状况就会雪上加霜了TCP引入慢启动机制先发送少量的数据探探路摸清当前的网络拥堵状态再决定按照多大的速度传输此处引入一个概念叫做拥塞窗口拥塞窗口是TCP 发送方根据网络拥塞状况自行维护的状态变量表示在未收到确认前允许注入网络的最大数据量用于防止过多数据导致网络过载。可以看出拥塞窗口大小是随着网络状况而变化的因此发送端发送数据既要考虑接收端的接收能力也要考虑网络的拥塞状况滑动窗口大小 min(对端ack窗口大小拥塞窗口大小)连接刚开始建立的时候定义拥塞窗口大小为1那么滑动窗口大小就是1就会只发送1个报文进行探测每次收到一个ACK应答拥塞窗口的大小就呈指数级增长(变为原来的2倍)为了不增长的那么快因此不能使拥塞窗口的大小单纯加倍此处引入一个叫做慢启动的阈值当拥塞窗口超过这个阈值的时候不再按照指数方式增长而是按照线性方式进行增长当TCP开始启动的时候慢启动阈值等于窗口最大值在每次超时重发的时候慢启动阈值会变为原来的一半同时拥塞窗口置回1少量的丢包, 我们仅仅是触发超时重传大量的丢包我们就认为网络拥塞当TCP通信开始后网络吞吐量会逐渐上升随着网络发生拥堵吞吐量会立刻下降拥塞控制归根结底是TCP协议想尽可能快的把数据传输给对方但是又要避免给网络造成太大压力的折中方案