<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title><![CDATA[Ops运维 | Mr`kang Blog｜小豆子]]></title> 
<link>https://www.cnmysql.com/index.php</link> 
<description><![CDATA[关注服务器运维技术、DB优化、Docker虚拟化、自动化运维、工作积累]]></description> 
<language>zh-cn</language> 
<copyright><![CDATA[Ops运维 | Mr`kang Blog｜小豆子]]></copyright>
<item>
<link>https://www.cnmysql.com/post//</link>
<title><![CDATA[TCP状态转换图 ]]></title> 
<author>kangyang &lt;kangyang315@qq.com&gt;</author>
<category><![CDATA[WEB技术]]></category>
<pubDate>Wed, 27 Aug 2014 04:05:02 +0000</pubDate> 
<guid>https://www.cnmysql.com/post//</guid> 
<description>
<![CDATA[ 
	<span style="font-size: 12px;"><span style="font-family: 宋体;"><br/>1、TCP状态图<br/><a href="https://www.cnmysql.com/attachment.php?fid=17" target="_blank"><img src="https://www.cnmysql.com/attachment.php?fid=17" class="insertimage" alt="点击在新窗口中浏览此图片" title="点击在新窗口中浏览此图片" border="0"/></a><br/>2、下面是TCP中的一种连接过程: 客户端发起连接请求 --> 连接确立 --> 信息交互 --> 客户端发起关闭连接请求 --> 完成关闭连接.<br/>&nbsp;&nbsp;(1)客户机的TCP状态序列<br/><a href="https://www.cnmysql.com/attachment.php?fid=18" target="_blank"><img src="https://www.cnmysql.com/attachment.php?fid=18" class="insertimage" alt="点击在新窗口中浏览此图片" title="点击在新窗口中浏览此图片" border="0"/></a><br/> （2）服务器的TCP状态序列<br/><a href="https://www.cnmysql.com/attachment.php?fid=19" target="_blank"><img src="https://www.cnmysql.com/attachment.php?fid=19" class="insertimage" alt="点击在新窗口中浏览此图片" title="点击在新窗口中浏览此图片" border="0"/></a><br/>客户端的TIME_WAIT状态<br/>在客户端TCP的状态转换图中，客户机会经历一个TIME_WAIT的状态。为什么在关闭连接过程中还需要这样的一个状态呢？<br/>先看一下下面这幅图：<br/><a href="https://www.cnmysql.com/attachment.php?fid=20" target="_blank"><img src="https://www.cnmysql.com/attachment.php?fid=20" class="insertimage" alt="点击在新窗口中浏览此图片" title="点击在新窗口中浏览此图片" border="0"/></a><br/>假如最后一个ACK在网络中丢失，那么接收不到确认信号的服务器将会重发最后一个FIN，这就需要客户端维护一个状态信息以允许它能重发最终的ACK。如果不维护这样的状态信息，它将会以RST（另外一个类型的TCP分节）响应，而服务器则会把这个RST解析成接收错误。&nbsp;&nbsp;&nbsp;&nbsp;<br/>但如果TCP要进行完毕必要的工作来彻底终止某个连接双方向的数据流（全双工关闭），那么在关闭连接中的四个分节的任何一个的丢失情况都必须要正确处理。这就是需要存在TIME_WAIT状态的其中一个重要理由。<br/><br/>1、建立连接协议（三次握手）<br/> （1）客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的报文1。<br/> （2） 服务器端回应客户端的，这是三次握手中的第2个报文，这个报文同时带ACK标志和SYN标志。因此它表示对刚才客户端SYN报文的回应；同时又标志SYN给客户端，询问客户端是否准备好进行数据通讯。<br/> （3） 客户必须再次回应服务段一个ACK报文，这是报文段3。<br/>2、连接终止协议（四次握手）<br/>　 　由于TCP连接是全双工的，因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动，一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭，而另一方执行被动关闭。<br/>　（1） TCP客户端发送一个FIN，用来关闭客户到服务器的数据传送（报文段4）。<br/>　（2） 服务器收到这个FIN，它发回一个ACK，确认序号为收到的序号加1（报文段5）。和SYN一样，一个FIN将占用一个序号。<br/>　（3） 服务器关闭客户端的连接，发送一个FIN给客户端（报文段6）。<br/>　（4） 客户段发回ACK报文确认，并将确认序号设置为收到序号加1（报文段7）。<br/>CLOSED: 这个没什么好说的了，表示初始状态。<br/>LISTEN: 这个也是非常容易理解的一个状态，表示服务器端的某个SOCKET处于监听状态，可以接受连接了。<br/>SYN_RCVD: 这个状态表示接受到了SYN报文，在正常情况下，这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态，很短暂，基本上用netstat你是很难看到这种状态的，除非你特意写了一个客户端测试程序，故意将三次TCP握手过程中最后一个ACK报文不予发送。因此这种状态时，当收到客户端的ACK报文后，它会进入到ESTABLISHED状态。<br/>SYN_SENT: 这个状态与SYN_RCVD遥想呼应，当客户端SOCKET执行CONNECT连接时，它首先发送SYN报文，因此也随即它会进入到了SYN_SENT状态，并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。<br/>ESTABLISHED：这个容易理解了，表示连接已经建立了。<br/>FIN_WAIT_1: 这个状态要好好解释一下，其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是：FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时，它想主动关闭连接，向对方发送了FIN报文，此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后，则进入到FIN_WAIT_2状态，当然在实际的正常情况下，无论对方何种情况下，都应该马上回应ACK报文，所以FIN_WAIT_1状态一般是比较难见到的，而FIN_WAIT_2状态还有时常常可以用netstat看到。<br/>FIN_WAIT_2：上面已经详细解释了这种状态，实际上FIN_WAIT_2状态下的SOCKET，表示半连接，也即有一方要求close连接，但另外还告诉对方，我暂时还有点数据需要传送给你，稍后再关闭连接。<br/>TIME_WAIT: 表示收到了对方的FIN报文，并发送出了ACK报文，就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下，收到了对方同时带FIN标志和ACK标志的报文时，可以直接进入到TIME_WAIT状态，而无须经过FIN_WAIT_2状态。<br/>CLOSING: 这种状态比较特殊，实际情况中应该是很少见，属于一种比较罕见的例外状态。正常情况下，当你发送FIN报文后，按理来说是应该先收到（或同时收到）对方的ACK报文，再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后，并没有收到对方的ACK报文，反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢？其实细想一下，也不难得出结论：那就是如果双方几乎在同时close一个SOCKET的话，那么就出现了双方同时发送FIN报文的情况，也即会出现CLOSING状态，表示双方都正在关闭SOCKET连接。<br/>CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢？当对方close一个SOCKET后发送FIN报文给自己，你系统毫无疑问地会回应一个ACK报文给对方，此时则进入到CLOSE_WAIT状态。接下来呢，实际上你真正需要考虑的事情是察看你是否还有数据发送给对方，如果没有的话，那么你也就可以close这个SOCKET，发送FIN报文给对方，也即关闭连接。所以你在CLOSE_WAIT状态下，需要完成的事情是等待你去关闭连接。<br/>LAST_ACK: 这个状态还是比较容易好理解的，它是被动关闭一方在发送FIN报文后，最后等待对方的ACK报文。当收到ACK报文后，也即可以进入到CLOSED可用状态了。<br/>最后有2个问题的回答，我自己分析后的结论（不一定保证100%正确）<br/>1、 为什么建立连接协议是三次握手，而关闭连接却是四次握手呢？<br/>这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后，它可以把ACK和SYN（ACK起应答作用，而SYN起同步作用）放在一个报文里来发送。但关闭连接时，当收到对方的FIN报文通知时，它仅仅表示对方没有数据发送给你了；但未必你所有的数据都全部发送给对方了，所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后，再发送FIN报文给对方来表示你同意现在可以关闭连接了，所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。<br/>2、 为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态？<br/>这是因为：虽然双方都同意关闭连接了，而且握手的4个报文也都协调和发送完毕，按理可以直接回到CLOSED状态（就好比从SYN_SEND状态到ESTABLISH状态那样）；但是因为我们必须要假想网络是不可靠的，你无法保证你最后发送的ACK报文会一定被对方收到，因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文，而重发FIN报文，所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。<br/><br/>同时打开<br/><br/>两个应用程序同时执行主动打开的情况是可能的，虽然发生的可能性较低。每一端都发送一个SYN,并传递给对方，且每一端都使用对端所知的端口作为本地端口。例如：<br/>主机a中一应用程序使用7777作为本地端口，并连接到主机b 8888端口做主动打开。<br/>主机b中一应用程序使用8888作为本地端口，并连接到主机a 7777端口做主动打开。<br/>tcp协议在遇到这种情况时，只会打开一条连接。<br/>这个连接的建立过程需要4次数据交换，而一个典型的连接建立只需要3次交换（即3次握手）<br/>但多数伯克利版的tcp/ip实现并不支持同时打开。<br/><a href="https://www.cnmysql.com/attachment.php?fid=21" target="_blank"><img src="https://www.cnmysql.com/attachment.php?fid=21" class="insertimage" alt="点击在新窗口中浏览此图片" title="点击在新窗口中浏览此图片" border="0"/></a><br/>SYN_RCVD与SYN_SEND都是转换为ESTABLISHED的中间状态，目标是两端均转换到ESTABLISHED状态。<br/><br/>同时关闭<br/><br/>如果应用程序同时发送FIN，则在发送后会首先进入FIN_WAIT_1状态。在收到对端的FIN后，回复一个ACK，会进入CLOSING状态。在收到对端的ACK后，进入TIME_WAIT状态。这种情况称为同时关闭。<br/>同时关闭也需要有4次报文交换，与典型的关闭相同。<br/><a href="https://www.cnmysql.com/attachment.php?fid=22" target="_blank"><img src="https://www.cnmysql.com/attachment.php?fid=22" class="insertimage" alt="点击在新窗口中浏览此图片" title="点击在新窗口中浏览此图片" border="0"/></a><br/><br/><br/>LISTEN：侦听来自远方的TCP端口的连接请求<br/>SYN-SENT：再发送连接请求后等待匹配的连接请求<br/>SYN-RECEIVED：再收到和发送一个连接请求后等待对方对连接请求的确认<br/>ESTABLISHED：代表一个打开的连接<br/>FIN-WAIT-1：等待远程TCP连接中断请求，或先前的连接中断请求的确认<br/>FIN-WAIT-2：从远程TCP等待连接中断请求<br/>CLOSE-WAIT：等待从本地用户发来的连接中断请求<br/>CLOSING：等待远程TCP对连接中断的确认<br/>LAST-ACK：等待原来的发向远程TCP的连接中断请求的确认<br/>TIME-WAIT：等待足够的时间以确保远程TCP接收到连接中断请求的确认<br/>CLOSED：没有任何连接状态<br/><br/>netstat -na&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ----显示当前所有活动的网络连接<br/>netstat -aultnp&nbsp;&nbsp;&nbsp;&nbsp;----显示出所有处于监听状态的应用程序及进程号和端口号<br/>netstat -aultnp &#124; grep 80&nbsp;&nbsp;&nbsp;&nbsp;----显示所有80端口的网络连接<br/>netstat -aultnp &#124; grep :80 &#124; sort&nbsp;&nbsp;----显示所有80端口的网络连接并排序<br/><div class="code">#netstat -n -p&#124;grep SYN_REC &#124; wc -l</div><br/>这个命令可以查找出当前服务器有多少个活动的 SYNC_REC 连接。正常来说这个值很小，最好小于5。 当有Dos攻击或者邮件炸弹的时候，这个值相当的高。尽管如此，这个值和系统有很大关系，有的服务器值就很高，也是正常现象。<br/><br/>列出所有连接过的IP地址。<br/>#netstat -n -p &#124; grep SYN_REC &#124; sort -u<br/>列出所有发送SYN_REC连接节点的IP地址。<br/>#netstat -n -p &#124; grep SYN_REC &#124; awk '&#123;print $5&#125;' &#124; awk -F: '&#123;print $1&#125;'<br/>使用netstat命令计算每个主机连接到本机的连接数。<br/>#netstat -ntu &#124; awk '&#123;print $5&#125;' &#124; cut -d: -f1 &#124; sort &#124; uniq -c &#124; sort -n<br/>列出所有连接到本机的UDP或者TCP连接的IP数量。<br/>#netstat -anp &#124;grep 'tcp&#124;udp' &#124; awk '&#123;print $5&#125;' &#124; cut -d: -f1 &#124; sort &#124; uniq -c &#124; sort -n<br/>检查 ESTABLISHED 连接并且列出每个IP地址的连接数量。<br/>#netstat -ntu &#124; grep ESTAB &#124; awk '&#123;print $5&#125;' &#124; cut -d: -f1 &#124; sort &#124; uniq -c &#124; sort -nr<br/>列出所有连接到本机80端口的IP地址和其连接数。80端口一般是用来处理HTTP网页请求。<br/>#netstat -plan&#124;grep :80&#124;awk &#123;'print $5'&#125;&#124;cut -d: -f 1&#124;sort&#124;uniq -c&#124;sort -nk 1<br/><br/>TCP恋爱史：三次握手和四次分手<br/>TCP协议非常重要，这里把它的连接和释放整理一下。<br/><a href="https://www.cnmysql.com/attachment.php?fid=42" target="_blank"><img src="https://www.cnmysql.com/attachment.php?fid=42" class="insertimage" alt="点击在新窗口中浏览此图片" title="点击在新窗口中浏览此图片" border="0"/></a><br/>首先是三次握手：<br/>1、&nbsp;&nbsp;客户端发起，像服务器发送的报文SYN=1，ACK=0，然后选择了一个初始序号：seq=x。<br/><br/>SYN是干什么用的？<br/><br/>在链接的时候创建一个同步序号，当SYN=1同时ACK=0的时候，表明这是一个连接请求的报文段。如果对方有意链接，返回的报文里面SYN=1，ACK=1,。从这个意义上来说，SYN=1的时候，就表明这是一个‘请求’或者‘接受请求’的报文。<br/><br/>SYN=1的报文段不能携带数据。但是要消耗掉一个序号，<br/><br/>ACK是干什么用的？<br/><br/>仅当ACK=1的时候，确认字号（期望收到对方下一个报文段的第一个数据字节的编号）才有效。因此，TCP规定，当链接建立之后，所有往来的报文里面的ACK都应该是1（事实上，也只有客户端发起的链接请求报文的ACK没有置1）。<br/><br/>现在的状态：客户端进入SYN-SEND状态；<br/><br/>2、&nbsp;&nbsp;服务器接收到了SYN=1，ACK=0的请求报文之后，返回一个SYN=1，ACK=1的确认报文。<br/><br/>同时，确认号ack=x+1，同时也为自己选择一个初始序号seq=y<br/><br/>现在的状态：服务器进入SYN-REVD状态；<br/><br/>3、&nbsp;&nbsp;客户端接收到了服务器的返回信息之后，还要给服务器返回最后一条确认，ACK=1，确认号ack=y+1；<br/><br/>现在的状态：客户端进入ESTABLISHED状态。<br/><br/>下面说一下为什么两次握手不行，非得三次：<br/><br/>首先说明一种正常的情况，就是客户端发送了一条请求链接的报文，但是由于网络原因丢失了，所以，不可能接收到服务器端的确认。这个时候，客户端就就只有再一次发送原来的请求报文，这次服务器收到之后返回确认，客户端再确认一次，链接确立。<br/><br/>然后考虑一种不正常的情况，客户端发了两次请求链接的报文，第二条被服务器捕捉到，返回数据，完成了两次握手。数据传送完成之后，链接关闭。但是这时候，第一条拥塞的请求报文现在到达了服务器端，服务器还以为客户端要又一次建立连接，于是发送确认，然后把自己敞开，等着客户端发送过来数据。于是，很多的网络资源就是这样浪费掉了。<br/><br/>要是实行三次握手，服务器收到了一条过期的请求报文，返回确认信息，客户端接收到了服务器的信息之后感到莫名其妙，心想：我他妈又没要链接，你返回这个是不是疯了。于是不置一词。服务器过一段时间还没有收到第三次握手的数据，知道客户端并没有要求建立链接的请求，含泪离开。<br/><br/>然后是四次分手：<br/>现在双方的状态都是ESTABLISHED状态。<br/><br/>1、&nbsp;&nbsp;客户端发起请求，请求断开链接。FIN=1，seq=u。u是之前传送过来的最后一个字节的序号+1。<br/><br/>FIN：用来释放一个链接，当FIN=1的时候，表明此报文的发送方已经完成了数据的发送，没有新的数据要传送，并要求释放链接。<br/><br/>客户端进入FIN-WAIT-1状态，等着服务器返回确认；<br/><br/>2、&nbsp;&nbsp;服务器收到客户端的请求断开链接的报文之后，返回确认信息。ACK=1，seq=v，ack=u+1。<br/><br/>服务器进入CLOSE-WAIT状态。<br/><br/>这个时候，客户端不能给服务器发送信息报文，只能接收。但是服务器要是还有信息要传给服务器，仍然能传送。<br/><br/>3、&nbsp;&nbsp;当服务器也没有了可以传的信息之后，给客户端发送请求结束的报文。FIN=1，ACK=1，<br/><br/>ack=u+1，seq=w。<br/><br/>这个时候的状态：服务器进入LAST-ACK状态。<br/><br/>4、&nbsp;&nbsp;客户端接收到FIN=1的报文之后，返回确认报文，ACK=1，seq=u+1，ack=w+1。<br/><br/>发送完毕之后，客户端进入等待状态，等待两个时间周期。关闭。<br/><br/>为什么最后还要等待两个时间周期呢？<br/><br/>1、&nbsp;&nbsp;客户端的最后一个ACK报文在传输的时候丢失，服务器并没有接收到这个报文。这个候。<br/><br/>服务器就会超时重传这个FIN消息，然后客户端就会重新返回最后一个ACK报文，等待两个时间周期，完成关闭。如果不等待这两个时间周期，服务器重传的那条消息就不会收到。服务器就因为接收不到客户端的信息而无法正常关闭。<br/><br/>2、&nbsp;&nbsp;预防上一次在三次握手中提到的失效的报文干扰。两个时间周期过去之后，所有的报文都会在网络中消失，保证下一次重新连接的时候有乱七八糟的报文影响。<br/><br/></span></span><br/>Tags - <a href="https://www.cnmysql.com/tags/tcp/" rel="tag">tcp</a> , <a href="https://www.cnmysql.com/tags/time_wait/" rel="tag">time wait</a>
]]>
</description>
</item><item>
<link>https://www.cnmysql.com/post//#blogcomment</link>
<title><![CDATA[[评论] TCP状态转换图 ]]></title> 
<author> &lt;user@domain.com&gt;</author>
<category><![CDATA[评论]]></category>
<pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate> 
<guid>https://www.cnmysql.com/post//#blogcomment</guid> 
<description>
<![CDATA[ 
	
]]>
</description>
</item>
</channel>
</rss>