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

中小企业信息查询平台官网山东网站建设seo

中小企业信息查询平台官网,山东网站建设seo,中投建设集团有限公司 网站,wordpress相册博客类主题争做西格玛男人 文章目录 一、UDP协议1.端口号2.理解UDP报头3.UDP的特点#xff08;面向数据报#xff0c;全双工#xff09; 二、TCP协议1.理解TCP报头某些TCP的策略1.1 TCP报头字段#xff08;TCP的黏包问题#xff09;1.2 网络协议栈和linux系统的联系#xff08;以p…争做西格玛男人 文章目录 一、UDP协议1.端口号2.理解UDP报头3.UDP的特点面向数据报全双工 二、TCP协议1.理解TCP报头某些TCP的策略1.1 TCP报头字段TCP的黏包问题1.2 网络协议栈和linux系统的联系以port为键值的开散列哈希表哈希桶存储port对应的PCB的地址1.3 从代码层面理解TCP报头结构体数据1.4 确认应答机制序号和确认序号TCP面向字节流的特点1.5 流量控制16位窗口大小1.6 TCP报文段的类型6个标志位详解URG和RST1.7 超时重传机制数据包在超时时间窗口内没有收到应答则判定为丢包进行重传 2.连接管理机制2.1 为什么要三次握手最小成本验证全双工通信防止产生单主机对服务器进行SYN洪水攻击的漏洞2.2 双方四次挥手时状态的变化理解CLOSE_WAITTIME_WAIT状态2.2.1 四次挥手的详细过程2.2.2 测试TIME_WAIT状态2.2.3 主动断开连接的一方为什么要维持2MSL时间的TIME_WAIT状态2.2.4 解决服务器立即重启无法bind原来端口号的问题 3.TCP的高效性3.1 滑动窗口批量化发送数据段支持超时重传机制3.2 拥塞控制3.2.1 拥塞控制和超时重传机制大面积的丢包拥塞控制小面积的丢包超时重传3.2.2 拥塞窗口的引入MSS和SMSS3.2.3 慢启动算法拥塞避免算法阈值前指数增长阈值后线性增长 3.3 延迟应答 捎带应答 4.TCP相关问题和相关实验4.1 TCP异常情况4.2 用UDP实现可靠性传输4.3 理解listen的第二个参数backlog全连接长度backlog1 一、UDP协议 1.端口号 1. 在网络通信中通信的本质实际就是两台主机上的进程在网络环境中进行通信也就是数据的传输而我们总说TCP/IP协议栈这两个协议分别解决了两个重要的问题即一台主机如何在网络环境中标定自己的唯一性一台主机中的某个进程如何在主机内部标定自己的唯一性实际就是通过网络层协议IP地址和传输层协议端口号port来解决这两个问题的。 2. 端口号一般可以分为知名端口号和操作系统动态分配的端口号知名端口号的范围是0-1023例如使用HTTPHTTPSSSHFTP等应用层协议的进程bind的端口号都是知名端口号他们的端口号都是固定的1024-65535是操作系统动态分配的端口号我们自己在写服务器时要避免bind知名端口号因为这些端口号早已被成熟的应用层协议占用了。 所以我们之前在写socket套接字编程的时候无论是UDP服务器还是TCP服务器bind的端口号都是1024-65535范围的port不会占用知名端口号。 3. 一个进程是否可以bind多个端口号呢可以的一个进程可以同时提供多个应用层网络服务比如一个进程内部可以创建出多个thread每个thread都可以调用socket接口创建struct sockaddr_in 结构体然后再将sockfd和结构体进行bind这样就可以在一个进程内部bind多个端口号使得一个进程可以提供多种应用层网络服务了。 一个端口号是否可以被多个进程bind呢一个端口号一定是不可以被多个进程bind的因为端口号需要标识进程的唯一性如果多个进程bind了同一端口号那数据包从传输层在向上交付时该将数据包交付给哪个进程呢我们知道数据包是通过端口号来向上交付给特定的进程的所以一个端口号是不能被多个进程bind的端口号到进程必须是具有唯一性的。 4. 接下来介绍两个工具一个用于查看网络中连接状态的命令行工具netstat一个是用于快速查看服务器进程id的命令行工具pidof 2.理解UDP报头 1. UDP协议的报头格式如下因为UDP不需要保证可靠性所以UDP报头的字段内容也会比较少所以UDP通信起来比较简单。无论是学习什么协议都要能够做到解包和分用像我们之前自己制定应用层协议时进行应用层报头和有效载荷分离时我们是通过特殊分隔符\r\n的方式来分隔报头和有效载荷。而在UDP这里其实是通过固定报头长度的方式来进行有效载荷和报头的分离的进行分用时只要通过16位目的端口号就可以将数据向上交付给特定的应用层进程。 还有一个字段16位UDP长度用于表示报文的整体大小。校验和我们一般不关心如果校验和正确则证明该报文在传输过程中没有发生损坏对端正常接收该报文即可如果校验和出错例如发生比特位翻转等问题则对端直接丢弃该报文该报文视为无效报文。 2. 对UDP报头仅仅理解到上面那种层次是绝对不够的上面仅仅是从逻辑层面的理解我们需要将理解深入到代码层面才能更好的认识UDP报头。 传输层和网络层都是在linux内核中实现的而linux内核是用C语言实现的那UDP报头实际就是一个结构体结构体成员变量实际就是UDP报头中的各个字段值所以在分用时只需要让指针指向数据包的前8个字节然后将指针类型强转成结构体类型然后读取里面成员变量的值以此来实现分用。 在C语言中即使是结构体数据他其实也是二进制的字节流如果想要将报头和有效载荷粘连在一块我们可以开辟一大块char数组然后将结构体数据按照字节流的方式拷贝到char数组中然后紧接着再将有效载荷拷贝到里面想要读取UDP报头进行分用时可以直接将指针强转成结构体类型然后进行成员选择读取UDP报头的内容。 3.UDP的特点面向数据报全双工 1. UDP最典型的特点就是面向数据报创建socket时内核会在传输层建立两个内核缓冲区一个是发送缓冲区一个是接收缓冲区UDP在发送数据时可以理解为不使用发送缓冲区而是直接将数据包向下交付这点和TCP面向字节流是极大不同的面向字节流会使用发送缓冲区来提升发送效率比如使用滑动窗口等策略。 数据报之间是有明确边界的所以应用层在读取UDP传输层缓冲区中的数据时只可能读取到一个完整的报文或者压根没有读取到报文(报文丢失)不会出现读取1个多报文或者半个报文的情况这点与字节流也是不同的在TCP中接收缓冲区中会包含多个数据包的有效载荷这些有效载荷是按照字节流的方式被应用层读取的所以应用层就可能会读取1个多报文的内容也可能读取半个报文的内容因为他是字节流的报文和报文之间是没有明确边界的。 换种形象点的说法UDP通信就像寄信一样发送方将一个一个的信封寄到对端对端接收的时候就会一个一个的接收信封然后打开信封看里面的内容而TCP通信就像把所有的信的内容写到一张大A4纸上然后直接将这个A4纸发给对端对端接收时需要自己判断A4纸上的内容从哪到哪是一封信的内容从哪到哪是下一封信的内容。 2. 并且从UDP和TCP通信时使用的套接字编程API的不同我们也可以看出UDP面向数据报和TCP面向字节流之间的差异UDP是无连接的所以UDP每次在发送数据时都需要指定对端的socket地址同样每次在接收数据时也需要指定发送端的socket地址保证在每次发送时将一整个数据报都发送给对方对方接收时也是直接接收一整个数据报所以sendto调用几次recvfrom就会相应的调用几次。 而TCP是有连接的TCP在发送数据时首先send会将应用层数据拷贝到socket发送缓冲区中而实际发送的时候TCP有自己的发送策略因为TCP叫做传输控制协议比如滑动窗口策略拥塞控制等等所以一个完整的数据报在socket发送缓冲区可能并不会完整的发送到对端而是会经过拆分或和其他报文组装之后再发送给对端因为这样能够提升TCP数据传输的效率这也是字节流的特点。所以send调用几次和recv调用几次他们之间并没有必然的联系可以调用send100次将100个字节的数据进行发送对端可以调用recv一次读取100字节的数据这就是字节流。 3. 无论是UDP还是TCP在创建socket时内核会相应的创建出发送和接收缓冲区而缓冲区其实有点像生产消费模型里面的环形队列它可以使得通信双方解耦应用层只要将数据拷贝到内核socket缓冲区之后应用层就可以作别的事情了至于数据什么时候穿过网络协议栈进行发送发送过程中数据丢失怎么办这些问题都是传输层来解决应用层根本不关心 所以我们说缓冲区是可以实现通信双方解耦的问题的同时对于TCP来讲发送缓冲区还可以提升TCP数据传输的效率对于UDP而言UDP并没有真正意义上的发送缓冲区但实际他是有的只不过这个缓冲区没有什么用内核收到该数据后会直接封装报头讲数据向下交付给网络层进行后续处理。 4. 无论是UDP还是TCP他们都是全双工的因为双方都有一套发送和接收的缓冲区这使得在一个时间点上client既可以给server发送数据server又可以给client发送数据这极大提高了网络中通信的效率。缓冲区就像一个超市的存在client的应用层就像producerserver的应用层就像consumer这就是典型的生产消费模型支持忙闲不均使通信双方解耦。 5. 另一个需要说明的点是我们学习的所有网络级别的系统调用接口并不是网络数据发送和接收的接口而是拷贝接口例如你在调用send sendto write时实际上是把数据从你在应用层定义的缓冲区中拷贝到内核中的接收缓冲区当数据继续向下贯穿协议栈时传输层自己会将内核中的缓冲区的数据提取出来然后添加上报头向下交付给网络层同样当你调用recv recvfrom read时实际上也是把数据从内核缓冲区中拷贝到应用层的缓冲区应用层的缓冲区实际就是我们定义出来的 char buffer所以我们说网络IO接口实际就是网络拷贝接口。 值得注意的是内核socket缓冲区中存放的是传输层的有效载荷是不包含UDP报头或TCP报头的。 6. UDP的报头中有一个16位UDP长度的字段值所以UDP报文的最大长度就是2^16次方大小也就是65536字节如果应用层的报文长度超过65536-20的长度则应用层需要自己手动的分包分为多个报文进行网络数据传输接收端自己收到数据后需要手动进行拼装。 做法其实很简单因为UDP是面向数据报的而且通过16位UDP长度这个字段值在传输层可以轻松得到有效载荷的字节大小所以在发送端应用层可以在拆分的报文中增加一个序号比如在应用层的有效载荷的第一个字节的位置增加一个序号然后再将拆分的各个报文向下交付接收端在接收时从传输层将有效载荷拿到应用层时可以读取完应用层报头后读取有效载荷时判定该报文的序号根据序号来重新组装。 实际我说的还是有点麻烦了我们可以将应用层报文的序号直接放到应用层报头里面对端读取时只要读完应用层报头就可以知道当前这个报文的序号是多少了。 从UDP报头中的16位UDP长度可以得到有效载荷的大小我们也可以发现UDP面向数据报的端倪为什么说UDP不存在黏包问题呢因为接收方在自己的传输层就可以通过UDP报头得到有效载荷的大小每一个报文的有效载荷大小都可以确定所以接收方在读取的时候是可以做到精准的只读取一个报文的不存在黏包问题(多个应用层报文在内核缓冲区中互相之间没有边界引用层读取时不知道当前读取的数据是一个数据包还是多个数据包黏在一块的)。 二、TCP协议 1.理解TCP报头某些TCP的策略 1.1 TCP报头字段TCP的黏包问题 1. TCP全称为transmission control protocol传输控制协议人如其名TCP对于数据的传输有着详细的控制既能够通过多种策略保证他耀眼的可靠性又能通过其他策略同时保证他的高效性。 学习任何一个协议我们都需要考虑该协议如何做到报头与有效载荷分离如何将分离后的有效载荷向上交付给上层TCP协议报头有自己的标准长度20字节如果报头中携带了其他的TCP头部选项则报头就不止20字节其中4位首部长度代表着TCP报头的大小4位首部长度的值×单位5byte等于TCP报头的长度所以TCP报头的长度在20字节-60字节范围之间。 拆分TCP报文时只需要先读取标准头部长度20字节的内容读到4位首部长度这个字段值然后×5这样就可以获得完整的TCP报文的长度而剩余部分则为有效载荷向上交付也非常简单标准头部中有16位目的端口号通过端口号就可以准确的将有效载荷交付给特定的应用层协议(使用该协议的process) 2. 现在有一个问题TCP报头里面只有表征TCP报头长度的字段却没有表征有效载荷大小的字段那接收方在应用层读取TCP缓冲区中的有效载荷时如何才能确定读取的是一个报文中的有效载荷呢以达到没有多读或者少读的情况。 其实这也是TCP面向字节流的特点这点与UDP面向数据报是完全不同的TCP没道理解决你应用层能不能读取到一个完整报文的问题TCP没有这个责任与义务因为TCP是面向字节流的TCP缓冲区中的数据就是一个个字节存放着的并不像UDP那样面向数据报从而有效载荷(传输层)之间有着明确的边界这也正是字节流的特点。 那这个问题(其实就是黏包问题)谁解决呢当然是应用层来解决应用层需要定好协议以便接收方在读取报文时能够完整的读取一个报文其实这个问题我们当时在写网络版本计算器时就解决过这个问题我们当时通过\r\n作为特殊字符分隔符来明确两个包之间的边界应用层报头和报文之间有特殊分隔符报文的末尾也有特殊字符分隔符。 1.2 网络协议栈和linux系统的联系以port为键值的开散列哈希表哈希桶存储port对应的PCB的地址 1. 前期我们在说端口号的时候我们说传输层通过协议报头中的16位目的端口号将数据交付给使用应用层协议的特定进程我们这么理解确实没问题但理解到这个程度还是不够深刻我们需要将这个过程细化将网络协议栈和Linux的文件系统联系起来从而更好的理解传输层向上交付有效载荷这个过程。 实际上内核中会维护一个以port端口号为键值的开散列哈希表哈希桶中会存放指向PCB结构体的指针传输层通过port来向上将有效载荷交付给特定进程时通过哈希表就可以快速找到特定的进程结构体了所以向上交付不是一句空话而是需要通过特定的数据结构来完成的。 除此之外调用socket接口返回的sockfd其实就是文件描述符该文件描述符其实就是fd_array数组的下标该下标对应的位置中会存放指向文件结构体的指针文件结构体内部会维护创建sockfd时内核同时创建出来的接收缓冲区和发送缓冲区的指针该指针名为struct sk_buff * sk_receive_queue/sk_send_head所以收发缓冲区也不简单是一个数组而是一个结构体。 网络在收到数据时对端主机会先将数据放到用于通信的sockfd对应的文件结构体内部的接收缓冲区中这样应用层就能以文件描述符的方式来读取网络数据等上层调用recv recvfrom read等网络IO接口时实际就是把sockfd对应的文件结构体内部的接收缓冲区中的数据拷贝到应用层缓冲区 1.3 从代码层面理解TCP报头结构体数据 1. 理解TCP的报头和UDP报头一样他们其实都是linux内核里面的结构体当向网络层交付报文时TCP会把发送缓冲区中的数据和TCP报头粘连在一起然后统一向下交付。 我在从代码层面理解TCP报文统一向下交付的过程时内核将结构体数据TCP报头拷贝到一个大的char buffer里面然后紧接着在将TCP有效载荷拷贝到这个大buffer里面这点其实很容易做到。下面的截图示范了如何将结构体数据拷贝到char数组里面然后在读取数组内容时完整的将结构体成员变量值给解释出来其实就是将指针类型做一下强制类型转换就可以。 当然内核是不是这样做的我也不清楚我这个水平还看不懂内核源码单单从逻辑层面上理解其实是可以这样理解的并且顺带还能复习一下C语言的指针和结构体这方面的知识。 2. 我们只需要将结构体数据看成二进制流就可以了其实也就是字节流然后我们可以调用memcpy将结构体数据拷贝到char数组里面需要注意的是在接收端接收到报文时进行读取报头的时候需要将指针强转为结构体类型以此来对char数组前一部分的数据进行解释拿到原本的二进制流语义以此来读到报头中的各个字段。 1.4 确认应答机制序号和确认序号TCP面向字节流的特点 1. 接下来我们来讨论保证TCP可靠性的其中的一个机制确认应答机制。 实际上为什么网络传输中会存在不可靠问题呢本质原因还是因为传输距离过长。 比如我在内蒙给广东的网友发送消息那数据包其实是要经过很多的路由器结点进行数据包转发穿过很多的局域网在局域网内部经过双绞线(以太网技术常用的物理介质)传输还要经过运营商的基站数据包在如此之长的传输距离中很有可能会丢失数据里面的比特位翻转又或是数据包中的字节乱序又或是数据包重复发送给我的广东网友(发送方可能以为数据包丢失了)。 TCP应该如何解决网络传输时的不可靠问题呢需要确认应答acknowledgement 虽然数据包在网络中传输的距离过长但只要我发送给我网友的消息有回复有应答那我就能判断我发的数据一定到达了我网友的主机上比如我问我网友你TCP/IP学的怎么样啊最近我网友给我回复说我最近正学TCP的确认应答机制呢那我立马就可以肯定我发送的数据经过网络传输后我的网友一定收到了因为网友对我发送的消息做出了回复。同样的如果我没回复我的网友那网友也不敢确定他说的话我一定收到了因为我还没有给他发送的消息做出回复呢而这就是典型的确认应答机制 但其实你可以发现我和我的网友在发消息时总会有最后一条消息是没有被确认的无论最后一条消息是他发还是我发所以我们可以得出结论TCP并没有绝对的可靠性只有相对的可靠性 所以TCP的可靠性永远不谈最新的消息只谈论历史的消息因为一定存在最新的一条消息是没有被应答的。 2. 我们在学习TCP时最常见的图就是左边的但实际TCP的工作模式是右边的发送消息时会一次性发送一批数据段确认应答时也会一次性发送一批确认数据段。如果一个数据段不包含任何有效载荷只有ACK标志位被置为1那我们称这个报文段为单纯的确认数据段不包含任何消息。 3. 谈论确认应答机制绝对离不开TCP报头中的序号和确认序号在TCP中每一个被发送的数据段都有自己的序号或确认序号而序号和确认序号的值其实就在TCP的报头中在发送时只要将数据段报头中的相应序号或确认序号的值填充上即可。 序号比较好理解相当于每一个数据段都有一个数字来唯一标识自己但确认序号的定义却是这样的确认序号的值表示接收方收到了ack序号之前的所有报文而且是连续的报文比如ack确认序号的值是11那就代表接收方收到了10号及之前所有序号的报文发送端下次从第11序号开始发送报文就可以了所以确认序号的值从发送方的角度来理解可以理解为发送方下一次发送报文时报文的序号的值。 如果server收到了10 12 13序号的报文则server向client发送确认数据段时确认序号只能是11号因为11号报文server没有收到即使收到了12 13也不能ack14因为必须保证ack确认序号之前的所有序号的报文都被收到才可以。 4. 为什么确认序号要这么定义呢 其实这样定义是有原因的后面讲滑动窗口时就能知道确认序号的精妙所在了它可以在某些情况下提高网络数据传输的效率。 client批量化发送数据段时数据段到达server的顺序和发送时的顺序一定是一样的吗 这是不一定的但数据到达server之后数据段乱序其实也是长距离网络传输不可靠的一种表现所以TCP在收到一批数据段之后首先会根据数据段的序号进行排序以此来保证数据段到达时的有序性 为什么要有两个序号一个序号不可以吗 因为TCP是全双工的所以需要两个序号一个报文段可以有双重身份既可以有确认序号又可以有序号兼具确认应答和发送网络数据的作用。比如client发送10号报文段内容是server你学完TCP了吗server发送20号报文段同时确认序号为11(通信双方发送的报文段序号是具有全局唯一性的同时序号和确认序号可以协商不会发生冲突的)内容是我学完TCP了。此时server发送的报文段就具有全双工的作用既是给client发送报文段的确认应答又是自己要发送给client的含有网络数据的报文段。这就是全双工通信。 5. 实际上只要将应用层缓冲区的数据拷贝到传输层缓冲区中天然的每个字节的数据就都有自己的序列号这也就是字节流的特点有效载荷之间无明显边界。 发送报文段时报文段的序列号为有效载荷的首个字节的数据在缓冲区中的序列号。 确认序号告诉发送者发送者下次从哪里发送数据以及接收方接收到了哪些数据。 1.5 流量控制16位窗口大小 1. 流量控制可以说是既能保证TCP的可靠性又能保证TCP的高效性谈论流量控制也一定离不开TCP报头中的16位窗口大小这一字段。 如果数据发送的太快对端可能由于接收缓冲区一下子被打满从而丢弃一些数据段。如果数据发送的太慢对端的应用层调用recv阻塞式读取接收缓冲区中的数据时可能会影响对端上层的业务处理因为recv默认是阻塞式读取。所以在TCP这里发送数据过快也不行过慢也不行速度必须要合适。 所以双方在发送数据时必须要知道对方对于数据的接收能力大小从而控制自己在给对方发送数据时的速度大小而16位窗口大小表示的就是自身接收缓冲区中剩余空间的大小告知对方自己对数据的接收能力所以双方在通信时每一次发送报文都会携带自己的16位窗口大小交换双方自己对于数据的接收能力以此来达到流量控制的目的。 2. 现在有一个问题发送方怎么在第一次发送数据的时候就知道对方的接收能力是多少呢 其实早在三次握手的阶段双方就已经互相交换了各自的接收能力握手期间就可以得知对方报头中的16位窗口大小的值从而控制自己发送数据的速度大小。窗口大小的字段越大网络对于数据的吞吐量就越高。 如果接收方的缓冲区被打满16位窗口大小值为0发送端得知对方缓冲区已经没有剩余空间后会停止发送报文但会每隔一段时间发送一个窗口探测报文接收方会返回一个窗口更新通知当接收方的缓冲区有剩余空间之后双方又会继续完成通信。 除此之外如果你想扩大16位窗口的大小在TCP40字节的头部选项中有一个窗口扩大因子选项扩大因子位MM的范围是0-14修改过后实际窗口大小会扩大2^M倍数值得注意的是窗口扩大因子选项只能出现在同步报文段中如果出现在其他通信报文段则该选项将被忽略。当连接建立好之后窗口扩大因子会固定不变。这个选项了解一下即可用的不多。 1.6 TCP报文段的类型6个标志位详解URG和RST 1. TCP报头中还有6个标志位不同的标志位代表不同类型的报文段服务器会收到来自不同的大量的客户端的报文段而每个报文段都会有自己的类型。 SYN同步报文段用于TCP三次握手建立连接时的连接请求 ACK确认报文段对历史报文段进行确认应答 URG表示紧急指针是否有效 FIN结束报文段用于断开连接进行TCP四次挥手 RST复位报文段reset表示重新建立连接 PSH催促接收方应用层尽快从传输层接收缓冲区中取走数据为后续到来的数据腾出空间。 2. 16位紧急指针表示的是紧急数据在有效载荷中的偏移量TCP规定死紧急数据只能有1字节当URG标志位被置为有效时紧急指针就会派上用场接收方在读取TCP报头之后会首先从紧急指针表示的偏移量处读取1字节的紧急数据然后再重新从有效载荷的起始位置读取完成剩余的数据这1字节的紧急数据我们一般称为带外数据。 实际上紧急指针和URG标志位我们在99.99%的情景下都用不到如果真要是用带外数据可能运维的一些人员会用到比如服务器现在压力非常大可以发送1字节的带外数据用于询问当前服务器的状态怎么样有没有恢复到健康状态服务器可以返回1字节的带外数据用1字节的数据来对应状态码返回服务器是因为什么原因而导致过载因为带外数据不用经过冗长的数据流可以直接在应用层读取。 所以带外数据实际上并不在正常的数据流中一般用带外数据的也就是UDP和TCP协议了。如果想要读取带外数据可以将recv的flags标志位按位或上MSG_OOB这样就可以读取带外数据了。 3. 三次握手建立连接并不一定能够成功建立连接没人说三次握手一定能够成功同样四次挥手也一样就算连接建立成功了那也是有可能断开的比如单方面的将服务器主机电源拔掉那连接不就会自动断开吗等服务器重启的时候服务器不认为连接建立成功但client还认为连接存在着所以client就会给服务器一直发消息服务器就会感觉很奇怪连接都已经不存在了你为什么还要和我通信呢所以此时服务器就会给client发送一个复位报文段其报头中的RST标志位被置为1告诉client说你别再给我发消息了连接早就异常断开了你再重新发起三次握手重新和我建立连接吧。 所以复位标志位用于通信双方中任何一方认为建立连接不一致时认为连接异常的一方会发送复位报文段告知对方我们需要重新建立连接。 下面这样的场景很多人应该遇到过吧其实就是因为服务器压力过大无法承载更多的连接导致连接被重置连接异常而服务器向我们的浏览器发送的报文段就是复位报文段让客户端重新和服务器建立连接。 1.7 超时重传机制数据包在超时时间窗口内没有收到应答则判定为丢包进行重传 1. 我们之前谈论过网络传输中不可靠问题之一丢包超时重传就是为了解决丢包问题。 丢包分为两种情况一种是数据段真的丢了一种是数据段发送过去了但ACK报文段丢了其实这两种情况发送方是无法分辨出来的发送方只能规定在一个时间段内如果发出去的报文没有收到确认应答则该报文就被认定为丢包就好比某些家长的孩子找不见了这个孩子可能仅仅是走丢了也可能失去life了也有可能被人贩子拐跑了家长其实无法确定孩子到底去哪了只能从时间维度上来判断如果1年或更长的时间孩子都没有回来那家长就认为孩子丢了。 2. 如果是第二种丢包情况那接收方会收到两个一模一样的报文此时接收方会对报文按照序号进行排序去重保证收到的报文是可靠的因为重复报文也是不可靠表现的一种。 3. 我们知道发送的数据段是有可能没有收到ACK的所以被发出的数据不应该立马被移除(计算机上的移除其实就是数据覆盖)应该先保存一段时间如果发送的数据丢包了则可以将保存的数据再重新发送而像这样已经发送但没有收到ACK的数据其实是存放在滑动窗口里面的这个后面会讲现在先提一下。 4. 超时的时间怎么定呢其实这个时间应该是随着网络情况动态变化的如果网络情况好超时时间设定的非常长这其实就会影响网络传输的效率因为数据包发送的速度非常快可能数据包来回一次共需要50ms但你将超时时间设定为500ms那中间的450ms的时间就会被平白无故浪费掉如果网络情况特别差超时时间设定的非常短那更离谱了数据包正在传输的过程当中就被判定为丢包了这同样也会影响数据传输的效率。 所以最理想的情况就是找出一个最短的时间保证绝大部分的网络情况下数据包都可以在这个最短的时间窗口内发送过去同时ACK报文能够发回来。 5. 在linux(unix和windows也一样)中超时实际上是以500ms作为基本单位来进行控制的如果第一次重发后还没有得到确认则会以2的指数幂×500ms的方式来逐渐增大超时的时间窗口累计达到一定重传次数则TCP会强制关闭双方建立的连接。 2.连接管理机制 2.1 为什么要三次握手最小成本验证全双工通信防止产生单主机对服务器进行SYN洪水攻击的漏洞 1. 三次握手是TCP建立连接的过程客户端先给服务器发送一个SYN报文段表示客户端想和服务器建立连接然后服务器确认应答客户端的连接请求同时服务器也想和客户端建立连接请求所以服务器会发送一个捎带应答的报文段报文段既是服务器想和客户端建立连接的SYN报文段同时兼具确认应答的作用当客户端收到来自服务器对他自己发送的SYN报文段的确认应答后客户端则会认为连接已经建立成功了客户端收到来自server的SYN报文段后客户端也会向server发送一个ACK确认报文段当server收到ACK报文段之后server则也会认为连接建立成功了当双方各自都认为连接建立成功后那么双方实际上就可以完成通信了这就是三次握手的整个过程。 2. 我们以前谈论过TCP只能保证历史消息的可靠性永远不谈最新一条消息的可靠性这句话没问题比如三次握手的最后一次握手这个消息的确就是不可靠的因为客户端发送的ACK报文段server是否收到客户端是不知道的但这没有关系就算ACK报文段丢失了那server不会认为连接建立成功此时如果client给server发送消息则server会感觉很奇怪既然连接不建立成功你还给我发消息那就说明我们双方产生了认为连接建立不一致的情况那server就会给client发送复位报文段请求重新三次握手重新建立连接因为我们现在连接的建立是不一致的client认为连接建立成功但server不认为成功。或者还有另一种情况server发送的SYN报文段超时没有确认应答则server就会进行超时重传当client收到重复的捎带应答报文段时client就会意识到自己给server发送的确认应答报文段可能丢失了此时client就会重发ACK报文段。 所以三次握手的最后一个报文段无论出现什么样的不可靠行为我们都不用担心TCP会有相应的策略解决不可靠问题比如超时重传双方认为连接建立不一致时认为连接不存在的一方可发送复位报文段重新建立连接。 还需要说明的一点是三次握手建立连接是站在双方各自的视角不是站在上帝的视角认为连接建立成功那就成功了。比如client只要收到了server的ACK报文段那client就会认为连接已经建立成功了也就是三次握手的第二次握手完成之后client就已经认为连接建立成功了所以只要双方自己认为连接建立成功那就算连接建立成功。 3. 所以没人说三次握手一定会成功但其实在良好的网络环境下99.99%的三次握手都会成功只能说三次握手大概率都会成功我们最担心的其实是第三次握手的报文段丢失因为他是最新的消息他是不可靠的不过TCP对于这样不可靠消息是有解决方案的所以也不用过多的担心。 另外服务器是会收到来自大量的不同的客户端的连接请求的所以服务器是需要将这些大量的连接管理好的因为我们知道传输层是在服务器主机的OS内部实现的而操作系统需要管理连接那采用的方式就一定是先描述再组织所以所谓的连接在OS中一定是需要相关的结构体来描述的然后再通过某种数据结构将大量的连接对象(连接结构体)管理起来则可以得出结论维护一个连接是有成本(时空成本)的。 4. 谈论完三次握手的详细过程之后我们来谈谈为什么一定是三次握手 首先一次握手肯定不行因为单个client主机给server发送一堆SYN报文那么server就需要维护好建立好的连接而我们知道建立连接是有成本的所以server在单主机情况下就会遭受SYN洪水攻击使服务器挂掉。 两次握手也不行服务器依旧会遭受到SYN洪水攻击client给server发送SYN报文段server那就需要维护连接同时server也会给client发送SYNACK但client可以忽略掉这个报文段直接把他丢弃掉那client就不用维护连接所以两次握手也是不可以的。因为在单个client主机攻击的情况下服务器都遭不住了更别说大量的client主机了。 那三次握手为什么可以呢 a.三次握手能够以最小成本验证全双工通信。全双工通信指的是server和client各自都能够进行数据的接收和发送发送和接收是解耦的client可以发送SYN报文段也能接收来自server的ACK报文段server可以发送SYN报文段也能接收来自client的ACK报文段话说回来四次握手可以验证全双工通信吗当然也可以但既然三次握手行为什么要四次握手呢第四次握手不是平白无故的消耗网络资源吗所以三次握手是以最小成本来验证全双工通信的。 b.三次握手可以防止产生单主机对服务器SYN洪水攻击的漏洞。其实服务器受到了攻击这本身就不应该是TCP来解决的问题这是网络安全的话题我TCP只负责进行数据传输的控制但如果因为你TCP建立连接的机制有明显的被攻击的漏洞那这就是你TCP的问题了我们知道一次握手和两次握手都存在明显的被SYN洪水攻击的漏洞那三次握手就不存在吗答案是不存在。 三次握手中server建立连接的前提是client已经建立好连接了所以client和server是同等成本的消耗想要让server建立连接首先client你自己就得先建立好连接而大部分情况下服务器的配置要比client高很多所以如果双方在不停的以相同成本进行消耗那也一定是client先扛不住而不是server所以单主机的情况下client想要SYN洪水攻击服务器这是不现实的这就有点像你朋友给你吃一个很酸或很辣的捉弄你的零食但你说必须你先吃我才会吃这就是双方进行同等成本的消耗 四次握手五次握手可以吗 前面我们说过三次握手已经可以最小成本验证全双工通信了那四次五次握手肯定也可以。但四次握手也存在SYN洪水攻击的漏洞最后一次握手是server发送给client的让client来建立连接的但client可以忽略掉这个报文段只让server自己建立连接去承担维护连接需要的成本。同时五次握手由于最后一次是client发送给server的所以server建立连接之前client也会建立连接双方是同等成本的消耗则可以避免单主机的SYN洪水攻击。所以三次握手之后的其他握手偶数次依旧存在SYN洪水攻击奇数次不存在SYN洪水攻击但他们都不是最小成本验证全双工通信信道所以使用三次握手而不是其他握手 5. 实际上如果真想搞掉服务器也很简单就是让多个肉鸡同时连接一个服务器所谓的肉鸡其实只是一种网络说法肉鸡就是客户端只要服务器接收来自大量的客户端连接那服务器就要为此承担很多成本最后的结果就是服务器过载严重无法接收来自其他正常客户端的连接请求这样的攻击称为ddos攻击即服务拒绝式攻击。其实只要肉鸡足够多什么服务器都能够打的下来最高端的攻击往往采用最朴素的方式2001年我们国家和外国的黑客进行网络攻防大战时我们国家的红客就打下过白宫的服务器采用的方式其实就是SYN洪水攻击当时由于美国对我们国家做出的种种欺压行为我国红客在5月4日青年节的同一时间段内8w台主机同时访问白宫官网并在首页挂上五星红旗以及让美国舰艇归回去的愤慨之词。 6. 现在对于SYN洪水攻击有了更多的安全机制例如黑名单白名单防火墙SYN cookie机制等等安全策略能够缓解SYN洪水攻击。 以前的12306由于使用人数过多尤其在逢年过节时服务器压力非常大服务器很容易就会挂掉直到弹性云服务器的出现才解决了12306负载严重的问题。 2.2 双方四次挥手时状态的变化理解CLOSE_WAITTIME_WAIT状态 2.2.1 四次挥手的详细过程 1. 建立连接是由一方主动发起大部分都是客户端主动向服务器发起连接请求但断开连接可以由任意一方主动发起发起的上层条件其实就是调用close()关闭套接字文件描述符sockfd当client发送完毕消息之后可以发送一个结束报文段FIN服务器发送ACK应答确认client断开连接的请求同样服务器也可以不给client发送消息了他也可以发送一个结束报文段FIN客户端发送ACK应答确认server断开连接的请求。 我们这里所说的不发送消息指的是不发应用层的数据了并不代表传输层自己不能发送该层的管理报文段例如FINACK等等报文段。 2. 有一个问题为什么TCP是有连接的呢是因为TCP要保证可靠性但为什么TCP连接能够保证可靠性呢其实连接并不能直接保证可靠性但他能够间接的保证可靠性。 当连接建立好之后内核会创建连接结构体用于管理连接而正是连接结构体的建立才能够更好的完成TCP保证可靠性的种种策略例如超时重传(结构体内部维护定时器)确认应答(结构体内部有序号和ack序号字段)流量控制(结构体内部有16位窗口大小)所以TCP连接结构体是TCP保证可靠性的数据结构基础。 3. 可以看到下面连接在四次挥手时有很多的状态FIN_WAIT_1FIN_WAIT_2TIME_WAITCLOSEDLAST_ACKCLOSE_WAIT这些状态其实就是连接结构体内部的一个属性字段就像int status;一样而这些大写英文的状态其实就是宏不同的宏代表不同的连接状态。这么多状态最重要的就是CLOSE_WAIT和TIME_WAIT状态要好好理解。 如果客户端先调用close(sockfd)主动断开连接向服务器发送FIN报文段则客户端进入FIN_WAIT_1状态服务器收到报文段之后会返回一个ACK报文段同时服务器进入CLOSE_WAIT状态服务器调用close(sockfd)断开连接时向客户端发送FIN报文段同时服务器进入LAST_ACK状态客户端收到FIN报文段之后向服务器返回一个ACK报文段同时客户端进入TIME_WAIT状态当服务器收到ACK报文段之后服务器进入CLOSED状态客户端进入TIME_WAIT状态时需要等待2MSL(Max Segment Life报文最大生存时间)的时间才会进入CLOSED状态。 4. 所以主动断开连接的一方最终状态是TIME_WAIT状态被动断开连接的一方完成最初的两次挥手后状态变为CLOSE_WAIT状态。 这个实验很好做我们只需要让建立好连接的client率先终止进程断开连接则通过netstat便可以查看出服务器连接的状态server的TCP连接处于CLOSE_WAIT状态client的TCP连接处于FIN_WAIT2状态由于客户端和服务器是在一台云服务器上作测试的所以查看网络状态时我们分别能够看到client和server的连接状态。 如果我们的服务器出现了大量的CLOSE_WAIT状态一般有两种情况一种是服务器存在bug服务器代码中没有close(sockfd)这会导致服务器无法完成最后的两次挥手。另一种情况是服务器现在压力可能比较大比如忙着向client推送消息导致来不及执行close(sockfd)不过这种情况只是暂时的等服务器缓过来之后是能够完成后两次挥手的。 如果要测试CLOSE_WAIT状态则可以把HandlerHttp(sockfd);下面的close代码屏蔽掉这样当客户端断开连接时服务器并不会调用close(sockfd)也就是不会完成后两次挥手则服务器状态会一直持续CLOSE_WAIT状态。 如果要测试TIME_WAIT状态就是下面第二张图片那样一个正常的服务器会在客户端退出之后自己也会退出调用close(sockfd)完成后两次挥手使得连接完全断开。服务器自己退出时其实就是read读到0代表client已经退出了服务器此时也应该退出。 2.2.2 测试TIME_WAIT状态 下面是测试TIME_WAIT状态的过程可以看到服务器代码中建立好一个连接之后会和客户端进行一次通信也就是执行HandlerHttp方法但只要服务器执行完这个方法服务器就会执行close(sockfd)所以只要让服务器是被动断开连接的一方并且四次挥手全部完成服务器最终状态就会是TIME_WAIT状态在动图中可以看到客户端是主动断开连接的一方服务器被动断开连接。 2.2.3 主动断开连接的一方为什么要维持2MSL时间的TIME_WAIT状态 为什么四次挥手结束之后主动断开连接的一方要维持一段时间的TIME_WAIT状态呢而且这个时间是2MSL(maximum segment life) 理由1保证主动断开连接的一方最后一次挥手发送的ACK报文尽可能的让对方收到因为主动断开连接的一方还可能会补发最后一个ACK报文。书上的概念可靠的终止TCP连接 MSL指的是一个报文段从左到右或从右到左的最大生存时间注意是最大生存时间这样的最大生产时间即使网络情况很差在一个MSL内报文段都可以到达对方主机了如果网络情况较好都不需要一个MSL时间就可以到达对方主机。 如果最后一个ACK报文丢失则服务器首先会没有收到ACK报文然后服务器会重发FIN报文段客户端再次收到FIN报文段时客户端就会意识到最后一个ACK报文段可能丢失了则客户端会补发最后一个ACK报文段而服务器判断ACK报文段丢失自己重发FIN报文段这两个时间最大就是2MSL所以持续2MSL能够有效的保证最后一个ACK报文即使丢失了主动断开连接的一方依旧能够有能力再次补发最后一个ACK报文段。 理由2双方在断开连接的时候网络中可能还存在滞留的报文保证滞留的报文消散。书上的概念保证让迟来的TCP报文段有足够的时间被识别并丢弃 有可能有一种情况是当服务器发送FIN报文段后有一个携带信息的报文段(比如6号报文段)晚于FIN报文段发送给客户端如果客户端处于TIME_WAIT状态则可以有足够的时间来识别6号报文段并把他丢弃掉也就是让网络中可能存在的滞留的报文彻底消散。 而持续2MSL的时间可以让两个传输方向上尚未被接收到的迟来的报文段都能够消散例如左边给右边发送一个迟来的报文段右边收到后同样会返回一个迟来的报文段而这一来一回的时间最多就是2MSL MSL的时间大小可以在下面的路径中查出来默认为60s 2.2.4 解决服务器立即重启无法bind原来端口号的问题 1. 以前在socket套接字编程的时候我们会遇到服务器有时立即重启无法bind原来的端口号但有时却又可以bind原来的端口号其实就是因为TIME_WAIT状态。 如果你先关闭客户端后关闭服务器则服务器最终状态是CLOSED状态此时你立即重启服务器bind原来的端口号就不会出现bind error的问题因为端口号并没有被占用着。 如果你先关闭服务器则服务器的最终状态是TIME_WAIT状态此时你立即重启服务器bind原来的端口号这一定是bind不成功的因为原来的服务器进程还占用着8080端口号(拿8080举例)你现在重启服务器又bind8080号端口当然就会报错bind error了。因为一个端口号只能被一个进程绑定一个进程是可以bind多个端口号的因为一个进程可以打开多个文件描述符sockfd。 2. 服务器立即重启无法bind原来端口号是一个很严重的问题比如京东618期间服务器挂满了大量的连接如果由于连接数的不断增多服务器不小心挂掉了那服务器是需要立马重启的如果此时服务器无法bind原来的端口号而导致被迫等待2MSL的时间也就是120s那所有的用户此时就无法进行购物京东无法提供服务618期间1s就是上百万的营业额要是等120s公司得亏损多少啊所以服务器必须能够立即重启且能够bind原来的端口号大型公司的服务器他们bind的基本都是知名端口号在公司内部一旦一个服务器bind了一个端口号轻易是不会换端口号的。 3. 解决的方式也并不困难只需要设置sockfd选项为重用本地地址SO_REUSEADDR即使服务器(主动断开连接)的sockfd对应的连接结构体处于TIME_WAIT状态与sockfd绑定的socket地址(struct sockaddr_in local)也可以立即被重用这样就可以实现服务器立即重启依旧能bind原来的端口号了。 4. 除了设置SO_REUSEADDR选项外还可以修改内核参数/proc/sys/net/ipv4/tcp_tw_recycle来快速回收被关闭的sockfd回收其相关的所有资源例如连接结构体socket地址等等从而使得主动关闭TCP连接的一方根本就不进入TIME_WAIT状态进而允许服务器进程能够立即重用本地socket地址。摘自《Linux高性能服务器编程》 3.TCP的高效性 3.1 滑动窗口批量化发送数据段支持超时重传机制 1. 之前我们在谈论确认应答的时候对每一个发送的数据段都要有一个ACK确认应答收到ACK之后再发送下一个数据段这样的工作效率是非常低的实际当中是批量化的发送一批报文确认应答时可能只会返回一个ACK报文段而实现批量化发送其实就是依靠接收缓冲区中的滑动窗口批量化的数据段可以在一个时间点上直接发送大大提高了TCP的传输效率。 除提高效率外滑动窗口其实还支持了超时重传机制我们知道数据段在发送的时候是可能存在丢包问题的而解决丢包就需要重传数据段所以未收到ACK的数据段不应该立马被移除而应该先暂存一段时间而这样未收到ACK但已发送的报文段其实就是在滑动窗口中暂存着的。 2. 其实我们可以在逻辑上将发送缓冲区分为4个部分从左到右依次为已经发送同时被ACK的数据(这部分数据可以被新数据覆盖)已经发送但没有被ACK的数据(这部分数据不能被新数据覆盖)尚未被发送的数据(刚刚从应用层缓冲区中拷贝下来的数据)剩余的没有数据的空间(其实开辟空间时有初始化的数据) 所以随着滑动窗口的右移右边的数据就会被逐渐发送左边的数据会被应答如果需要重传数据段则重传的就是滑动窗口中的数据段。 3. 我们可以将缓冲区看作一个大的buffer而实际上所谓的滑动窗口其实就是buffer中win_start和win_end两个数组下标之间构成的空间滑动窗口移动其实就是win_start和win_end当滑动窗口内的数据被ACK确认应答了滑动窗口就会右移。 但我们对滑动窗口的理解就止步于此了吗当然不是这仅仅只是一个开始而已 4. 接下来我们要提出许多问题通过解答这些问题来加深对滑动窗口的理解。 (1)滑动窗口的大小是怎么设定的未来大小又是怎么变化的 滑动窗口的大小要始终和对方的接收能力挂钩因为滑动窗口的大小一次批量化发送数据段的多少我们知道TCP有流量控制而一次批量化发送数据段的多少其实是由对方的16位窗口大小和网络的拥塞情况(下面的拥塞控制就会讲到现在先提一嘴)共同决定的所以滑动窗口的大小min(16位窗口大小拥塞窗口大小)如果随着网络情况变好同时对方接收能力也提升上来那滑动窗口自然就会变大而无论是网络情况还是对方接收能力只要有一个下降滑动窗口自然就会变小因为滑动窗口大小是取两者的最小值。 初始时win_start0win_endwin_starttcp_win(对方的接收能力)这么认为其实是不对的因为还要考虑网络的拥塞情况不过暂时这样理解是可以的大部分情况下网络都是良好的 (2)窗口一定会向右滑动吗能不能向左滑动呢 滑动窗口不一定向右滑动有可能保持不动比如发送端发送的报文都丢包了或者发送报文的ACK丢包了这两种情况滑动窗口都会保持不动。当然一般正常情况下滑动窗口都会右移的比如最左侧报文段收到ACK那滑动窗口就是会右移的。 但滑动窗口一定不会向左滑动左边的数据都是已经被ACK的而滑动窗口内的数据是未被ACK的向左滑动是不合理的 (3)滑动窗口大小会保持不变吗会变大吗会变小吗变化的依据又是什么 滑动窗口大小会保持不变比如上面我们说的两种丢包的情况滑动窗口的大小和位置都会保持不变。 滑动窗口是会变大的比如对方应用层将socket缓冲区内的数据全部拿走缓冲区的剩余空间一下子增多同时网络情况也一直很良好那么滑动窗口就可以增大发送数据时就可以一次批量化的发送更多的数据了。 滑动窗口也是会变小的比如对方的应用层就是不拿走传输层的数据则随着对方接收缓冲区不断接收数据段则对方的接收能力就会下降而此时滑动窗口也会跟着下降。 (4)收到ACK的报文如果不是窗口左侧的报文而是中间的或者是右侧的该怎么办窗口还要滑动吗 收到ACK的报文是中间的(右侧的也一样)一般有两种情况一种是左侧报文段丢失了另一种是左侧报文段没丢失但对应的ACK报文段丢失了。 对于第一种情况在滑动窗口中假设丢失报文段的序号是1000发送成功的报文段的序号是2000所以丢失的其实就是1000序号-1999序号这1000字节的数据而后面发送的报文段都得到了ACK但值得注意的是这些ACK报文段的确认序号是什么呢我们之前学习确认应答机制的时候知道确认序号表示的是ack序号之前的所有数据都已经收到了所以这些返回的ACK报文段的确认序号就全部是1000此时发送端就知道1000号报文段在传输过程中丢包了那就会触发超时重传机制。 对于第二种情况如果仅仅只是ACK报文段丢失了那后面发送成功的报文段对应返回的ACK报文段的确认序号就会是正常的而这种情况并不会产生任何问题滑动窗口正常右移即可。 (5)滑动窗口会变为0吗 会的如果对方接收能力为0则滑动窗口也会为0比如对方缓冲区被打满其上层还不取走缓冲区中的数据则此时接收能力也就是16位窗口大小的值就会为0 (6)滑动窗口一直向右滑动吗如果剩余空间不够了该怎么办 实际上发送缓冲区被内核维护成了一个环形结构所以滑动窗口确实会一直向右滑动而所谓的环形结构其实就是通过模运算来实现的当滑动窗口的位置发生变化时win_start和win_end会随之增大或不变在变化之后可以让win_start和win_end%缓冲区的大小防止下标越界这样其实就维护好一个环形队列了。 3.2 拥塞控制 3.2.1 拥塞控制和超时重传机制大面积的丢包拥塞控制小面积的丢包超时重传 1. 之前我们谈论的所有TCP策略和机制其实都是在谈通信两端没有谈论中间网络数据传输的环节丢包除了因为双方的问题还有可能因为中间环节网络出现了问题而由于网络异常或压力过大导致的丢包需要TCP进行拥塞控制。所以滑动窗口的大小不仅需要考虑对方的接收能力大小还要考虑中间环节网络的情况如何。 TCP引入了许多的机制来保证网络数据传输的可靠性例如流量控制超时重传确认应答连接管理同时也引入了滑动窗口拥塞控制等机制来保证网络数据传输的高效性只不过TCP的可靠性过于耀眼导致很多人忽略了TCP的高效性但实际上TCP也是非常高效的。 所以你敢说UDP一定比TCP更高效吗虽然网上有很多人这么说但我不敢这么说。 2. 如果clien给server发送一批数据段只有几个数据段丢失了那client并不会觉得怎么样直接超时重传即可但如果丢失了非常多的数据段则client会认为此时是网络出现问题了因为在流量控制机制的管理下发送的一批数据段一定是符合对方接收能力的此时如果出现大面积的丢包则一定是网络环境出现了问题而如果网络出现了问题就需要TCP的拥塞控制来缓解网络压力。 所以TCP的可靠性不仅仅考虑了通信双方可能出现的问题同时还考虑了中间环节网络可能出现的问题。 3. 如果此时发生了大面积的网络丢包那TCP还能采取超时重传的策略吗 需要知道的是网络中通信的可不止你和服务器这两台主机还要其他主机也在通信如果TCP采取超时重传策略那所有的主机都会采取超时重传策略本来网络的压力就已经够大了结果所有的主机还在不停的向网络中疯狂的塞报文那造成的结果就是又一次的大面积丢包因为此时网络环境已经出问题了比如带宽太窄网络数据拥塞。所以重传只会加剧网络故障问题。 正确的做法应该是让所有的主机都遵守停下来的机制网络是有自我恢复的能力的只要所有主机都暂时不发送报文或者仅仅只发送少量的报文则等待网络恢复之后再继续通信这才是正解。而当网络中出现大面积丢包所有主机停下来(只是形象化的说法实际是让主机只发送少量的探测报文)等待网络恢复的机制其实就是拥塞控制。 3.2.2 拥塞窗口的引入MSS和SMSS 1. 当网络出现拥塞时发送端会发送拥塞窗口大小的探测报文段用于探测网络状况如何。 而拥塞窗口大小的单位其实就是SMSS(Segment Maximum Segment Life 分段最大段大小)SMSS是在一个TCP连接中发送端可以发送的最大数据段的大小。SMSS的值是根据网络条件和对方接受能力共同考虑得出的估计值SMSS用于优化数据传输和拥塞控制即使在非常差的网络条件下SMSS也大概率能够发送到对端主机。 SMSS并不是在TCP三次握手阶段通过选项字段来协商决定的而是在建立连接之后实际进行通信时发送方根据网络和接收方的反馈动态调整的所以拥塞窗口的单位大小就是SMSS例如发生网络拥塞时主机会按照2的指数幂的大小发送具体个数的SMSS报文。 2. MSS(Maximum Segment Size)是TCP三次握手阶段通过kind2最大报文段长度选项来协商的MSS指的是TCP模块发送报文时最大支持的报文段长度通常这个值是MTU-40因为MTU(Maximum Transmission Unit)是数据链路层最大的传输单元减去的40字节包括TCP头部20字节和IP头部20字节假设TCP和IP头部都不包含选项字段并且这也是一般情况。 所以控制TCP最大报文段为MTU-40可以有效避免IP层分片的情况因为IP分片与组装会平白无故增加很多的网络消耗如果我们能够避免分片那我们就应该尽量这么去做。 3. 总结一下MSS是在三次握手阶段协商出来的TCP层最大报文段大小如果在TCP头部选项设置了MSS的大小则在实际通信时无论你发送什么报文段都不能超过这个大小。 而SMSS是在三次握手成功之后实际在通信时发送方根据网络和对方接收能力反馈动态调整的报文段大小其实就是拥塞窗口的单位大小。 3.2.3 慢启动算法拥塞避免算法阈值前指数增长阈值后线性增长 1. 慢启动是拥塞控制的一个非常好的策略当网路发生拥塞时TCP会进行慢启动以2的指数幂递增的方式来发送SMSS大小的报文段我们知道指数增长其实是爆炸式增长前期可能增长的比较慢但只要增长起来后面增长的就会越来越快。 而这正好适合拥塞控制前期网络状况不好正好可以慢启动发送少量的SMSS大小的报文段正好可以让网络状况快速的恢复后期网络状况恢复好了同时拥塞窗口也增长到很大了正好可以支持大批量的数据段传输让网络数据传输的效率快速提升上来。 2. 拥塞窗口实际上是有一个动态调整的阈值ssthreshslow start threshold当拥塞窗口前期以2的指数幂方式不断增大时一旦超过了这个阈值则拥塞窗口会线性增长(每次1)当网络再次出现拥塞时拥塞窗口重新被置为1同时阈值重新调整为发生网络拥塞时拥塞窗口大小值的一半阈值的初始值为窗口最大值65535byte上图中拥塞窗口以SMSS为单位来显示CWND但实际上他是以字节为单位的同时横坐标以RTT(Round-Trip Time 往返时延)为单位图中所画的初始ssthresh大小为16SMSS但实际是65535byte。 3. 拥塞窗口既保证了可靠性发生大量丢包时进行慢启动等待网络状况恢复同时也保证了高效性拥塞窗口不断变化这也能同时调整滑动窗口的大小保证数据传输的高效性即照顾了网络状况同时又能够使数据传输的效率提升上来。 3.3 延迟应答 捎带应答 1. 延迟应答是提高数据传输效率的一种手段如果接收方立马返回ACK应答返回的窗口可能比较小但如果延迟一段时间在这段时间里可能接收方的上层拿走了缓冲区中的数据此时在返回ACK应答ACK应答里面的窗口大小就会比较大了发送方下次发送的数据量就可以更多了这就可以提高数据传输的效率。 值得注意的是并不是只要延迟了上层就一定会在这段时间内拿走缓冲区中的数据这是概率性事件只是说大概率上层会拿走如果拿走那恰巧就可以提高效率没拿走那也就只能没拿走呗这个世界上没有绝对的事情任何事情都是概率性的只不过分为大概率和小概率延迟应答也一样只不过他是较大概率的事件。 2. 一定要记得窗口越大网络吞吐量就越高传输效率也就会越高(一次传输的数据更多了嘛)TCP提高效率的机制就是保证在网络不拥塞的前提下尽可能提升传输效率。 所有的包都可以延迟应答吗其实是有限制的。 数量限制每隔N个包就应答一次一般N取2(不同version的OS可能会有差异) 时间限制超过最大延迟时间就应答一次一般最大延迟时间取200ms(不同version的OS可能会有差异) 3. 捎带应答也是TCP提高数据传输效率的一种手段这个我们很早之前就见过了比如三次握手的第二次握手阶段server也想和client建立连接则server会向client发送一个SYN报文段而这个SYN报文段就可以捎带上ACK应答上一个client给server发送的SYN报文段所以捎带应答很简单只要将这个报文段的ACK和SYN标志位都置为有效即可。 4.TCP相关问题和相关实验 4.1 TCP异常情况 1. 如果client或server进程挂掉了那之前TCP建立的连接怎么办如果是电脑关机呢 进程终止OS会释放进程相关的所有资源包括套接字文件描述符释放sockfd仍然可以发送FIN报文段触发四次挥手所以和正常关闭没有什么区别。前面我们测试TIME_WAIT状态的时候其实就看到过这个现象了客户端进程终止连接其实是正常断开的所以进程终止和直接调用close(sockfd)从作用效果上来讲没什么区别。 电脑关机同样也是如此我们平常在电脑关机时如果有未关闭的进程电脑应该是有提示的会告诉你现在有部分应用未关闭是否要选择强制关机如果我们强制关机其实就是操作系统手动杀死掉了正在运行的进程作用效果其实和进程终止一样。 拔电源或拔网线曾经的连接怎么办呢 拔电源或拔网线物理隔绝其实是最致命的连网线都没有了你怎么发送FIN报文段呢不过也不用担心客户端由于拔电源或网线异常后但服务器还认为连接是正常的而服务器会有自己对于连接的保活策略会定期发送探测报文段询问客户端是否在线当发送累计到一定次数时服务器会自动断开连接。 4.2 用UDP实现可靠性传输 1. 其实用UDP实现可靠性传输是有对应的方案的因为摆在我们面前的TCP就是这个世界上最优秀的可靠性传输协议而UDP也想要实现其实就是在应用层仿照内核层TCP的机制来实现。 例如在应用层引入序列号保证数据的有序性。引入确认应答确保对端收到数据。引入超时重传发生丢包时能够补发数据包。 4.3 理解listen的第二个参数backlog全连接长度backlog1 1. 以前我们在写tcp的socket编程时监听连接到来的接口listen的第二个参数backlog当时直接无脑设置为5的大小但其实他是有原因的实际表示的是内核监听队列的最大长度。 2. 平常生活中我们在吃海底捞的时候总会见到海底捞外面排着一些等待的队伍为什么海底捞要有这种排队等待的机制呢其实就是为了提高资源的利用率从而达到赚钱的目的。海底捞中就餐的位置是有限的而常常出现的情况就是座位全都满了外面会有一批人在等待着其实就是为了当某些座位出现空闲的时候这些空闲的座位又能够立马重新投入使用保证座位大部分时间都有人在使用那么海底捞餐厅的营业额就会增长的更多所以排队的本质其实就是让我们的资源在空闲的时候能够立马让资源投入使用以提高资源的利用率而这个资源对于海底捞来说其实就是就餐的座位对于计算机来说其实就是时间和内存空间。那这个排队的队列的长度该怎么定呢如果海底捞外面排了50个人那我今天还会吃吗可能还没到吃呢我就交代在这儿了如果队伍排短一些我或许还会排队试试看如果非常长我一定不会排的。 而在内核中OS其实会为TCP维护一个内核监听队列该队列里面的连接都是全连接表示他们已经完成三次握手只要accept把这些连接拿上去这些连接就立马能够投入使用进行TCP通信有可能服务器进程忙着做别的事情没有调用accept接口但此时服务器已经监听到许多TCP的连接请求了此时服务器会将这些连接放到内核等待队列中当服务器执行accept代码时服务器会从内核等待队列中拿走一个全连接然后用accept返回的用于TCP通信的sockfd来进行后续的数据传输。 3. 所以accept是不参与TCP三次握手的accept只负责从内核等待队列中将全连接拿上来然后开始后续的网络通信工作。真正的TCP三次握手是由connect和listen系统调用发起和完成的。 connect调用时客户端会向服务器发送一个SYN报文段同时客户端进入SYN_SENT状态而服务器早由于调用listen从而进入了LISTEN状态当服务器收到SYN报文段时服务器会返回一个SYNACK的报文段然后服务器进入SYN_RCVD状态当客户端收到ACK的时候客户端就已经认为连接建立成功了从而进入ESTABLISHED状态客户端看到SYN报文段时也会向服务器返回一个ACK报文段服务器收到后也就会进入ESTABLISHED状态整个三次握手的过程中是没有accept参与的。 4. 我把backlog设置为2同时服务器初始化好之后在启动时不会调用accept把任何全连接从内核等待队列中拿出来这样所有和服务器完成三次握手建立成功的连接就会全部呆在内核等待队列里面。 从下面代码我们也能看出如果你想实现一个多线程的服务器只要保证一个线程始终能够从全连接队列中拿取连接然后由其他线程来为拿取上来的连接提供通信服务这就是一个多线程的服务器。至于listen接口早在服务器初始化时候就调用过了服务器启动时无须管listen接口了就只需要调用accept即可。 从实验结果可以看出当出现4个客户端向服务器连接时第四个客户端的连接情况有些许不同服务器不认为连接建立成功状态为SYN_RECV而客户端认为连接是建立成功的状态为ESTABLISHED但在一段时间过后服务器会把连接关闭所以netstat就无法查到了。第四个客户端的连接请求到来时服务器不会受理客户端发来的最后一次握手的ACK报文段不会让三次握手建立成功仅仅只是半连接状态。 5. 从实验结果可以看出TCP的内核等待队列最多只能允许backlog1个全连接后续来的连接只能是半连接如果不能尽快完成握手server会自动关闭掉半连接。 值得注意的是全连接队列的长度和server能够建立多少个连接是没有关系的只是说当server没有调用accept拿取连接时如果连接到来了那就暂时把连接先放到等待队列中而这个等待队列最多只能容纳backlog1个连接等server执行到accept接口的时候再把连接从队列中拿上来。 6. Linux内核协议栈其实为管理一个TCP连接使用了两个队列一个是半连接队列一个是全连接队列当全连接队列满了的时候服务器无法再继续受理新到来的连接只会维持一小段时间的半连接。
http://www.pierceye.com/news/807152/

相关文章:

  • 临沂建设局网站官网文明网站建设工作进度表
  • 网站编辑seo旅游网站建设代码
  • 为什么自己做的网站打开是乱码wordpress live-2d
  • 素材下载网站电商自建站
  • 浙江省的网站建设公司有哪些代理注册公司一般多少钱
  • 如何在建设银行网站预约纪念币东莞网站建设服务有什
  • 有哪些可以做h5的网站代理网址上境外网
  • 做网站所需要的代码6红杏直播
  • 南通制作网站wordpress移动版设置
  • 哪个网站有免费ppt下载建筑类网站的推荐理由
  • 视觉差的网站公司外包
  • 基础做网站内蒙住房和城乡建设部网站
  • 发帖效果好的网站展馆展示设计公司排名
  • 童装网站建设文案什么网站做的号
  • 能打开的a站莆田网站建设建站系统
  • 上海市城乡建设管理局网站一个月做网站
  • 网站后台管理系统 aspwordpress拖拽上传
  • 华为手机官方网站登录爬虫做视频网站
  • 山东省工程建设信息官方网站河南网站seo推广
  • 低成本做网站 白之家重庆市建设执业资格注册管理中心网站
  • 电子商务网站建设需求在别的公司做的网站可以转走吗
  • 网站流量怎么做乡1万做网站需要几个人
  • 阿里云centos7做网站怀化网站seo
  • 我做的网站怎样被百度收录易语言 做网站mysql
  • 花店网站模板免费下载9个做简历的网站
  • 东港区网站制作seo推广模式是什么
  • 用织梦做网站能练技术吗广州专业网络推广公司
  • 下载ppt模板免费的网站在线做头像网站
  • 网络推广怎么免费做网站内部优化的方法
  • 沧州wap网站制作哈尔滨建设网证件查询