网络基础2(三)
1.传输层1.1再谈端口号下面进入传输层(本质在学操作系统网络部分)首先再谈一下端口号作为一台主机上面会绑定各种各样的应用层服务底层协议是TCP 每种应用层服务都绑定一个确定的端口号。在TCP/IP协议中用源IP, 源端口号, 目的IP, 目的端口号, 协议号五元组组来标识一个通信。以上说的不谈应用层如何序列/反序列化作HTTP还是HTTPS等单纯站在两主机要通信角度是需要这五元组的这五元组分别由不同层的不同协议来给我们提供对应的字段来保证通信正常否则通信不正常定协议等也是没有什么意义。下面谈一下端口的划分端口号不是随便被我们去使用的还可认识一些知名端口号再看看netstat 命令还有个小工具叫pidofxargs是把标准输入转为后面命令行参数。1.2 UDP协议下面学UDP 协议先看UDP协议的格式UDP协议的报头是这个样子整个报文宽度是0~31也就是4个字节。 UDP报头是操作系统自动给我们加的发的‘你好’消息在数据那里。这个报文怎么知道把报头加上后怎么做分离呢可看到上面四个字段是UDP 8字节的报头所以UDP采用定长报头做到报头和有效载荷做分离。所以收到一个UDP报文前8个字节拿走就是报头剩下的是有效载荷。收到了对应的报文怎么知道收到的UDP要交给上层的哪个协议呢根据目的端口号标定目标进程。继续说UDP协议UDP协议中对应源端口号作为客户端在发数据时会随机形成端口目的端口号是我们自己传的。有了这两个字段可进行客户端和服务器双方进程的识别。16位UDP长度表示整个报文长度减去8就是对应数据的长度。16位UDP检验和可检验数据是否出现偏差在传输过程。UDP特点是单独聊一下面向数据报面向数据报特点是你发几次对方就必须收几次(类似发信)每个报文之间有边界性。 UDP严格意义上讲只有接收缓冲区暂时保存来不及其处理的报文它没发送缓冲区因为把报文交给UDP后直接添加报头向下交付不需要发送缓冲区先出发不一定先到乱序UDP不保证可靠性直接上交。 UDP长度是16位表明单个UDP报文最大是2的16次方大概64KB 若发的报文超出长度了就得自己在应用层进行拆分。下面再理解一下UDP示意图UDP协议本质是双方操作系统所做的约定 Linux系统是用C语言写的、数据就是自己写的字符串、协议字符串等这些报头字段是采用结构体构建的报头。所谓的 UDP可叫做struct udp_header语言上叫位段类型协议上叫UDP报头。语言上这是自定义类型是类型就可以定义变量开辟空间这就是把报头信息填好了。用户想用UDP传helloworld 很多人向对方发UDP报文 UDP没发送缓冲区但报文要在内核流动交付。注定了网络通信期间存在大量UDP报文所以操作系统要对同时存在的多个UDP报文进行管理(先描述再组织)管理UDP报文的结构体叫sk_buff构建个UDP报文在内核层面定义一段缓冲区再开始把udp_header拷贝进来把用户空间内容拷贝进来。start指向缓冲区头部end指向结尾pos指向报文有效部分这样一个报文有了。未来还能存在大量报文还没有发出去所以next把每个用链表形式连接起来对UDP报文管理转为对sk_buf对应的描述结构体的管理。1.3TCP协议下面来谈TCP协议它是传输层中最重要的一个协议全称是传输控制协议。下面来理解一下黑线以上是应用层黑线以下是传输层最典型的叫TCP协议。应用层协议里典型的比如说之前写的网络版计算器 HTTP/HTTPS我们用到的应用层协议底层用的就是TCP协议的功能。TCP协议在操作系统内会存在自己的发送缓冲区和接收缓冲区也就是操作系统内只要支持TCP协议的每建立一个链接此时在操作系统内就要为该链接创建发送缓冲区和接收缓冲区。在应用层我们自己是要定义一些缓冲区来接收用户输入或把输出结果给用户的自己定义的缓冲区叫用户级缓冲区。我们在上层调那些接口的本质不是把数据发送到网络中比如发送其实是把数据从用户缓冲区拷贝到TCP的发送缓冲区。到这有应用层把数据交给了操作系统至于数据什么时候发送发送多少出错了怎么办完全由操作系统即TCP协议自主决定。文件也是类似的如我们往文件里写也用 read接口每个文件有对应的文件缓冲区应用层把数据写到文件本质是把数据从应用层拷贝到内核文件的缓冲区里至于数据什么时候刷等是由操作系统和磁盘交互的。所以磁盘和网络没区别无非就是把磁盘换成网卡就可完成数据的IO。所以应用层只负责对收上来的数据进行协议处理如反序列化等处理完后把数据再写给操作系统应用层任务就完成了数据什么时候发等由TCP 协议决定。同信双方都支持TCP协议客户如此对方服务器亦是如此对于TCP协议来讲双方地位是对等的。发送是把传输层发送缓冲区的数据写到对方接收缓冲区里这个工作本质也是拷贝只不过这个拷贝要通过网络。 read本质是从接收缓冲区读数据经常说read会有阻塞情况本质是接收缓冲区没数据。这里所谓的发送或接收缓冲区其实是内存空间操作系统为了管理内存把整个内存想成一个个4KB空间操作系统通过每个内存块的struct配置来管理内存块使用情况所以这的缓冲区本质是由多个内存块即struct配置构成的(TCP协议的控制体现在数据什么时候发等由 TCP协议决定)。下面谈谈TCP协议段格式每种数据在协议的不同层有不同的名称应用层把数据叫请求和响应传输层把数据叫数据段。这是TCP的报文那报头和有效载荷如何分离如何交付给上层TCP基本结构分三部分前20个是 TCP标准报头选项我们忽略不管剩下的是TCP协议的有效载荷。如果收到了一个TCP报文由前两个字段源和目地端口号找到上层的目标进程就可向上交付。把前20个字节拿出来就全拿到报头信息了标准报头里有个字段是4位首部长度描述的是整个报头长度(包含选项)。4位首部长度范围是[0,15]它计算的时候有基本的大小单位4字节所以表示范围是[0,60]因此选项最多是 40个字节。如果不谈选项首部长度应填0101所以4位首部长度可准确帮我们把报头从整个报文里准确的去了。报头和有效载荷分离是先读取前20个字节根据4位首部长度算出报头总大小再减20剩下的就是选项大小。再从原始报文读选项剩下的就是数据。所以分离做法是固定长度自描述字段。下面聊一下16位的窗口大小应用层构建了HTTP请求把数据通过read写到本地的发送缓冲区。发送给对方时要给发送的数据添加TCP报头封装后通过底层把数据交到了对方的接收缓冲区里。对方将报头和有效载荷进行分离根据16位端口号把数据交给对方接收缓冲区所以client和服务器基于TCP协议进行通信的时候互发消息的时候发送的可是完整的TCP报文即一定携带完整的TCP报头。TCP协议是要保证可靠性的发送过程本质是把数据拷贝给对方数据被拷进来因为接收缓冲区是固定大小的所以剩余空间就小了。如果接收方应用层因为一些原因不读取接收缓冲区的数据通信时发送方不清楚接收方的接收能力所以发送方不断给接收方发的时候有可能对方已经满了进而导致数据出现大面积丢包。这是不可靠的因此通信时接收方接收能力很弱的时候要让发送方发慢一些或干脆不发通过控制发送速度让对方来得及接收避免丢包的做法称为流量控制。 TCP可靠性有一种叫丢包重传发送的报文丢了发送方仍可以做补发那是否可不做流量控制丢了重发就行虽然可以但不合理。一个报文经过正规途径消耗了网络资源到了对方那没什么错被丢了这样浪费很大效率不高。 TCP保证可靠性策略非常多凭什么保证可靠性最基本的一个特点是确认应答机制。比如直播课时老师说一堆话老师不确定远方同学有没有听到于是老师说听到的扣1同在扣1进行响应的过程叫确认应答。所以客户端给服务器发任何消息的时候服务器都要对收到的消息进行响应。因此客户端给服务器发了个消息即便服务器没任何话给客户端说服务器也要对这个消息确认应答服务器给客户端发也一样有了应答机制可以保证客户端到服务器方向的可靠性。下面谈一下通行过程客户端想发一条消息给服务器服务器收到消息后 TCP协议要立即给对方应答发送消息、确认应答都是TCP报文。发送方发的数据量可能非常大对方也要不断进行应答发送太快可能导致来不及接收所以要流量控制。客户端发送数据慢一些依据是什么对于发送方来讲发送速度由对方的接收缓冲区中剩余的空间的大小决定。那客户端怎么知道服务器接收的剩余大小呢因为发一个报文会有对应响应报文收到响应才发第2个响应中16位窗口大小填的是接收方剩余接收空间的大小。得知对方接收能力后发的时候就知道最多发多少数据了。从客户端到服务器和服务器到客户端都要进行流量控制双方可依据16位窗口大小得知对方接收能力。因此 16位窗口大小填写的是自己的接收缓冲区中剩余的空间大小。下面来聊序号和确认序号为啥确认应答在TCP协议里这么重要比如两个人相隔100米喊话A对B喊我们一会去吃饭吧B回答说好的。此时站在A的角度收到‘好的’后可以保证刚A给B发的消息B收到了B也不知道他喊的‘好的’A有没有收到。为了让B知道收到了消息A又喊收到你的好的了此时站在B角度认为给A的应答A已经收到了。为了保证让A知道B收到了应答B又回复收到了......通过上述明白1.只要我收到了应答可以确认我最近发的消息对方收到了。2.没有应答的数据我们无法保证可靠性所以最新的一条消息是没有应答的所以我们无法保证发出去的消息是100%可靠的。因此站在全局的角度这个世界上不可能存在100%可靠的网络协议。那局部上呢A给B发消息B给A应答最新的应答对B来说不确定A 是否收到了但A 收到了应答可保证从A到 B方向上的可靠性。未来A给B了应答A不确定B是否收到应答但B收到应答可保证从B到A方向上的可靠性。最新消息之前的消息可保证双方在两个方向上的可靠性。下面来说TCP真实的方案客户端给服务器发个消息服务器合适的时候要对消息做应答。因为是客户端在给服务器发消息这个过程中最重要的是要保证发的消息被服务器收到了客户端收到应答后就能保证消息被服务器收到了不需要再服务器进行响应如果服务器给客户端发了消息服务器是主动的一方客户端做应答。服务器收到应答后立马意识到它发的消息对方收到了因此从服务器到客户端的可靠性也可以保证所以发送的数据有收到的配套的应答就可以保证两个朝向上对应的可靠性。一段时间如果没收到应答客户端认为数据丢了。下面谈个细节现实中A说我们去吃饺子吧B说吃什么饺子。B说的话既带了确认又带了B想给A说的话。比如让服务器响应又应答是发了两个报文这样效率很低。此时把应答和给对方发的消息二合一这种策略叫捎带应答。以上是最基本的通信过程现在客户端要给服务器发很多消息那客户端给服务器发了第一条消息服务器没应答时客户端能不能发第二条消息从原始过程看收到应答后才能接着发这样整个TCP通信效率非常低下。客户端此时要向服务器一次并行发一批消息服务器原则上要对每个消息进行应答只要客户端收到了历史上发出去的报文的每个应答照样可保证客户端向服务器方向上的可靠性。现在问题是客户端按顺序把报文消息发出去服务器是否一定按顺序接收呢不一定这种情况叫数据包乱序问题。造成乱序本身就是不可靠的一种怎么解决所以每个TCP报头中包含了序号发的时候给每个报文带上了序号到了服务端后根据序号对收到的报文排序就可以保证可靠性因此序号保证数据的按序到达。下面谈一下序号是什么上层是用户层下层是TCP发送缓冲区可想成一个char outbuffer[N]。用户层拷下来的任何数据在缓冲区里按顺序存在的把缓冲区当成char类型数组的时候天然每个字节都有自己的编号(本质是数组下标)。假设把一批数据发出去时发一个数据块整个报文将来被封装的时候填的序号是发送的数据块的最后一个字符的下标。现在问题是对方发来响应发送方怎么知道哪个响应对应的谁的所以TCP协议为了区分响应是对应哪个报文的响应就有了一个确认序号的概念。确认序号填充的是收到报文的序号1那为什么这么规定确认序号的意义是表示是确认序号之前的数据我已经全部收到了下一次发送数据就从确认序号指定的数字开始指定长度发送。意思是发个1000对应的响应是1001客户端读到应答后能确定自己发的1000对方前面收到了客户端再发送就从1001开始指定长度发送下一个报文。这样好处是应答时如果1001和2001丢了但是3001还在这样才能确定服务器把3001 之前的报文收到了这样包容了应答有少量的丢失。有个问题客户端给服务器发消息时带了序号 1000应答时又没数据把序号改为1001应答回来就行约定用一个序号就行为啥报头中看到有两个序号1.客户端给服务器发消息服务器应答时同时也可能想给客户端发消息所以服务器给客户端应答用确认序号它本身也带数据需要客户端响应此时用序号所以分开不能复用。2.客户端给服务器发的同时服务器也可能发给客户端所以序号还是分开。TCP报头里还携带了6个标记位作为服务器会收到多个客户端的请求TCP通信的时候建立链接通信完成后TCP断开链接断开和建立之间要进行正常数据通信。虽然不懂客户端和服务器怎么建立链接但知道一定要发TCP报文包括正常通信断开链接要发 TCP报文。所以在通信之前有些TCP请求是用来建连接的有的是把数据交给服务器的有的是断开链接的。注定了服务器收到的报文一定是有各种类型的不同的类型决定了服务器要做不同动作。那接收方如何得知报头的类型各自是什么呢所以TCP中存在了6个标记位标志位存在的意义是区分TCP报头的类型。下面聊的第一个标记位叫ACK它的作用是确认序号是否有效。通信时只要三次握手成功了大部分情况所有报文的ACK是默认被置1的。ACK就是用来确认整体的TCP报文是确认报文有数据就是携带数据的确认报文(只要有应答属性ACK就要置1)。下面来看SYN表示双方要通信首先要建立链接客户端会给服务器发各种TCP报文服务器怎么知道哪些客户端想建立链接哪些想通信一个报文中标记位要是设置了SYN标记位代表这个报文是想和服务器三次握手建立链接。下面看FIN表示我想和对方断开链接。下面说说PSH标志位TCP报头以及它所配套协议完全由操作系统自主决定所以PSH、SYN等标记位不会直接暴露给用户让用户直接去修改比特位会给我们提供系统调用来设置。PSH全称是PUSH通信时应用层因一些原因导致上层一直不取缓冲区的数据缓冲区数据会越来越多。下层由操作系统管理上层读不读由用户决定。对于缓冲区来讲操作系统一定要把收到的数据放缓冲区一定要有人从上层把缓冲区的数据取走这就是系统级的生产者消费者模型因此所有流量控制本质是对发送过程同步的过程。同步了发送方和接收方的应用层发送方一直写接收方不拿可能导致发送和接收缓冲区都满了因此发送方应用层会有写阻塞。那接收方开始取数据后发送方怎么知道自己可以发了1.发送方定期询问接收方。2.接收方缓冲区更新了此时给发送方发个报文表示更新了。以上两种策略同时存在哪种先协商成功就用哪一种。假设是第一种然后接收方应用层就是不拿缓冲区里的数据此时发送方会不断的问对方依旧不拿走此时发送方继续发TCP报文并且把PSH标志位设置。接收方收到了携带PSH标志位的报文时他的操作系统就明白要赶紧把缓冲区的数据给上层PSH作用是催促对方尽快把缓冲区李的数据取走。下面来看 RST标记位TCP是为了通信保证可靠性的那是否意味着建立链接必须要成功TCP可靠性是在建立链接成功通信时有很多保证可靠性的策略建立连接失败是正常的。所以TCP虽然保证可靠性但是TCP允许建立链接失败。客户端和服务器通信三次握手期间三次握手全部完成时双方才认为把链接建立好了。所以作为服务端操作系统内可能存在多个已被建立好的链接直接证据是服务端会在应用层让用户获取多个连接所对应的文件描述符。每次三次握手成功要维护一个链接(以服务端为例)一个链接断开要把链接断开。通信时通信双方发送时起始、确认序号是什么累计发送或接收了多少数据这些信息都要由双方操作系统TCP层维护的。服务端存在多个链接服务端操作系统要对链接进行常规管理先描述再组织。因此操作系统要为链接创建struct结构体里面包含链接对应的属性如链接的起始序号、确认序号等等。起始连接结构体再带上连接指针对连接管理转为对链表管理客户端和服务器维护链接也是有成本的如对象创建花空间、时间等。大量客户端进行连接的时候可能出现连接建立异常的情况。如果客户端认为连接建立好了服务器认为连接没建立好此时三次握手完成了。因此客户端给服务器发消息服务器应答时会携带RST要求对连接重新建立。下面来理解一下TCP建立连接时要经历三次握手第一次握手客户端要给服务器发送SYN报文服务器收到SYN后给客户端发SYNACKACK表示的是对上一个报文的确认SYN标记位表示服务器也同意和客户端建立链接。接下来客户端再给服务器发送ACK进行确认这三次称为三次握手其中握手时是经历网络的。客户端到服务器和服务器到客户端的三次握手中画的线是斜的因为报文中间是要经过网络发送的所以客户端发对应报文和服务端收到报文是有时间差的。那对于客户端来讲三次握手时是把ACK发出去客户端认为建立好了链接还是服务器真正收到了报文客户端才认为链接建立好了最后一个ACK没有应答短期内客户端不可能知道ACK有没有被服务端收到所以客户端认为链接建立好策略是只要他把第三个报文发出去了他就认为链接已经建立好了。TCP三次握手前两个报文有应答第三个报文没有应答所以TCP进行三次握手时本质是在赌在赌第三个ACK一定能被服务端收到如果收到了链接就建立好了。我们说了链接也要被管理所以客户端在发出最后的时候创建链接的结构体准备通信。假设最后的ACK丢了服务端认为链接并没有建立好。客户端和服务器在发送和接收最后一个报文时是有一些时间窗口的所以这个时间窗口内客户端和服务器对于链接是否建立成功的认识不一致。客户端认为链接建立好了就可以开始向服务端发送数据服务器收到这个报文就会感觉很奇怪服务器就给客户端进行应答设置RST标志位。客户端收到报文设置了RST就把前面的链接结构体释放了重新发起三次握手。因此RST标志位是在客户端和服务器认为链接不一致即建立链接异常时双方连接重置使用的。下面谈一下URG我们称它为紧急指针标志位。我们说过TCP是按序到达的但有些情况下就是想让有些数据进行插队进行优先处理。在原始TCP语境之下这种插队情况是绝对不可能存在的因为TCP必须按序到达。若就想让上层高优先级处理某些特殊数据此时就可以设置URG标志位。一旦URG被置为1表示16位紧急指针有效16位紧急指针通常表示在当前TCP报文中包含紧急数据的哪个数据的偏移量。比如数据携带了100016位紧急指针填500表示500那个位置包含的是紧急数据。这个数据是要被插队的数据 TCP中紧急数据默认一般只允许携带1个字节。那什么样的场景用URG比如这是机房中的某个硬件服务器上面载了某种服务底层网络通信用的是TCP协议。现有客户端想向服务器发起请求完成某种任务但用着时发现服务不响应了但机器没有挂。我很好奇不知道怎么回事所以服务端要支持读取紧急数据和给软件功能提供编号。这时服务器不响应客户端发个紧急数据这个数据不用排队服务器读到后把软件状态以紧急数据给客户端返回数字客户端很快就知道服务器当前怎么了。下面来说TCP策略问题(补充前面没有说过的)来说一下超时重传主机A在向主机B发送消息时如果是数据本身丢失了主机B不知道A给他发过数据所以B也不会给A进行应答。所以A没有收到应答就不清楚自己发的报文是否被B收到没有应答A也不清楚报文是丢了还是阻塞中。所以我们定出一个特定的时间间隔在特定的时间间隔内没有对话的应答主机A就判定数据丢了。此时进行重传这种策略我们称为超时重传。当然主机A给主机B发消息时可能数据没有丢而是应答丢了也会导致主机A没有收到确认应答规定只要特定时间没有收到数据就立马做补发。通过上述可明白主机对于发出去的报文是否丢失无法判定必须通过规定表真是否重传。上面情况二数据没丢应答丢了A再次发数据注定主机B会收到重复报文。为了保证可靠性需要去重那主机B如何去重呢报文有序号所以可对重复报文进行去重。那超时时间如何确定呢如果我们网络情况特别好超时的时间间隔一定不要设置的太长如果出现小量丢包时要等待好长时间才能去重传会影响传送效率。如果网络情况特别糟糕也不能把等待的时间设置的太短如果太短每发个数据都会超时会不断进行报文补发。所以这个时间间隔需要是动态的和网络状况相关。这的时间设置策略是这样默认超时时长 500 毫秒即0.5秒如果判定超时了需要重发后得不到应答等待时间就变成了 2×500毫秒。不断增加重传一定次数后还是得不到应答TCP就认为网络或对端主机出现了问题就把链接强制关了。下面正式说说TCP连接管理机制TCP可靠性保证里有一个是TCP在通信前要建建立三次握手。首先客户端向服务端发送SYN服务端收到SYN认为客户端想和我建立链接。客户端给服务务端推送SYNACK客户端再给服务端发ACK叫做三次握手成功。对于客户端来讲第三次握手只要发出链接就建立好服务端收到第三次的ACK后认为链接建立好。三次握手成功之后客户端和服务端双方连接已经建立好了该维护的连接数据结构就维护好了。往后进行数据通信一方发数据另一方ACK应答。三次握手的过程中有两个重要的函数客户端向服务端发起链接调用connect向对方发起链接请求其实这个建立的过程是客户端构建SYN报文发给服务端。connect函数只负责发起3次握手至于三次握手的细节上层系统调用接口实则不过多的参与握手过程是双方操作系统自主完成的。connect发出SYN之后connect此时就处于阻塞状态等待三次握手完成后connect返回。accept函数本身不参与三次握手只会把已经建立好的链接拿上来底层没有建好的链接accept就会一直阻塞。进行三次握手时双方操作系统对应的连接状态会发生变化发起三次握手发送方处于SYN_SENT这个状态我们称为同步发送。服务端收到了SYN它的状态称为SYN_RCVD表示同步收到。客户端收到第二个报文后立马发ACK发完后它的状态变为ESTABLISHED表示本地链接已经建立。服务端收到最后ACK后状态变为ESTABLISHED。通信完成后要断开链接应用层对应的是上层调用close。客户端关闭链接此时向服务端发FIN标记位因为保证可靠性所以服务端对FIN做ACK应答。服务端到客户端也是一样这就是四次挥手。TCP通信是基于链接的建立和断开链接采用三次握手和四次挥手。为什么要三次握手和四次挥手实际上是客户端发一个SYN 服务端要ACK完了后服务端再发SYN客户端回ACK。以上是正常的三次是中间两次报文捎带应答了。四次挥手中ACK和FIN也可捎带应答压缩成三次挥手。从普通角度来看三次握手和四次挥手本质是一来一回的一种可靠性少了任何一个报文可靠性就无法保证了它们其实都是4次只不过被捎带应答了所以变成了一个三次一个四次。再说个理由三次握手能够保证无论是客户端还是服务端在进行双方通信之前双方都至少做过一次发至少做过一次收。这叫做验证通路是否通畅也就是验证全双工通路是否通畅(双方能发能收)。若是单次握手客户端发大量SYN服务端就要把链接建立好这样服务端资源会被消耗完这过程称为SYN洪水显然不可以。若是两次握手服务端第二次发出连接就建立好客户端收到连接也建立好。这过程中服务端先建立好链接客户端才建立链接。万一客户端发完SYN后崩了因为ACK不用应答所以服务端仍维持链接一段时间。直到发在客户端长时间不访问这个链接才会关闭。若大量客户端出异常了服务端上要长时间挂大量异常链接。若是3次握手就算第三次ACK丢了但对客户端讲连接建立成功了服务端认为连接没建立成功。这里连接失败的成本嫁接到了客户端上这样可保这服务器本身的稳定性。为啥三次握手上述思路总结下面说说为啥4次挥手断开链接是双方的事情客户端给服务端说要断开是可以的但服务端也必须给客户端说要断开链接。断开链接本质是断开链接方没有数据给对方发送了发送数据时双方都可能发所以必须断开两次。客户端发一个FIN再收一个ACK是保证从客户端到服务端没数据发了。同样再两次保证从服务端到客户端也没数据发了。协商后双方都不再互发消息了所以连接没有存在的必要了。所以四次挥手本质是可靠的保证双方都能得知道对方不想发消息的意愿所以需要四次挥手。主动断开链接的一方先发FIN状态是FIN_WAITE_1服务端收到后状态变为CLOSE_WAIT。再次进行ACK后客户端变成FIN_WAIT_2这种状态下客户端不再给服务端发送数据了(但可发管理报文如ACK)。服务端如果也发FIN状态变为LAST_ACK。客户端收到对方想断开链接后状态变为TIME_WAITACK后服务端状态是CLOSED。客户端状态进入TIME_WAIT 表示客户端要等待一段时间。下面说说TCP关于状态的相关介绍这是我们之前写过的TCP代码main.cc这写了一个服务器然后初始化和启动。用TcpServer.hpp中注释一些内容我们重点是研究TCP三次握手和四次挥手的过程我们最多研究到获取到链接这一层就行。目前对应的服务器在start函数这里相当于什么都没有做连连接都没有获取前面只是有创建套接字绑定和监听。TcpClient.cc就创建套接字连接失败后还会做超时重传。继续看中间服务器右边是客户端启动服务后通过netstat查到了服务端。因此tcp服务没调任何accept只要套接字处于Listen状态就可以通过netstat命令查到。这份代码没有进行accept把backlog参数设为1了客户端连服务器查的时候看到服务器所在地方上(阿里云)存在一个远端的47.93.176.120:39414的 ESTABLISHED连接虽然服务器上没有accept但客户端和服务端已经建好链接了。云服务器无法绑定自己的ip地址Local Address没填47.93.176.120公网IP是因为其实那个IP是被模拟出来的显示的才是真的公网。意味着站在阿里云机器上即服务器角度已经有这个客户端来连我了。连接是双向的客户端上netstat -ntp看到客户端上存在一个远端的47.93.176.120:8888的ESTABLISHED连接。所以连接建立成功这件事和上层没有accept是没有关系的侧面验证了三次握手是双方操作系统自动完成的。再来一个客户端去连服务器左下查的时候看到他们连的是同一个服务器本地采用随机端口端口号不同就行了连接状态是 ESTABLISHED。再增加一个服务器连接发现第三次时客户端认为连接建立好了服务器认为连接没有建立好。继续这样可以发现从第三次往后除非上一次有连接退出了否则服务器认为连接是SYN_RECV的。因为listen第二个参数前面设置为了1所以底层能建立的链接数是2。下面说说一个客户端向服务端建立连接时如果三次握手可以完成服务端在底层建立好连接这个链接可暂时不把它拿上去第二个客户端来后完成三次握手服务器底层也要建立连接。在这样来后服务端上会存在大量已经建立好的链接但这些被建立好的链接并没有被上层拿上去因此操作系统对建立好的链接要有临时的维护先描述后组织。这里采用队列形式管理已经建立好的链接accept 就是从队列里把连接取走和特定文件关联。Listen第2个参数1表示底层已经建立好的链接队列的最大长度这个队列也称为全链接队列。前面来第三个链接时不让入队列了所以服务端状态是SYN_RECV。上面第三次连接时不能让连接进入到全链接队列即使3次握手成功也不能放(上层没accept)。从一个状态变成下一个状态必须满足下一个状态的条件这里客户端一定发了ACK 因为客户端建立连接成功了。只是因为listen第二个参数的限制此时全队列已经被打满了而这里状态没变是由于服务端把最后ACK丢弃了因为服务端判定全链接队列已经满了就不允许再建立链接成功了。前两次允许握手客户端发ACK时发现全连接队列是满的于是服务端把ACK扔了服务端3次握手不完整就无法从一个状态变迁到下一个状态所以是SYN_RECV。过了一段时间再查时发现这个SYN_RECV链接从服务端查不到了说明服务端把它释放了而客户端认为连接还是成功的。因此服务端不会长时间维护SYN_RECV被建立链接的一方处于SYN_RECV时由于握了前两次这种链接称为半链接。半链接也是要维护队列的称为半链接队列半链接的节点不会长时间维护。当用最后一次连接的客户端发消息服务端上看到又有了SYN_RECV这里底层又3次握手(因为实际没完成3次握手重建链接)。上面反映客户端和服务器链接不一致的问题了解以上后来看两个问题1.listen 第二个参数为什么不能太长2.为什么不能没有1.如果全连接队列太长可能会导致服务器上有些链接来不及被上层处理依旧要在系统内长时间维持。服务器此时非常忙来不及消化全链接维护了长链接非常占资源而且没创造什么价值所以不能太长。2.(饭店门口凳子的例子)服务器上处理完客户端后闲下来了就可以有能力再从全链接队列里获取一个链接否则就要等着客户端发起链接accept处于阻塞服务器上资源没被充分利用(如上三次握手状态变化谈完了)。下面谈一下4次挥手先断开链接的一方是FIN_WAIT_1服务端还没关链接进入CLOSE_WAIT。建立链接后让客户端退出再查时看到客户端那什么都没了服务端处于CLOSE_WAIT状态虽然客户端进程退出了文件描述符也就关了相当于断开链接给服务端发FIN 服务端也给对方ACK了。但服务端并没有CLOSE所以服务端无法从CLOSE_WAIT变迁到LAST_ACK。服务端把节点拿上去5秒后把文件描述符关了服务端先要断开连接。日志信息显示到文件再连一下查时看到8899建立了close后连接状态是FIN_WAIT_2(服务端发FIN,客户端回ACK服务端此时状态)。现在把客户端给退出了此时服务端状态是TIME_WAIT 客户端此时查就没了。根据上述来看(前提是把链接从底层获取上来否则直接断开看不到状态现象链接直接释放了)服务端链接关了FIN后客户端ACK 服务端进入FIN_WAIT_2。客户端ctrlc发FIN服务端再ACK收到ACK后客户端断开链接但主动断开链接的一方此时处于TIME_WAIT状态。过了一段时间再看8899的TIME_ WAIT没有了上面发现主动断开链接的一方在4次挥手完成后要进入TIME_ WAIT状态等待若干时长之后会自动释放。下面基于现象聊一下最后服务端关了再重新启动发现出现绑定失败。正常通信时主动断开链接的一方如果是服务方服务方就要进入TIME_ WAIT状态意思是这时链接没有彻底被断开。这样意味着ip和port正在被使用端口号无法被两个不同进程同时绑定所以再次启动时无法绑定。这样问题是当有很多客户端和服务端处于正常链接但最后一个新链接来的时候导致服务端挂了这样服务端变成了主动断开链接的一方此时服务端中一定会充满大量的TIME_WAIT状态这样服务端无法立即重启在一些场景下会很大损失。如何解决man setsockopt设置套节字属性设置成允许我们地址复用。参数是文件描述符层级一般设置到sock这一层。下面用一下以后可不断启动。如果ip和端口被使用是因为TIME_WAIT状态这样就可以立即启动(别的状态不行客户端没这样问题是因为客户端用的随机端口)。下面说说TIME_WAIT等多长时间为什么要等以及等这么长时间TCP规定主动断开链接一方处于TIME_WAIT一旦TIME_WAIT要等待两个MSL才能进入CLOSED状态。一个报文从客户端到服务端来讲一个报文在网络中是有存活时间的一个报文在网络中存活的最大时长叫MSL(从一端到另一端)。等待是因为这个时间相当于一个报文一来一回这样可保证客户端和服务端上双方历史发了还未收到的报文可能在这个时间被彼此收到。还有个原因是可以保证四次挥手以较大的概率正确挥手完比如最后ACK丢了可补发FIN 此时还有回应。一个报文在网络存活时长规定为两分钟可能遇到阻塞等存在时长和传输时长两个状态注意区分。ubanto中看见是60秒