文章目录一. TCP基础原理1. TCP头的格式 和 segment1.1. TCP头的格式1.2. segment2. TCP的状态机与连接与关闭连接3. 三次握手与四次挥手3.1. 建立连接三次握手3.2. 关闭连接四次挥手3.3. TCP四次挥手过程中为什么需要等待2MSL,才进入CLOSED关闭状态二. TCP连接相关问题1. 建立连接时SYN超时2. SYN Flood攻击与半连接队列ing3. 关闭连接时TIME_WAIT状态过多的危害3.1. 会出现的问题3.2. 优化time_wait过多的方式a. 调整系统内核参数b. 使用nginx调整短链接为长链接从client到nginx的长连接从nginx和server的长连接三. 数据传输相关1. TCP的粘包和拆包2. 可靠传输及乱序问题-TCP滑动窗口3. TCP的拥塞处理4. 所以TCP是如何确保数据的可靠性一. TCP基础原理1. TCP头的格式 和 segment1.1. TCP头的格式TCP的包是没有IP地址的那是IP层上的事。但是有源端口和目标端口。一个TCP连接需要四个元组来表示是同一个连接src_ip, src_port, dst_ip, dst_port准确说是五元组还有一个是协议。头中四个重要的参数参数说明Sequence Number包的序号用来解决网络包乱序reordering问题。Acknowledgement NumberACK——用于确认收到用来解决不丢包的问题。Window又叫Advertised-Window也就是著名的滑动窗口Sliding Window用于解决流控的TCP Flag包的类型主要是用于操控TCP的状态机的1.2. segmentTCP网络协议中第二层上的数据我们叫Frame在第三层上的数据叫Packet第四层的数据叫Segment。发送数据时我们程序的数据首先会打到TCP的Segment中然后TCP的Segment会打到IP的Packet中然后再打到以太网Ethernet的Frame中传到对端后各个层解析自己的协议然后把数据交给更高层的协议处理。2. TCP的状态机与连接与关闭连接先来一张TCP状态机的状态转换图表示了client和server端状态的转换流程。图中的11种状态状态解释listenserver等待client端发来的连接请求syn_sentclient发送一个syn报文代表应用程序执行了主动打开的操作并等待对端回应以此完成连接的建立syn_recvserver端收到了client的syn报文并发送了SYN/ACK报文这个报文同时设置了SYN和ACK位等待client发送ACK以此完成建立进入establishedfin_wait1应用程序关闭了连接。client发送一个fin报文用来中断连接并等待对端发来的ACK。fin_wait2fin_wait1的client端已经收到了server发来的ACK半关闭closing之前处在FIN_WAIT1状态的client正在等待对端发送ACK但却收到了FIN。这表示server端也正在尝试执行一个主动关闭。换句话说这两个TCP节点几乎在同一时刻发送了FIN报文。即同时关闭time_waitclient完成主动关闭后收到了fin报文server端执行了一个被动关闭。此时client将在time_wait阶段中等待一段时间为了确保tcp连接可靠的终止同时确保任何老的报文在重新建立连接之前超时当固定段超时后连接就关闭了相关内核资源得到释放。close_waitserver端收到处于fin_wait1的client发送fin报文后将状态设置为close_wait状态。last_ack应用程序被动关闭处于close_wait状态的server端发送一个fin报文给client并等待确认。收到ack报文时连接关闭相关内核资源释放closed关闭状态3. 三次握手与四次挥手先了解几个概念双工的概念TCP是全双工的全双工client在给server发送信息的同时server也可以client发送信息半双工server可以给client发送信息或相反但是client和server不能同时发。发送syn表示和对方建立一个连接ack表示收到对方的syn后做出回应是两个操作。3.1. 建立连接三次握手对于三次握手建立连接有一个主要动作是初始化Sequence Number的初始值client和server要互相通知自己的SN初始值是多少此过程称为 SYNSynchronize Sequence Numbers。 以便之后数据传递过程中不出现乱序的问题。第一次建立连接。client发送SYN报文其中位置设置为1SN设置为J并进入syn_send状态等待服务器确认第二次server收到SYN报文后ack进行报文确认设置Acknowledgment Number为J1syn同时发送自己的SYN报文位置设置为1SN设置为kserver置为syn_recv。第三次client收到SYNACK报文段后设置ANk1并发送SYN之后client和server都进入establish状态。3.2. 关闭连接四次挥手server端或client端都可以是发起者第一次挥手client可以是client或server发送fin设置sequence Number 和 Acknowledgment Number报文段给serverclient进入fin_wait1状态表示client没有数据要发送给server端了。第二次挥手sever收到了client的fin报文后回复client一个ackANSN1报文client收到后状态为fin_wait2。此时告诉clientserver接收到了它的请求且server要做一些关闭自己前的清理工作。第三次挥手server向client发送fin清理工作已经做完请求关闭连接报文并将自己设置为close_wait.第四次挥手client收到server的fin报文向server发送ack报文然后client进入time_wait状态。server收到ack就关闭连接此时client等待2MEL后依然没有收到回复则说明server已正常关闭此时client也关闭连接。3.3. TCP四次挥手过程中为什么需要等待2MSL,才进入CLOSED关闭状态2MSLtwo Maximum Segment Lifetime即两个最大段生命周期。假设主动发起挥手的是client那么需要2MSL的原因是为了保证client发送的ack报文server端能收到假设ack报文段丢失server端就收不到报文。server端因报文超时重传finack二、三次挥手给client确认此时client就能在2MSL超时 1MSL收到重传的finack报文段然后发送ack给server重新开启2MSL计时。防止已失效的连接请求报文段出现在本连接中client发完最后一个ack报文段后再经过2MSL就可以使本连接持续的时间内所产生的所有报文段都消失这样就可以使下一个连接中不会出现这种旧的连接报文段二. TCP连接相关问题1. 建立连接时SYN超时假设server端收到client发送的syn并回复了ack后client掉线了server没有再接收到client的ack此时连接就处于一个中间状态。于是server再一定时间内没有收到ack时就重发syn-ack。linux下默认重试为5次分别为1、2、4、8、16共31秒第五次后再等待超过32s后client和server都知道第五次也失效了还没有接收到client的ack就会断开这个连接。共63秒。2. SYN Flood攻击与半连接队列ingSYN Flood攻击——给服务器发了一个SYN后就下线了于是服务器需要默认等63s才会断开连接这样攻击者就可以把服务器的syn连接的队列耗尽让正常的连接请求不能处理。linux下给了一个叫tcp_syncookies的参数来应对当SYN队列满了后TCP会通过源地址端口、目标地址端口和时间戳创建出一个特别的Sequence Number发回去又叫cookie如果是攻击者则不会有响应如果是正常连接则会把这个 SYN Cookie发回来然后服务端可以通过cookie建连接即使你不在SYN队列中。3. 关闭连接时TIME_WAIT状态过多的危害3.1. 会出现的问题time_wait 状态结束前该socket所占用的端口将一直无法释放。在高并发没秒几万qps且采用短连接方式的交互系统在运行一段时间后系统中就会出现大量的time_wait状态当time_wait将系统中可用的端口都占用完了就出现无法向server端创建新的socket连接的情况此时系统几乎停转任何连接都不能建立。大量的time_wait也会占用一定的fd、内存和cpu资源当然这个量比较小不是主要危害。3.2. 优化time_wait过多的方式a. 调整系统内核参数修改/etc/sysctl.conf文件一般涉及下面的几个参数net.ipv4.tcp_syncookies1表示开启SYN Cookies。当出现SYN等待队列溢出时启用cookies来处理可防范少量SYN攻击默认为0表示关闭 net.ipv4.tcp_tw_reuse1表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接默认为0表示关闭 net.ipv4.tcp_tw_recycle1表示开启TCP连接中TIME-WAIT sockets的快速回收默认为0表示关闭。 net.ipv4.tcp_fin_timeout修改系统默认的 TIMEOUT 时间 net.ipv4.tcp_max_tw_buckets5000表示系统同时保持TIME_WAIT套接字的最大数量(默认是18000).当TIME_WAIT连接数量达到给定的值时所有的TIME_WAIT连接会被立刻清除并打印警告信息。但这种粗暴的清理掉所有的连接意味着有些连接并没有成功等待2MSL就会造成通讯异常。一般不建议调整 其他优化 net.ipv4.ip_local_port_range102465535增加可用端口范围让系统拥有的更多的端口来建立链接这里有个问题需要注意对于这个设置系统就会从1025~65535这个范围内随机分配端口来用于连接如果我们服务的使用端口比如8080刚好在这个范围之内在升级服务期间可能会出现8080端口被其他随机分配的链接给占用掉这个原因也是文章开头提到的端口被占用的另一个原因 net.ipv4.ip_local_reserved_ports7005,8001-8100针对上面的问题我们可以设置这个参数来告诉系统给我们预留哪些端口不可以用于自动分配。b. 使用nginx调整短链接为长链接简介一下短连接和长连接短连接连接-传输数据-关闭连接HTTP是无状态的浏览器和服务器每进行一次HTTP操作就建立一次连接但任务结束就中断连接。也可以这样说短连接是指SOCKET连接后发送后接收完数据后马上断开连接。长连接连接-传输数据-保持连接 - 传输数据- 。。。-关闭连接。长连接指建立SOCKET连接后不管是否使用都保持连接但安全性较差。长连接比短连接从根本上减少了关闭连接的次数减少了TIME_WAIT状态的产生数量在高并发的系统中这种方式的改动非常有效果可以明显减少系统TIME_WAIT的数量。从client到nginx的长连接默认情况下nginx已经自动开启了对client连接的keep alive支持。一般场景可以直接使用但是对于一些比较特殊的场景还是有必要调整个别参数keepalive_timeout和keepalive_requests。http{keepalive_timeout120s120s;keepalive_requests10000;}keepalive_timeout 第一个参数设置keep-alive客户端连接在服务器端保持开启的超时值默认75s 第二个参数可选、在响应的header域中设置一个值“Keep-Alive:timeouttime”通常可以不用设置 keepalive_requests 一个keep alive建立之后nginx就会为这个连接设置一个计数器记录这个keep alive的长连接上已经接收并处理的客户端#请求的数量#。 如果达到这个参数设置的最大值时则nginx会强行关闭这个长连接逼迫客户端不得不重新建立新的长连接。 默认值100凑合够用 QPS10000时客户端每秒发送10000个请求(通常建立有多个长连接)每个连接只能最多跑100次请求意味着平均每秒钟就会有100个长连接因此被nginx关闭。 同样意味着为了保持QPS客户端不得不每秒中重新新建100个连接。 因此就会发现有大量的TIME_WAIT的socket连接(即使此时keep alive已经在client和nginx之间生效)。 因此对于QPS较高的场景非常有必要加大这个参数以避免出现大量连接被生成再抛弃的情况减少TIME_WAIT。从nginx和server的长连接upstream http_backend{server127.0.0.1:8080;keepalive1000;//设置nginx到upstream服务器的空闲keepalive连接的最大数量}server{...location/http/{proxy_pass http://http_backend;proxy_http_version1.1;//开启长链接proxy_set_header Connection;...}}proxy_set_header Connection;清理从client过来的http header因为即使是client和nginx之间是短连接nginx和upstream之间也是可以开启长连接的。这种情况下必须清理来自client请求中的Connectionheader。 但这个地方需要注意如果有一些认证鉴权的cookie或者session信息在head里面 不建议开启此选项或者对需要保留的header要保存下来否则这些信息可能会丢掉从而发不到上游upstream的服务器上。三. 数据传输相关1. TCP的粘包和拆包TCP中的negal算法通信两端有很多小的数据包要发送虽然传送的数据很少但是流程一点没少也需要tcp的各种确认校验这样小的数据包如果很多会造成网络资源很大的浪费。negal算法做了这样一件事当来了一个很小的数据包我不急于发送这个包而是等来了更多的包将这些小包组合成大包之后一并发送提高了网络传输的效率。TCP是面向流的client到server端数据传输过程中因为TCP面向流数据没有边界所以TCP会根据缓冲区例如1024字节为一个缓冲区进行包的划分。一个完整的包可能会被TCP拆分成多个包进行发送也有可能把多个小的包封装成一个大的数据包发送这就是所谓的TCP粘包和拆包问题。正常的理想情况两个包恰好满足TCP缓冲区的大小或达到TCP等待时长分别发送两个包粘包两个包较小间隔时间短发生粘包合并成一个包发送 拆包一个包过大超过缓存区大小拆分成两个或多个包发送拆包和粘包Packet1过大进行了拆包处理而拆出去的一部分又与Packet2进行粘包处理。解决方案给每个数据包添加包首部首部包含数据包的长度当服务器端获取到指定的包长时才说明获取完整。指定包的结束标识这样当我们获取到指定的标识时说明包获取完整。在数据包之间设置边界如添加特殊符号这样接收端通过这个边界就可以将不同的数据包拆分开。2. 可靠传输及乱序问题-TCP滑动窗口TCP必需要解决的可靠传输以及包乱序reordering的问题所以TCP必需要知道网络实际的数据处理带宽或是数据处理速度这样才不会引起网络拥塞导致丢包。见下篇ing3. TCP的拥塞处理见下篇ing4. 所以TCP是如何确保数据的可靠性连接和断开的可靠性三次握手四次挥手、有状态哪些数据发送了哪些没发、可控制超时重传、流量控制、拥塞控制等。TCP的连接是基于三次握手而断开则是基于四次挥手。确保连接和断开的可靠性。有状态TCP会记录哪些数据发送了哪些数据被接收了哪些没有被接受并且保证数据包按序到达保证数据传输不出差错。可控制它有数据包校验、ACK应答、超时重传(发送方)、失序数据重传接收方、丢弃重复数据、流量控制滑动窗口和拥塞控制等机制。参考LWIP应用开发|TCP/IP设计原理二https://www.eet-china.com/mp/a68780.html如何优化高并发TCP链接中产生的大量的TIME_WAIT的状态
阅读完成 · 觉得有帮助?