您的位置:首页 > 理论基础 > 计算机网络

38-连接断开异常(服务器进程终止)

2017-04-22 15:39 309 查看
代码托管在 gitos 上,请使用下面的命令获取:

git clone https://git.oschina.net/ivan_allen/unp.git[/code] 
如果你已经 clone 过这个代码了,请使用 git pull 更新一下。

本次实验所使用的程序路径仍然是
unp/program/echo/processzombie
。processzombie 程序看起来似乎已经完美无缺了,但是做完此实验后,你仍然会发现问题。

我们的目的不是让客户端主动发起连接断开,而是由服务器主动断开。接下来再看会有什么情况发生。

1. 实验步骤及结果

在 flower 主机上启动服务器

flower $ ./echo -s -h flower


在 sun 主机上开启 tcpdump 准备抓包

sun $ sudo tcpdump -ttt -i ens33 tcp and host flower and port 8000


在 sun 主机上再开启一个终端,启动服务器

sun $ ./echo -h flower


接下来,我们在客户端键入
'hello'
,得到服务器的回射。

杀死服务器子进程,主动断开连接

flower $ ctrl z // 将服务器放到后台
^Z
[1]+ 已停止        ./echo -s -h flower
flower $ ps // 查看服务器进程
PID TTY          TIME CMD
3082 pts/0    00:00:00 bash
3363 pts/0    00:00:00 echo
3364 pts/0    00:00:00 echo
3373 pts/0    00:00:00 ps
flower $ kill -9 3364 // 杀死服务器子进程
flower $ fg // 将服务器切换到前台


具体详见图 1.

为了方便后面阅读起来更加清楚,我用 flower 指代服务器,用 sun 指代客户端,以便大家对照图。



图1 杀死 flower 子进程

当我们杀死 flower 服务器的子进程后,父进程收到子进程退出信号,并进行了回收。另一方面,我们看 tcpdump 抓取的数据包(观察最后两行)。flower 子进程退出后,给 sun 客户端发送了 FIN 报文,并正确得到了对端的 ACK. 转而进入 FIN_WAIT2 状态。

但是,我们的 sun 客户端应用层对此毫不知情,仍然阻塞在标准输入上!

接下来,在 sun 客户端键入
'world'
字符,回车发送过去



图2 sun 客户端再次键入 world 发送给对方

很不幸的是,sun 客户端并没有收到对端的回射信息,而是在 readline 的时候,读取到了对端(flower)发来的 FIN 段,返回了 0,因此打印了一行 peer closed.

为了能方便分析结果,这里贴出 sun 客户端的代码

void doClient(int sockfd) {
int nr, nw;
char buf[4096];

while(fgets(buf, 4096, stdin)) {
nw = writen(sockfd, buf, strlen(buf));
if (nw < strlen(buf)) puts("short write");

nr = readline(sockfd, buf, 4096);
if (nr == 0) {
puts("peer closed");
break;
}
else if (nr < 0) ERR_EXIT("readline");

write(STDOUT_FILENO, buf, nr);
}
}


再观察 tcpdump 的输出:



图3 tcpdump 多了 6 行输出

我们看图 3 红色框框中的第一行,这是 sun 客户端发送
'world'
字符的 TCP 报文段,没有任何异样,它对应于代码

nw = writen(sockfd, buf, strlen(buf));


虽然 sun 客户端在此前已经接收到了对端的 FIN 段,但是这只表明对端(flower)不再会发送数据过来,因此 sun 客户端此时执行 writen 并不觉得自己有什么错误。可是,flower 服务器进程此时已经不在了,一旦 flower 服务器那边收到了
'world'
报文段,立即回送 RST 段。

但是 sun 客户端的执行速度太快了,在还没有收到对端的 RST 段之前,它执行的 readline 函数已经返回,而且返回了 0,它得出的结论是对端 flower 已经关闭,于是打印 peer closed,然后退出循环,执行 close(sockfd),向对端发送 FIN 段(图 3 中红色框框的第 2 行),不过我们看到了 sun 主机连续发送了两个 FIN 段,第二个 FIN 是在第一次发送 FIN 后 44 ms 左右发出去的,实际上这是一次重传。

接下来,我们看到了 flower 连续回送了 3 个 RST 段,第一个 RST 段显然是对 sun 客户端 writen 的答复,也就是
'world'
报文的答复,后面两个 RST 是对 sun 连续两个 FIN 的答复。

2. 结果分析

上面的实验,展示了一次 TCP 的异常关闭过程,这是一种状态的,不优雅的关闭,虽然最后并未造成多大的影响。所谓优雅的关闭一个 TCP 连接,指的是经历一次完整的四次挥手的过程,而上面的实验,最后却以 RST 报文而告终。

优雅的关闭一个 TCP 连接就像和平分手,大家好聚好散。而异常关闭,就像双方分手还撕破脸皮,双方都没有好处。

当 flower 上的服务器被我们手工 kill 后,很绅士的发送了 FIN 段给客户端,并在接收到 ACK 后进入 FIN_WAIT2 状态。可是,服务器进程此时已经退出了,不能再接收新的数据了,它期待的是对端 sun 发送 FIN.

客户端在 writen(
'world'
) 后,并不会察觉到什么错误。flower 接收到了
'world'
后,自然立即回送 RST 段,因为 flower 期待的是 FIN 而不是一个普通的数据报文。

接下来 sun 执行 readline,这个时候,就看服务器回送 RST 什么时候到达了,如果在 readline 返回前 RST 到达了,readline 必然就会返回错误。在我们实验中,sun 的 readline 返回后,RST 还没到达。

3. 总结

知道为什么实验中最后一次 flower 服务器会发送 RST

思考:有没有办法让客户端在收到 FIN 端后立即能够得到通知?(提示:用我们学过的知识,比如多线程,IO 复用)

本文我们先不给出具体的解决方案,因为后面还有一个实验。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐