10 July 2017

介绍

一个 TCP 连接的正常过程是 四次挥手

最先发起关闭的一方是 client,client 发起关闭后,client 不能再向 server 发数据,而 server 还可以继续写数据给 client。当 server 也关闭了,这时候 client 还会进入 time wait 状态,这个状态存在的原因是保留一段时间让 client 继续接受由 server 发送但是还未到达的数据(server 的 write 调用并不一定表示 client 已经成功收到)。

但是,如果我们最先发起关闭的一方是 server,然后是 client 也发送 FIN 请求来关闭,那么会导致 server 进入 time-wait 状态。

server 进入 time-wait 状态的时候,应用看起来 server 是关闭状态,但实际上该 端口 已经被占用了。所以重启的时候,socket 编程的 bind 函数就会报错。
而如果是 client 先发送 FIN 请求来关闭,之后 server 再发送 FIN 的情况就不会导致 server 的端口被占用,这时候是 client 会进入 time-wait 状态

实例

 1: // client.c
 2: #include <unistd.h>
 3: #include <stdio.h>
 4: #include <sys/socket.h>
 5: #include <arpa/inet.h>
 6: #include <errno.h>
 7: #include <stdlib.h>
 8: #include <string.h>
 9: 
10: static void err_sys(char *buf) {
11:     fprintf(stderr, "%s\n", buf);
12:     exit(-1);
13: }
14: 
15: int main(int argc, char *argv[]) {
16:     const int MAXLINE = 1024;
17:     char buf[MAXLINE];
18: 
19:     int port = 8888;
20:     int server_fd;
21:     struct sockaddr_in server_addr;
22: 
23:     server_fd = socket(AF_INET, SOCK_STREAM, 0);
24:     memset(&server_addr, 0, sizeof(server_addr));
25:     server_addr.sin_family = AF_INET;
26:     server_addr.sin_port   = htons(port);
27:     inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
28: 
29:     if (connect(server_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) != 0) {
30:         err_sys("connect");
31:     }
32: 
33:     int nread_from_stdin;
34: 
35:     nread_from_stdin = read(STDIN_FILENO, buf, MAXLINE);
36:     write(server_fd, buf, nread_from_stdin);
37:     read(server_fd, buf, nread_from_stdin);
38: 
39:     close(server_fd);
40: 
41:     return 0;
42: }

client 首先从 stdin 获取输入,然后写到 server 中,再将 server 的 “回响” 读出来,关闭连接


 1: // server.c
 2: #include <unistd.h>
 3: #include <stdio.h>
 4: #include <sys/socket.h>
 5: #include <arpa/inet.h>
 6: #include <errno.h>
 7: #include <stdlib.h>
 8: #include <string.h>
 9: 
10: static void err_sys(char *buf) {
11:     fprintf(stderr, "%s\n", buf);
12:     exit(-1);
13: }
14: 
15: int main(int argc, char *argv[]) {
16:     const int MAXLINE = 1024;
17:     char buf[MAXLINE];
18: 
19:     int server_fd;
20:     int client_fd;
21:     socklen_t client_size;
22:     int port = 8888;
23: 
24:     if (argc == 2) {
25:         port = atoi(argv[1]);
26:     }
27: 
28:     struct sockaddr_in server_addr;
29:     struct sockaddr_in client_addr;
30: 
31:     server_fd = socket(AF_INET, SOCK_STREAM, 0);
32: 
33:     memset(&server_addr, 0, sizeof(server_addr));
34:     server_addr.sin_family      = AF_INET;
35:     server_addr.sin_port        = htons(port);
36:     inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr.s_addr);
37: 
38:     if (bind(server_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) != 0) {
39:         err_sys("error bind\n");
40:     }
41: 
42:     listen(server_fd, 5);
43: 
44:     client_size = sizeof(client_addr);
45:     client_fd   = accept(server_fd, (struct sockaddr *) &client_addr, &client_size);
46: 
47:     ssize_t nread_from_client;
48: 
49:     nread_from_client = read(client_fd, buf, MAXLINE);
50:     if (nread_from_client < 0 && errno != EINTR) {
51:         err_sys("read from client error");
52:     } else if (nread_from_client == 0) {
53:         printf("done\n");
54:         exit(0);
55:     } else if (nread_from_client > 0) {
56:         write(client_fd, buf, nread_from_client);
57:     }
58: 
59:     close(client_fd);
60:     close(server_fd);
61: 
62:     return 0;
63: }

server 首先读取 client 发送过来的数据,再将读到的数据发送回给 client


上面的 server 再执行完第一次之后,再次执行就会出现 bind error

分析:
就在 client 的第 37 行 client 接收 server 数据的时候(阻塞在 read 调用上),server 再执行 server 的第 56 行 的 write,server write 完就直接 close
这时候会导致 server 先关闭了,即先发送 FIN 给 client,这样会导致 server 进入 Time Wait 状态

解决方法

简单的解决就是服务器等待 client 的 FIN 发送过来,再关闭自己

// server.c
#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

static void err_sys(char *buf) {
    fprintf(stderr, "%s\n", buf);
    exit(-1);
}

int main(int argc, char *argv[]) {
    const int MAXLINE = 1024;
    char buf[MAXLINE];

    int server_fd;
    int client_fd;
    socklen_t client_size;
    int port = 8888;

    if (argc == 2) {
        port = atoi(argv[1]);
    }

    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;

    server_fd = socket(AF_INET, SOCK_STREAM, 0);

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family      = AF_INET;
    server_addr.sin_port        = htons(port);
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr.s_addr);

    if (bind(server_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) != 0) {
        err_sys("error bind\n");
    }

    listen(server_fd, 5);

    client_size = sizeof(client_addr);
    client_fd   = accept(server_fd, (struct sockaddr *) &client_addr, &client_size);

    ssize_t nread_from_client;

    nread_from_client = read(client_fd, buf, MAXLINE);
    if (nread_from_client < 0 && errno != EINTR) {
        err_sys("read from client error");
    } else if (nread_from_client == 0) {
        printf("done\n");
        exit(0);
    } else if (nread_from_client > 0) {
        write(client_fd, buf, nread_from_client);
    }

+    char fin_from_client;
+    read(client_fd, &fin_from_client, MAXLINE);

    close(client_fd);
    close(server_fd);

    return 0;
}

还可以这样

// server.c
#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

static void err_sys(char *buf) {
    fprintf(stderr, "%s\n", buf);
    exit(-1);
}

int main(int argc, char *argv[]) {
    const int MAXLINE = 1024;
    char buf[MAXLINE];

    int server_fd;
    int client_fd;
    socklen_t client_size;
    int port = 8888;

    if (argc == 2) {
        port = atoi(argv[1]);
    }

    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;

    server_fd = socket(AF_INET, SOCK_STREAM, 0);

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family      = AF_INET;
    server_addr.sin_port        = htons(port);
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr.s_addr);

    if (bind(server_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) != 0) {
        err_sys("error bind\n");
    }

    listen(server_fd, 5);

    client_size = sizeof(client_addr);
    client_fd   = accept(server_fd, (struct sockaddr *) &client_addr, &client_size);

    ssize_t nread_from_client;

+    while (1) {
+        nread_from_client = read(client_fd, buf, MAXLINE);
+        if (nread_from_client < 0 && errno != EINTR) {
+            err_sys("read from client error");
+        } else if (nread_from_client == 0) {
+            printf("done\n");
+            exit(0);
+        } else if (nread_from_client > 0) {
+            write(client_fd, buf, nread_from_client);
+        }
+    }

    close(client_fd);
    close(server_fd);

    return 0;
}