TCP11种状态

TCP11种状态图

CLOSED:初始状态,表示TCP连接是“关闭着的”或“未打开的”。

LISTEN :表示服务器端的某个SOCKET处于监听状态,可以接受客户端的连接。

SYN_RCVD :表示服务器接收到了来自客户端请求连接的SYN报文。在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat很难看到这种状态,除非故意写一个监测程序,将三次TCP握手过程中最后一个ACK报文不予发送。当TCP连接处于此状态时,再收到客户端的ACK报文,它就会进入到ESTABLISHED 状态。

SYN_SENT :这个状态与SYN_RCVD 状态相呼应,当客户端SOCKET执行connect()进行连接时,它首先发送SYN报文,然后随即进入到SYN_SENT 状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT 状态表示客户端已发送SYN报文。

ESTABLISHED :表示TCP连接已经成功建立。

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看到。

FIN_WAIT_2 :上面已经解释了这种状态的由来,实际上FIN_WAIT_2状态下的SOCKET表示半连接,即有一方调用close()主动要求关闭连接。注意:FIN_WAIT_2 是没有超时的(不像TIME_WAIT 状态),这种状态下如果对方不关闭(不配合完成4次挥手过程),那这个 FIN_WAIT_2 状态将一直保持到系统重启,越来越多的FIN_WAIT_2 状态会导致内核crash。

TIME_WAIT :表示收到了对方的FIN报文,并发送出了ACK报文。 TIME_WAIT状态下的TCP连接会等待2*MSL(Max Segment Lifetime,最大分段生存期,指一个TCP报文在Internet上的最长生存时间。每个具体的TCP协议实现都必须选择一个确定的MSL值,RFC 1122建议是2分钟,但BSD传统实现采用了30秒,Linux可以cat /proc/sys/net/ipv4/tcp_fin_timeout看到本机的这个值),然后即可回到CLOSED 可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(这种情况应该就是四次挥手变成三次挥手的那种情况)

CLOSING :这种状态在实际情况中应该很少见,属于一种比较罕见的例外状态。正常情况下,当一方发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING 状态表示一方发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?那就是当双方几乎在同时close()一个SOCKET的话,就出现了双方同时发送FIN报文的情况,这是就会出现CLOSING 状态,表示双方都正在关闭SOCKET连接。

CLOSE_WAIT :表示正在等待关闭。怎么理解呢?当对方close()一个SOCKET后发送FIN报文给自己,你的系统毫无疑问地将会回应一个ACK报文给对方,此时TCP连接则进入到CLOSE_WAIT状态。接下来呢,你需要检查自己是否还有数据要发送给对方,如果没有的话,那你也就可以close()这个SOCKET并发送FIN报文给对方,即关闭自己到对方这个方向的连接。有数据的话则看程序的策略,继续发送或丢弃。简单地说,当你处于CLOSE_WAIT 状态下,需要完成的事情是等待你去关闭连接。

LAST_ACK :当被动关闭的一方在发送FIN报文后,等待对方的ACK报文的时候,就处于LAST_ACK 状态。当收到对方的ACK报文后,也就可以进入到CLOSED 可用状态了。

CLOSING状态:

产生的原因是客户端和服务端同时关闭

细节描述

1.刚开始先启动服务器端,此时服务端就处于LISTEN状态。

2.接下来发起连接,也就是启动客户端,这样就建立了连接。

之所以有两个ESTABLISHED状态,是因为在我的实验中,客户端和服务器端在同一台机器上,所以两条ESTABLISHED对应一个客户端和一个服务器端。

3.之所以看不到状态SYN_SENT和SYN_RCVD,是因为这些状态存在的时间太短,故没办法显式出来。

4.假设服务器端先关闭,在这里关闭的操作是杀死服务端进程,相当于服务器端调用close

5.此时服务器端处于FIN_WAIT_2的状态,客户端处于CLOSE_WAIT的状态。

6.为什么服务器端不处于FIN_WAIT的状态呢?是因为客户端没有机会read返回0(看上面的图)

看客户端代码:

void echo_cli(int sock)
{
        char sendbuf[1024]={0};
        char recvbuf[1024]={0};
    
        while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
        {
                writen(sock,sendbuf,strlen(sendbuf));

                int ret=readline(sock,recvbuf,sizeof(recvbuf));
                if(ret==-1)
                ERR_EXIT("readline");
                else if(ret==0)
                {    
                          printf("client close\n");
                          break;
                }
                fputs(recvbuf,stdout);
                memset(sendbuf,0,sizeof(sendbuf));
                memset(recvbuf,0,sizeof(recvbuf));
        }
        close(sock);

}

客户端此时阻塞在fgets()函数,也就是阻塞在从键盘接收输入的位置,也就没有机会调用readline()函数,意味着客户端没有调用close,就不能发送一个FIN的TCP段给服务器端,服务端也就无法进入TIME_WAIT状态。

7.在客户端随便输入一个字符按回车

输入一个字符之后,就会导致fgets返回,然后就能够readline,此时就能将对方发送过来的FIN分节接收,read返回0。此时客户端break循环,执行close(sock)(看上面客户端的代码)。接着客户端就会发送FIN分节给客户端,不过现在服务端进程被杀死,看不到TIME_WAIT状态。

如果是客户端通过输入ctrl+d先关闭,那么客户端就会处于TIME_WAIT的状态,这个状态需要保留2MSL时间才会消失,如果是服务器端保留2MSL时间,这时候会导致服务器端无法重新启动,这也就是设置REUSEADDR的原因。

SIGPIPE信号

往一个已经发送FIN的套接字中写是允许的,接收到FIN仅仅代表对方不再发送数据并不代表不能发送数据给对方。

现在是发数据给对方,但是对方进程已经不存在了,就会导致TCP的重置,对方会发送RST的TCP给我们。

在收到RST段之后,如果再调用write就会产生SIGPIPE信号,对于这个信号的处理我们通常忽略即可。

展示SIGPIPE信号的产生

客户端代码

void echo_cli(int sock)
{
        char sendbuf[1024]={0};
        char recvbuf[1024]={0};
    
        while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
        {

//                writen(sock,sendbuf,strlen(sendbuf));

		//导致对方发送一个RST段过来,因为对方已经先发送了FIN
		write(sock,sendbuf,1);
		//再次写入就会导致SIGPIPE信号的产生,一旦产生这个信号,默认情况下就是终止当前进程,就没有机会再往下走了
		write(sock,sendbuf+1,strlen(sendbuf)-1);

                int ret=readline(sock,recvbuf,sizeof(recvbuf));
                if(ret==-1)
                ERR_EXIT("readline");
                else if(ret==0)
                {    
                          printf("client close\n");
                          break;
                }
                fputs(recvbuf,stdout);
                memset(sendbuf,0,sizeof(sendbuf));
                memset(recvbuf,0,sizeof(recvbuf));
        }
        close(sock);

}
void handle_sigpipe(int sig)
{
	printf("recv a sig=%d\n",sig);
}
int main(void)
{
	//用于检测确实收到一个SIGPIPE信号,此时上面说的没机会往下走的情况就不存在了。
    //因为捕捉了信号并进行了处理,所以没有将进程终止
	signal(SIGPIPE,handle_sigpipe);
//	signal(SIGPIPE,SIG_IGN);
...
}

这个13号信号就是SIGPIPE信号

发布了161 篇原创文章 · 获赞 60 · 访问量 7万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览