今天,继续来网管的自我修养之TCP协议,这可是除IP协议外另一个核心协议了。 TCP协议是网络传输中至关重要的一个协议,它位于传输层。向上支持FTP、TELNET、SMTP、DNS、HTTP等常见的应用层协议,向下要与网络层的IP协议相互配合,实现可靠的网络传输。分层网络模型OSI7层模型 为了让全世界的计算机有效的互联起来,国际标准化组织提出了一种概念化的网络模型,开放式系统互联模型(OpenSystemInterconnectionModel),简称OSI模型。 自上而下依次为应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。应用层 应用层提供为应用软件而设计的接口,以设置与另一应用软件之间的通信。例如:HTTP、HTTPS、FTP、Telnet、SSH、SMTP、POP3等。表示层 表示层把数据转换为能与接收者的系统格式兼容并适合传输的格式。会话层 会话层负责在数据传输中设置和维护计算机网络中两台计算机之间的通信连接。传输层 传输层把传输表头(TH)加至数据以形成数据包。传输表头包含了所使用的协议等发送信息。例如:传输控制协议(TCP)等。网络层 网络层决定数据的路径选择和转寄,将网络表头(NH)加至数据包,以形成分组。网络表头包含了网络资料。例如:互联网协议(IP)等。数据链路层 数据链路层负责网络寻址、错误侦测和改错。当表头和表尾被加至数据包时,会形成信息框。数据链表头(DLH)是包含了物理地址和错误侦测及改错的方法。数据链表尾(DLT)是一串指示数据包末端的字符串。例如以太网、无线局域网(WiFi)和通用分组无线服务(GPRS)等。 分为两个子层:逻辑链路控制(logicallinkcontrol,LLC)子层和介质访问控制(Mediaaccesscontrol,MAC)子层。物理层 物理层在局部局域网上传送数据帧,它负责管理电脑通信设备和网络媒体之间的互通。包括了针脚、电压、线缆规范、集线器、中继器、网卡、主机接口卡等。 OSI模型是国际标准模型,是指导互联网模型的概念标准。而在实际的设计实现过程中,最后形成了TCPIP4层模型结构。TCPIP4层模型 TCPIP模型实际上并不单单指TCP和IP,实际上这一个协议簇,还包含了其他的一些协议,比如UDP、ICMP、IGMP等。 TCPIP模型是事实上的标准模型,在7层模型的基础上将最上面三层的应用层、表示层、会话层统一为应用层,将数据链路层和物理层统一为链路层或者叫网络接口层。 实际应用中还是以4层模型为准,毕竟这才是事实上的标准。还有一种5层模型的说法,实际上就是把7层中的应用层、表示层、会话层合并为应用层,其他层保持不变。数据的加工和传输过程 TCPIP模型每个层都有各自的功能和分工,当有用户数据想要发送给另一台设备的时候,数据自上而下,从应用层向链路层传递有一个复杂的过程。 以Telnet为例,Telnet在传输层是使用TCP协议的。 数据从应用层进入,到达传输层,添加上TCP首部,将数据加工成TCP段,称为Segment。这是为了保证数据的可靠性。 接着数据到达网络层,在网络层使用IP协议,被添加上IP首部,将数据加工成IP数据报,称为datagram。经过网络层IP协议的加工,指定目标地址和MAC地址,保证数据准确的发送到目标机器。 接着数据到达链路层,添加上以太网头部,将数据加工成以太网帧,称为frame,包含了网卡等硬件相关的数据。 无论是Telnet还是HTTP,都至少涉及到两台设备才能称之为网络互连,那发送方有一个数据自应用层向底层链路层的加工过程,对应的,在数据接收方,有一个数据从链路层向应用层解析的过程。这中间可能经历了漫长的传输介质,比如光纤,还可能有若干个中间设备,比如路由器、交换机等等。要保证数据在这么复杂的网络环境中可靠、准确的发送到目标机器,就是靠的TCP、IP协议精巧的设计。 TCP协议 TCP,全称是TransmissionControlProtocol,传输控制协议。是一种面向连接的、可靠的字节流服务协议,正因为它要保证可靠性,所以比起UDP协议要复杂的多,正是由于这种复杂性,导致它的性能比UDP差。 TCP是TCPIP模型中的传输层一个最核心的协议,不仅如此,在整个4层模型中,它都是核心的协议,要不然模型怎么会叫做TCPIP模型呢。 它向下使用网络层的IP协议,向上为FTP、SMTP、POP3、SSH、Telnet、HTTP等应用层协议提供支持。其他的还有我们常用的Redis的RESP协议、MongoDB的网络协议,以及我们编程中用到的Socket,都是TCP协议在背后提供支持的。 网络协议是通信计算机双方必须共同遵从的一组约定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流。它的三要素是:语法、语义、时序。语法:即数据与控制信息的结构或格式;语义:即需要发出何种控制信息,完成何种动作以及做出何种响应;时序(同步):即事件实现顺序的详细说明。TCP协议格式 TCP首部用户数据被称为TCP段,其中TCP首部就是这里要主要研究的TCP协议的核心所在,用户数据部分是TCP段的负载。 TCP段的大小也是有限制的,最大是1460字节,这是怎么算出的呢? 最终由网卡发出去的数据包叫做以太网帧,以太网帧由以太网首部和负载构成。 以太网帧的负载就是一个IP数据报,IP数据报由IP首部和负载构成。 IP数据报的负载就是一个TCP段。所以,TCP段所能搭载的最大数据量可以这样计算出来: TCP段搭载的数据大小以太网帧大小以太网首部IP首部TCP首部 以太网帧的大小是固定的1522字节,而IP首部和TCP首部的大小是不固定的,但是最少会各占20字节,所以最后算下来TCP段搭载的数据大小最多为1460字节。 TCP段搭载的数据大小(最多1460)以太网帧大小(1522字节)以太网首部(22字节)IP首部(最少20字节)TCP首部(最少20字节) 下图是TCP协议的示意图,如果不算可选项部分的话,共占用32bitx5160bit,也就是20个字节。 源端口和目标端口 源端口和目标端口分别占用2个字节,共占用4字节,分别记录数据发送端的端口号和数据接收端的端口号,这两个标记和IP协议中记录的发送端IP和接收端IP组合起来,便可确定一个唯一的TCP连接。序号 由于TCP段的大小有限制,当要传输的数据量大于这个限制的时候,就要对数据进行分段,一段一段的发送,既然发送方要分段,那接收方就要对分段进行重组,才能还原回原始数据。在重组的过程中,要保证各段间的先后顺序,序号正是起到保证重组顺序的作用。 序号占用4字节,32位,它的范围是〔0,2{32}〕。TCP是字节流服务,会对每一个发送的字节进行编号。在建立连接的时候,系统会给定一个ISN(初始序号),然后这个设备在当前连接中发送的第一个字节的序号就是ISN1,假设ISN初始为0,那第一个字节的序号就是1。 举个例子,假设ISN为0,发送端第一次发送100字节的数据包,那这第一个TCP段的序号就是1,下次再发送100字节的数据包,那这第二个TCP段的序号就是101。 这样一来,最大可以一直标记2{32}个字节,也就是4个G的数据。当达到最大值后,又会从0开始标记。 序号只有在下面两种情况下才有用:数据字段至少包含一个字节。是一个SYN段,或者是FIN段,或者是RST段。确认序号 当数据发送出去,接收方收到之后,会回复一个确认序号回复给发送方,这个确认序号表示接收方希望下次接收的序号。例如发送了序号为501的,长度为100的TCP段,那接收方收到后要回复601的确认序号,表示【0600】的字节已经接收,下次希望收到第601个字节以后的数据。 为了提高效率,并不是每次接收到TCP段都会马上回复给发送方,而是采用累积确认的方式,即每传送多个连续TCP段,可以只对最后一个TCP段进行确认。 确认序号只有在ACK标志位被设置的时候才有效。首部长度 之所以需要首部长度,是因为可选项的大小是不固定的,如果没有可选项的话,那首部长度就是20字节。这个标示部分占4bit,单位是4字节,4bit可表示的最大值是15,一个单位表示的长度是4字节,所以首部长度最大可以是15x4字节,也就是60字节。 保留 顾名思义,是保留位,占用6个比特位,目前的值为0。6个标志位 协议中有6个比特标记位,可以理解为TCP段的类型。 URG 1个比特位,当被设置为1时,表明紧急指针字段有效,该报文段有紧急数据,应尽快发送。 ACK 当ACK设置为1时,确认号才有效,连接建立后,所有的报文段ACK都为1。 PSH 当PSH设置为1时,接收方应该尽快将这个报文段交给应用层,而不再等待整个缓存填满再交付。 RST 当RST为1时,表示连接出现严重错误,必须重新建立连接。 SYN 在建立连接时用到。 当SYN1,ACK0时,表明这是一个连接请求报文段。 当SYN1,ACK1时,表明对方同意连接。 FIN 用来释放一个连接窗口。当FIN1时,表明此报文段的发送方不再发送数据,请求释放单向连接。TCP断开连接用到。窗口大小 大小为2个字节,表示发送方自己的接收窗口,用来告诉对方允许发送的数据量,最大为65535字节。检验和 校验和是必需的,是一个端到端的校验和,由发送端计算,然后由接收端验证。其目的是为了发现TCP首部和数据在发送端到接收端之间发生的任何改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃。紧急指针 占2字节,当URG1时,紧急指针表示本报文段中的紧急数据的字节数,表示从这个TCP段的序号开始的后的若干个字节是紧急数据,之后的就是普通数据。 假设此TCP段的序号为101,紧急指针为30,那就表示从101开始,直到131,【101,131】这个区间内为紧急数据。三次握手和四次挥手 数据要完成传输,必须要建立连接。由于建立TCP连接的过程需要来回3次,所以,将这个过程形象的叫做三次握手。 而连接断开的时候要经过四次数据传输,所以也被称为4次挥手。 啥都别说了,先看图吧。 三次握手,建立连接 结合上面的图来看更清楚。 先说三次握手吧,连接是后续数据传输的基础。就像我们打电话一样,必须保证我和对方都拿着电话在听,才能保证我们两个说的话对方能够接收到。 三次握手大概就是这个意思: 张三想跟李四聊聊天,于是张三拨通了李四的手机号,李四听到铃声响起,按了接听按钮。 张三:Hi,李四,是你吗?唠两块钱的呀! 李四:Hi,张三,是我,可以唠。 张三:好,我确定是你了,接下来我要开始和你唠了。 看上去多少有点儿死板,但程序上确实就是这样的。 1、第一次握手 首先客户端发起连接请求,向服务器发送TCP段,段中包含了目标端口和本机端口,设置SYN标志位为1,序号为x,也就是初始序号ISN,如果是第一个连接,很有可能就是0。当然,此时服务器对应的端口要处于监听状态。此时,客户端进入SYNCSENT状态,等待服务器的确认。 2、第二次握手 服务端收到客户端发来的SYN段,对这个SYN报文段进行确认,设置AcknowledgmentNumber为x1(SequenceNumber1),这就是确认序号。同时,服务端还要发送SYN请求信息,将SYN位置为1,SequenceNumber为y(服务端的TCP段序号)。服务器端将上述所有信息放到一个TCP段(即SYNACK段)中,一并发送给客户端,此时服务器进入SYNRECV状态。 3、第三次握手 客户端接收到服务端发来的SYNACK段后,发送一个ACK给服务端,将AcknowledgmentNumber设置为y1。此时客户端进入ESTABLISHED(已连接)状态,服务端接收到此TCP段,也将进入ESTABLISHED状态,也就标志着三次握手结束,连接成功建立。 三次握手完成之后,连接就建立了,之后就可以愉快的传输数据了。四次挥手,江湖再见 一旦有了感情(连接),再分手就难了,难到需要四次挥手。不像UDP那样,没有连接,说分就分。 当客户端和服务端双方发送数据完成后,一般会由客户端主动发起断开连接的请求,当然,也有少数情况是服务端主动发起。 以最常见的客户端发起断开连接为例,说一下四次挥手的过程。 1、第一次挥手 客户端设置序号(SequenceNumber)和确认序号(AcknowledgmentNumber),发送一个FIN段给服务器。这时,客户端进入FINWAIT1状态,意味着客户端没有数据要发送了。 2、第二次挥手 服务端收到FIN报文段,向客户端发送一个ACK段,客户端进入FINWAIT2状态。表示服务端已同意连接关闭请求。 3、第三次挥手 服务端向客户端发送FIN段,请求关闭连接,同时服务端进入LASTACK状态。 4、第四次挥手 客户端收到服务端发来的FIN段,向服务端发送ACK段,之后客户端进入TIMEWAIT状态。服务端收到客户端的ACK段以后,就关闭连接。 上面就是由客户端主动发起关闭连接的过程。半关闭状态 TCP是一个全双工的字节流服务,意思就是说两个端点都可以同时发送和接收消息。 正常情况下需要四次挥手才能完成连接的完全断开。但是有一种情况是这样的,只主动关闭自己到对方的连接,但是对方还是可以给自己发送数据。 用WireShark抓住TCP Wireshark是帮助我们分析网络请求的利器,建议每个同学都装一个。我们先用Wireshark抓取一个完整的连接建立、发送数据、断开连接的过程。 我这儿只简单的介绍一下操作流程。 1、首先打开Wireshark,在欢迎界面会列出当前机器上的所有网口、虚机网口等可以抓取的部件。 2、我接下来要用Telnet连接一个外网服务器,所以我选择第一个WIFI:en0,这样Wireshark就会捕获我连接的wifi上的网络传输。 3、我只想要抓一下最简单的TCP连接、发数据、断开的过程,所以要做一下抓取过滤。Wireshark中的过滤器可以实现这样的需求。在下图红框部分可以选了一个过滤器。 4、因为当前没有直接可用的符合要求的过滤器,所以,需要自己写一个。点击前面的绿色书签图标,然后在弹出窗口中点击加号添加一个。 内容如下,语法就不解释了,一看就知道。tcpandhost你的远程ip 5、选择好刚添加的这个过滤器,双击wifi这个interface进入就开始捕获了。 6、我用telnet连接这台服务器的6379端口telnetip6379,因为这台服务器上装着redis,可以模拟发数据。 在控制台中连接到6379端口成功,然后在Wireshark上马上捕获到了。 这就是三次握手的过程。 7、然后直接关掉终端,这样会自动触发断开连接,并且发送最少的数据,方便我们观察。整个的过程都被Wireshark完整的捕捉到了。 第一部分是连接建立的三次握手,第二部分是发了长度为1个字节的数据,第三步是客户端主动发起的断开连接的四次挥手过程。Wireshark简单介绍 有图先看图 概览信息 也就是图中最上面的红色框部分。这一次的连接建立和中断一共产生了来回8次的请求,每次请求会在列表上列出时间、源端IP、目的端IP、以太网帧长度以及概览信息,包括数据传输方向(源端口目标端口)、标记情况、序号、确认序号、窗口大小等等。 以太网帧 在每次请求信息中,还包括以太网帧,因为信息最终都会通过帧的形式发送出去。 IP数据报 还有IP数据报内容,其中包含了源端IP和目的端IP等信息。 TCP段 TCP段当然是重点了,其中包含了TCP协议中的所有信息,包括端口号、 粘包、半包 MTU是什么 MTU全称是最大传输单元,一个在网络上传输的包不能无限大,MTU一般是对于链路层而言的,拿以太网来说,在链路层允许发送的最大的以太网帧的数据部分就是1500字节。注意是以太网帧的数据部分,再加上以太网帧的头部,会大于1500字节。 通过ifconfig(windows系统是ipconfig)可查看本机各个网络接口(网卡)的MTU大小。 MSS是什么 MSS指TCP最大报文长度,是TCP协议定义的一个选项,MSS选项用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度。还是用以太网为例,MTU是1500字节,减去TCP头(20字节)和IP头(20字节),就是MSS1460字节。 粘包 粘包就是将几个比较小的TCP包合并成一个包,这样就只发送一次就可以将多个小包发送出去。例如下面这样,一个TCP报文请求中,包含小包A、B、C,每一个小包原本都是一个TCP报文。 为什么要粘包呢?一个一个发送不行吗? 其实是可以的,只不过在多数情况下来一个包马上就发送可能会造成网络拥塞,一个TCP报文传输到链路层的时候,会加上TCP头和IP头,占用40字节,如果发送的数据内容很小,比如只有1个字节,为了这一字节的内容,要有40倍的额外的信息被传输,是不是有点浪费。 为了减少这种浪费,TCP协议就做了一些优化,比如Nagle算法:Nagle算法规定每次只有收到上一个包的确认(ACK)之后,才会发送下一个包,在这个时间段内正好将小的包粘到一起;但是太多的包也不行,大小不能超过MSS,也就是前面刚说的1460字节,太大了装不下;如果没有那么多小包,也不能一直等着,有一个超时时间,大约是200ms,超过这个时间也要发送; 由于现在的宽带和设备性能的提升,Nagle算法其实可以关闭了,有些设备上默认就是关闭的,也可以在写Socket的代码的时候主动关闭掉,关闭之后呢,只要接收端处理能力够快,可以保证来一个包马上发送,对那些要求实时反馈的应用来说尤其重要。 那来一个包发一个包,是不是就不会有粘包的问题了?也不是,这就要看接收端的处理能力了,接收端会有一个接收缓冲区,来不及被应用程序处理的会暂时放到这里,如果应用程序处理能力较差,这里还是会出现粘包。拆包 既然发生了粘包,就要把这些大包拆成小包。怎么拆分其实都是上层应用的事儿了,核心要点就是约定好分隔符。举个简单的例子,比如说将包A和包B用一个特殊字符分隔开,那应用在拆包的时候就要根据这个特殊字符进行分隔。当然了,真实情况要比这个复杂的多,如果你用过Netty,就会发现Netty提供了多种处理粘包拆包的方式。什么是半包 粘包是为了将多个小包变成一个大包,而半包是把超大包拆成小包。比如下图,假设包B是一个很大的包,已经超过了MSS了,单单发送它自己都发不过去了,所以只能将它拆开,一部分一部分的发送。 半包就没那么复杂了,纯粹是因为单独的包太大,协议不支持这么大的包,只能拆开。 这样一部分一部分的包,到了接收端之后就要将其合并为一个整体,合并也比较简单,就是如果这个部分包没有开始或没有结束标志,就表示它不是完整的,需要给其找到对应的其他部分。滑动窗口 接收方通告的窗口称为offeredwindow,意思就是说我这边可以接受的最大字节数为这么多。例如下图中的红框部分为offeredwindow,大小为6字节,发送端最大一次只能发送6个字节,要不然接收方就没有能力接收了。 可用窗口offeredwindow已经发送但未被确认的字节大小,这个值由发送方自己计算。前面说了三次握手,发送方发出去包,接收方接到后会反回一个ACK,发出去但未收到ACK的数据也会占用窗口,表明接收方正在处理,所以,可用窗口的大小是offeredwindow减去未收到ACK的大小。 为什么叫滑动窗口呢,看上面的图,把一个个字节想象成排成一排的格子。 首先看时刻1:红色格子的部分就是offeredwindow,大小为6字节,后面10、11、12字节因为没在窗口内,所以不能发送。已发送但未被确认的也占用窗口大小,所以最终可用窗口就是7、8、9这三个字节。 再看时刻2:刚才未被确认的4、5、6字节收到了ACK,所以16都变成了过去式,然后窗口覆盖到了7、8、9、10、11、12这6个字节,对比时刻1和时刻2,给我们的感觉就是窗口(红色格子)向右滑动了,这就是所谓的滑动窗口了。 还有,窗口两个边沿的相对运动增加或减少了窗口的大小。当窗口左边沿向右边沿靠近为窗口合拢。这种现象发生在数据被发送和确认时,假设现在接收方处理数据的时间变长了,来不及快速处理,那接收方在下次ACK的时候返回的窗口大小可能就会变小。 当窗口右边沿向右移动时将允许发送更多的数据,我们称之为窗口张开。这种现象发生在另一端的接收进程读取已经确认的数据并释放了TCP的接收缓存时。慢启动和拥塞避免算法 在使用TCP传输的过程中,肯定是希望数据传送的越快越好,但是在实际使用场景中,由于发送端和接收端处理数据的速度不一致,或者由于中间路由器性能限制、带宽限制等原因,发送的速度越快,越有可能导致丢包的情况。比如一下子发送了10M的数据出来,但是中间路由器只能处理5M,很可能就会把一些包丢弃。 因而设计了慢启动和拥塞避免算法,这两个设计都是为了合理的匹配发端的发送速度与收端的处理速度。慢启动 在连接刚建立的时候,发送端也不知道应该按什么速度发比较合适,所以就采用了一种渐进式的方式,就是慢启动的方式。 前面说了offeredwindow是接收端的,在发送端也有一个窗口,叫做拥塞窗口,记做cwnd,拥塞窗口初始化为1,表示1个报文段,也就是允许发送1个报文段,之后每当每当收到接收端返回的ACK时,就将cwnd的值加1。第一次发送一个数据报,当收到ACK后,cwnd变为2,然后下一次发送两个数据报,当收到这两个数据报的ACK时,cwnd就变成4。以此类推,这个增长是呈指数级的。 但是,在这个过程中,也是有限制的,发送的数据报大小要在消息接收端返回的通告窗口大小和cwnd中取较小的那个值。假设一个报文大小为1024字节,当cwnd为2,通告窗口大小为4096字节时,那发送端你可以连着发送2个数据报,也就是取cwnd的值,当cwnd为8时,通告窗口大小仍然为4096字节时,那发送端最多可连续发送4个数据报,也就是不能超过4096字节。拥塞避免 拥塞避免算法其实和慢启动是在一起使用的。在慢启动中除了有拥塞窗口外,还有一个叫做启动门限(ssthresh)的参数。启动门限默认的是65535字节。 在慢启动中,cwnd是呈指数级增长,但是这个增长速度太快了,所以,拥塞避免算法就是让这个增速减缓的方式。 当cwndssthresh的时候,就使用慢启动。 当cwndssthresh的时候,就启动拥塞避免算法。 拥塞避免算法保证当cwnd超过限制之后,每次收到一个确认时将cwnd增加1cwnd。 当拥塞发生时(超时或收到重复确认),ssthresh被设置为当前窗口大小的一半(cwnd和接收方通告窗口大小的最小值,但最少为2个报文段)。 用一张图来说明慢启动和拥塞避免算法 假定当cwnd为32个报文段时就会发生拥塞。于是设置ssthresh为16个报文段,而cwnd为1个报文段。在时刻0发送了一个报文段,并假定在时刻1接收到它的ACK,此时cwnd增加为2。接着发送了2个报文段,并假定在时刻2接收到它们的ACK,于是cwnd增加为4(对每个ACK增加1次)。这种指数增加算法一直进行到在时刻3和4之间收到8个ACK后cwnd等于ssthresh时才停止,从该时刻起,cwnd以线性方式增加,在每个往返时间内最多增加1个报文段。 正如我们在这个图中看到的那样,术语慢启动并不完全正确。它只是采用了比引起拥塞更慢些的分组传输速率,但在慢启动期间进入网络的分组数增加的速率仍然是在增加的。只有在达到ssthresh拥塞避免算法起作用时,这种增加的速率才会慢下来。重传机制 什么情况下要重传,当发送端认为丢包了就要重传,有两种情况下发送端就认为丢包了,于是就会发起重传。超时重传 发送端在一段时间(超时时间)后没有收到发送端返回的ACK,就认为这个包丢了,这个超时时间并不是固定的。 这里面有两个概念,RTT和RTO。RTT(RoundTripTime):往返时延,也就是数据包从发出去到收到对应ACK的时间。RTT是针对连接的,每一个连接都有各自独立的RTT。RTO(RetransmissionTimeOut):重传超时,也就是前面说的超时时间。快速重传 接收端回复的ACK会带着包的序号,当接收端重复三次收到同一个序号的ACK时,就要重传这个包; 比如下面图中画的这样: 1、seq1的包发过去,接收端ACK2,表示期望下次出现的序号为2,然后发送端就发了seq2的包,接收端接到后回复ACK3,表示期望下次收到序号为3的包,这是发送端第一次收到ACK3; 2、发送端继续发送seq3的包,但是这个包可能传输的比较慢(比如路由选择的不好),接收端一直没收到; 3、发送端先不管,继续发送seq4的包,接收端收到后,回复ACK,正常情况下应该是ACK5,但是序号为3的包还没收到,所以再次回复ACK3,这是第二次收到ACK3; 4、发送端继续不管,接着发送seq5的包,接收端收到后,回复ACK,正常情况下应该是ACK6,但是序号为3的包还没收到,所以再次回复ACK3,这是第三次收到ACK3; 到目前为止,已经收到三次ACK3了,然后发送端就重新发送seq3的包,这时候就当做这个包已经丢了。这就是快速重传。