三、TCP C/S:连接建立后,kill 服务器进程

原创
2016/06/18 14:38
阅读数 315

1. 程序源码

http://my.oschina.net/lowkey2046/blog/693852

2. 测试方法

a. 启动服务器进程

$ ./tcpserv01 &
[1] 4717

b. 启动客户端进程,输入 hello world

客户端能接收到服务器数据

$ ./tcpcli01 127.0.0.1
hello world
hello world

c. kill 掉与客户端连接的进程

$ ps -aux | grep tcpserv01
test     4717  0.0  0.0   4224   632 pts/1    S    14:08   0:00 ./tcpserv01
test     4721  0.0  0.0   4224    84 pts/1    S    14:08   0:00 ./tcpserv01
test     4728  0.0  0.0  18500   928 pts/1    S+   14:08   0:00 grep --color=auto tcpserv01
$ kill 4721
child 4721 terminated

kill 掉与客户端相连的服务器进程,客户端程序并没有终止。

因为客户端阻塞在 fgets上,该函数正等待标准输入的数据,客户端程序还不知道服务器程序已经终止。

d. 客户端继续输入字符 terminated

terminated
str_cli: read EOF

因为客户端并不知道服务器程序已经终止,会调用 write 将字符串 'terminated' 发送给服务器。

wirte 只负责将数据写入套接字缓冲区,也就是说,wirte 返回成功,只能说明数据已经成功的写入到系统的套接字缓冲区,并不能说明对方已经接收到数据。

当客户端 read socket 时,读到了服务器终止时发送过来的 'FIN',也就是 EOF,此时客户端终止。

3. wireshark 截图

输入图片说明

a. 当 kill 掉与客户端相连接的服务器进程时,服务器会向客户端发送一个 'FIN',客户端响应一个 'ACK'。 服务器向客户端发送 'FIN' 只能说明服务器数据发送完成,并不能说明服务器不再接收数据。因此向客户端的 socket wirte 数据不会出错。

b. 当客户端向已经终止的服务器程序发送数据时,服务器会发送一个 'RST'。

4. SIGPIPE 信号

向已收到 RST 的套接字执行写操作

程序源码

修改之前的程序源码

#include <signal.h>

void sig_pipe(int signo)
{
    fprintf(stderr, "signo = %d\n", signo);
    return;
}

void str_cli(FILE *fp, int sockfd)
{
    char    sendbuf[MAXBUF], recvbuf[MAXBUF];
    int recvn;

    signal(SIGPIPE, sig_pipe);

    while (fgets(sendbuf, MAXBUF, fp) != NULL) {

        if (write(sockfd, sendbuf, 1) < 0) {
            perror("write");
            return;
        }

        sleep(1);
        // 向某个已经收到 RST 的进程继续执行写操作
        if (write(sockfd, sendbuf + 1, strlen(sendbuf+1)) < 0) {
            perror("write");
            return;
        }

        sleep(1);

        if ((recvn = read(sockfd, recvbuf, sizeof(recvbuf)-1)) < 0) {
            fprintf(stderr, "str_cli: server terminated prematurely");
        } else if(recvn == 0) {
            fprintf(stderr, "str_cli: read EOF\n");
            return;
        }
        recvbuf[recvn] = '\0';

        fputs(recvbuf, stdout);
    }
}

执行测试程序

客户端执行

$ ./tcpcli02 127.0.0.1
hello world
hello world

此时将与客户端连接的服务器进程 kill 掉。客户端继续输入:

terminated
signo = 13
write: Broken pipe

当一个进程向某个已收到 RST 的套接字执行写操作时,内核向该进程发送一个 SIGPIPE 信号。该信号的默认行为是终止进程,因此进程必须捕获它以避免不情愿的终止。

不论该进程是捕获了该信号并从信号处理函数返回,还是简单的忽略信号,写操作都将返回 EPIPE 错误。

参考资料

《UNP》

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部