曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 5103|回复: 0
打印 上一主题 下一主题

捕捉 FIN_WAIT_1,FIN_WAIT_2 和TIME_WAIT 3个阶段

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2012-11-8 13:05:08 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
FIN_WAIT_1FIN_WAIT_2TIME_WAIT 是 TCP 连接中产生的几个状态,一般情况下,可以认为它们发生在主动关闭连接的客户端身上。

先假设客户端和服务器已经建立连接,即双方处于 ESTABLISHED 状态。

现在,客户端主动关闭对服务器的连接(如在客户端程序里调用 close() ),此时客户端向服务器发送 FIN 包,于是它就进入了 FIN_WAIT_1 这个阶段,目的是等待服务器的确认包。

接着,服务器发来 ACK 确认包,客户端收到后就会结束 FIN_WAIT_1 这个阶段,转而进入 FIN_WAIT_2 这个阶段,目的是等待服务器发来关闭请求(即服务器也向客户端发来 FIN 包)。

再接着,服务器发来了 FIN 包,同时服务器进入了 CLOSE_WAIT 状态,也就是服务器在等待客户端的应答,如果收到应答,那么它就会关闭连接并进入 CLOSED 状态。

现在,假设客户端已经收到了来自服务器的 FIN 包,那么客户端结束 FIN_WAIT_2 状态,从而进入 TIME_WAIT 状态,接着它向服务器发送一个应答,服务器收到后,结束之前的 CLOSE_WAIT 状态,从而完全关闭了先前的连接。

需要注意的是,服务器关闭后,客户端仍然处于 TIME_WAIT 状态 --- 它需要等待 2MSL 时间后才能结束。MSL 全称是 Maximum segment lifetime ,即“最长分节声明期” ,该值在 Linux 上一般为 60 秒:
$ cat /proc/sys/net/ipv4/tcp_fin_timeout
60

下面通过一个客户端程序和一个服务端程序,以及 netstat 工具进行这 3 种状态的捕捉。

服务器程序:
[C++] 纯文本查看 复制代码
#include <stdio.h>                                                                                                                                                    
#include <stdlib.h>                                                                                                                                                   
#include <string.h>                                                                                                                                                   
#include <errno.h>                                                                                                                                                    
#include <netinet/in.h>                                                                                                                                               
#include <arpa/inet.h>                                                                                                                                                
#include <sys/types.h>                                                                                                                                                
#include <sys/socket.h>                                                                                                                                               

void str_echo(int sockfd)
{
        int error;
        ssize_t n;
        char buf[1024];

again:
        while ( (n = read(sockfd, buf, 1024)) > 0)
                write(sockfd, buf, n);  /* Dend back to client */

        if (n < 0 && errno == EINTR)
                goto again;
        else if (n < 0) {
                fprintf (stderr, "Read error");
                exit (EXIT_FAILURE);
        }
}


int main(int argc, char **argv)
{
        int listenfd, connfd;
        int clilen;
        pid_t childpid;

        struct sockaddr_in servaddr, cliaddr;

        listenfd = socket(AF_INET, SOCK_STREAM, 0);

        bzero(&servaddr, sizeof(servaddr));

        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(2013);
        inet_aton("192.168.1.108", &servaddr.sin_addr);

        bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

        listen(listenfd, 5);

        for (; ;) {
                clilen = sizeof(cliaddr);
                connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
                if ( (childpid = fork()) == 0) {
                        close(listenfd);
                        str_echo(connfd);
                        sleep(2);
                        exit(0);
                }
                close(connfd);  /* parent closes connected socket */
        }
}

服务器端绑定在 192.168.1.108 这个 IP 上,并使用 2013 这个端口。服务器的主要作用是回射(echo)消息给客户端。当客户端连接服务器时,服务器会 fork 出一个子进程来处理这个连接。

客户端代码:
[C++] 纯文本查看 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define MAXLINE 1024

static int      read_cnt;
static char     *read_ptr;
static char     read_buf[MAXLINE];

static ssize_t
my_read(int fd, char *ptr)
{

        if (read_cnt <= 0) {
again:
                if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
                        if (errno == EINTR)
                                goto again;
                        return(-1);
                } else if (read_cnt == 0)
                        return(0);
                read_ptr = read_buf;
        }

        read_cnt--;
        *ptr = *read_ptr++;
        return(1);
}

ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
        ssize_t n, rc;
        char    c, *ptr;

        ptr = vptr;
        for (n = 1; n < maxlen; n++) {
                if ( (rc = my_read(fd, &c)) == 1) {
                        *ptr++ = c;
                        if (c == '\n')
                                break;  /* newline is stored, like fgets() */
                } else if (rc == 0) {
                        *ptr = 0;
                        return(n - 1);  /* EOF, n - 1 bytes were read */
                } else
                        return(-1);             /* error, errno set by read() */
        }

        *ptr = 0;       /* null terminate like fgets() */
        return(n);
}

ssize_t
readlinebuf(void **vptrptr)
{
        if (read_cnt)
                *vptrptr = read_ptr;
        return(read_cnt);
}
/* end readline */

ssize_t
Readline(int fd, void *ptr, size_t maxlen)
{
        ssize_t         n;

        if ( (n = readline(fd, ptr, maxlen)) < 0) {
                printf("readline error");
                exit(EXIT_FAILURE);
        }
        return(n);
}

void
Fputs(const char *ptr, FILE *stream)
{
        if (fputs(ptr, stream) == EOF) {
                perror("fputs error");
                exit(EXIT_FAILURE);
        }
}

void str_cli(FILE *fp, int sockfd)
{
        char sendline[MAXLINE], recvline[MAXLINE];

        while (fgets(sendline, MAXLINE, fp) != NULL) {
                write(sockfd, sendline, strlen(sendline));

                if (Readline(sockfd, recvline, MAXLINE) == 0) {
                        printf ("str_cli: server terminated prematurely");
                        exit(EXIT_FAILURE);
                }

                Fputs(recvline, stdout);
        }
}

int main(int argc, char **argv)
{
        int sockfd;
        struct sockaddr_in servaddr;

        if (argc != 2) {
                printf ("usage: tcpcli <IP>");
                exit(EXIT_FAILURE);
        }

        sockfd = socket(AF_INET, SOCK_STREAM, 0);

        bzero(&servaddr, sizeof(servaddr));

        servaddr.sin_family = AF_INET;

        inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

        servaddr.sin_port = htons(2013);

        connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

        str_cli(stdin, sockfd);

        exit(0);
}

之所以使用上面的服务端代码作为例子,是因为一般正式的应用中,一旦客户端主动关闭请求时,服务器就会马上回应,因此无法用 netstat 命令来捕捉到 FIN_WAIT_1 这个状态。因此,上面的服务端程序里的子进程部分代码的exit(0); 语句前特别休眠了了 2 秒,然后再子进程在退出。这样一来,客户端就会等待 2 秒后才会收到服务器的响应,从而才进入 FIN_WAIT_2 这个状态。

为了直观的展现捕捉这 3 个状态的情况,使用了下面的脚本:
[Bash shell] 纯文本查看 复制代码
#!/bin/sh

touch tmp.txt

while [ 1 ]
do
    
    wait1=`netstat -tulna |grep "FIN_WAIT1"`
    wait2=`netstat -tulna |grep "FIN_WAIT2"`
    twait=`netstat -tulna |grep "TIME_WAIT"`
    if [[ -z $wait1 && -z $wait2 && $twait ]]
    then
        continue
    else
        chkw1=`cat tmp.txt |grep "FIN_WAIT1"`
        chkw2=`cat tmp.txt |grep "FIN_WAIT2"`

        if [[ $chkw1 ]]
        then
            if [[ $chkw2 ]]
            then
                if [[ $twait ]]
                then
                    echo $twait >> tmp.txt
                    break
                fi
            elif [[ $wait2 ]]
            then
                echo $wait2 >> tmp.txt
                continue
            else
                continue
            fi
        
        elif [[ $wait1 ]]
        then
            echo $wait1 >> tmp.txt
            continue
        fi                
            
    fi        
done


可以先运行服务器端程序,然后再运行上面的脚本(在客户端主机上运行),接着使用客户端进行连接,在连接建立后,按下 Ctrl + d 组合键,即希望结束客户端。这样,脚本就能将上面所述的 3 个状态捕捉下来了(为了捕捉结果的简洁,脚本对每个状态只录入一次):
[beyes@beyes tcpstate]$ cat tmp.txt
tcp 0 1 192.168.1.109:36150 192.168.1.108:2013 FIN_WAIT1
tcp 0 0 192.168.1.109:36150 192.168.1.108:2013 FIN_WAIT2
tcp 0 0 192.168.1.109:36150 192.168.1.108:2013 TIME_WAIT
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-4 20:03 , Processed in 0.067270 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表