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

来源:互联网 发布:武大樱花 知乎 编辑:程序博客网 时间:2024/05/29 17:05

代码托管在 gitos 上,请使用下面的命令获取:

git clone https://git.oschina.net/ivan_allen/unp.git

如果你已经 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 flowerflower $ 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 psflower $ 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 复用)

本文我们先不给出具体的解决方案,因为后面还有一个实验。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 孕妇被降职降薪怎么办 公司降职降薪员工不同意怎么办 企业因为经营不善要降薪该怎么办 调岗不降薪我该怎么办? 怀孕后强制调岗怎么办 有限公司法人变更后债务怎么办 有限公司法人跑路债务怎么办 网上买票身份信息待核验怎么办 微信买票身份核验失败怎么办 买高铁票待核验怎么办 网上购票身份待核验怎么办 b站稿件版权原因怎么办 已离职老板打电话说账有问题怎么办 开到应急刹车道怎么办 自动挡的车刹车失灵怎么办 自动挡的车如果刹车失灵怎么办 手动挡汽车刹车失灵怎么办 车辆没有年检出了交通事故怎么办 跟着大货车闯了红灯怎么办 在万家金服买的电子产品坏了怎么办 汽车被油笔画了怎么办? 挂到别人车跑了怎么办 浪琴手表保修卡掉了怎么办 事故车辆维修和报废怎么办 4.2货车拉缸了怎么办 工作中与同事发生矛盾怎么办 和领导关系闹僵怎么办 内倒窗户卡住了怎么办 支付宝存在安全风险怎么办 地铁车站空调坏了怎么办 面试防汛值班发生灾情你怎么办 怀孕上班路途太远怎么办 硕士错过校招应该怎么办 收银员收多了钱怎么办 商铺贷款批不了怎么办 铁路局的门面乱收房租怎么办 酒店夜审房费多过怎么办 夜审房价录多了怎么办 做工地拿不到钱怎么办 赢了官司拿不到钱怎么办 工地上拿不到钱怎么办