34-异常处理(accept 返回前连接中止)
来源:互联网 发布:云点菜系统源码 编辑:程序博客网 时间:2024/06/06 09:46
网络编程的难度在于异常状况的处理。
在前面学习 TCP 协议的时候,我们就分析过各种连接异常,断开异常等等,大家要把各种情况烂记于心。本文我们探讨一种比较特殊的情况,即客户端连接建立成功后(进入 ESTABLISHED 状态),立即关闭连接退出。而此时服务器中的 accept 函数还没调用或者还没有返回。
1. 实验代码
1.1 代码托管地址
git clone https://git.oschina.net/ivan_allen/unp.git
如果你已经 clone 过这个代码了,请使用 git pull
更新一下。
1.2 程序路径
unp/program/echo/exception_accept
1.3 代码说明
这一份程序主要基于 ehch/basic
进行了少量的修改,大家在阅读后面解释的时候,记得对照着源代码看。修改内容主要有以下几个地方:
- 添加了一个命令行参数选项
-r
如果指定该选项,表示客户端以异常方式关闭连接,关闭时直接发送 RST 段给对方;服务器会在 accept 前等待 10 秒。'-r'
选项对应程序中 Options 的 isLinger 字段。
- accept 函数调用处修改为:
// 模拟异常,在 accept 前收到 RST 报文if (g_option.isLinger) sleep(10);cliaddrlen = sizeof cliaddr;sockfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddrlen);if (sockfd < 0) { // 添加了一行错误处理,如果在 accept 前收到 RST,可能会出现此错误,这依赖于操作系统实现。 if (errno == ECONNABORTED) puts("accept: connect reset by peer"); ERR_EXIT("accept");}
- 服务器 doServer 少量修改
else if (nr < 0) { // 如果 readline 返回小于 0,判断是否是因为收到 RST if (errno == ECONNRESET) { puts("readline: reset by peer"); break; } ERR_EXIT("readline");}
- 客户端修改为以下代码
void client_routine() { // 省略无关内容 // 创建 linger 对象 struct linger lgr; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) ERR_EXIT("socket"); // 如果命令行指定了 '-r' 选项,就为套接字打开 SO_LINGER 选项 // 这个知识点由于还没讲到,所以大家现在就认为只要执行了下面这段程序,客户端在 close 的时候不是发送 FIN,而是 RST. if (g_option.isLinger) { lgr.l_onoff = 1; lgr.l_linger = 0; ret = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &lgr, sizeof lgr); if (ret < 0) ERR_EXIT("set linger"); } ret = connect(sockfd, (struct sockaddr*)&servaddr, sizeof servaddr); if (ret < 0) ERR_EXIT("connect"); // 如果命令行指定了 '-r' 选项,测试连接成功后立即发送 RST if (g_option.isLinger) { puts("connect successful, now exiting..."); close(sockfd); return; } // ...}
2. 实验步骤
- 在 sun 主机上,打开 Linux 的抓包工具 tcpdump,这个工具之前没有教大家使用过,因为之前我们一直用的 OmniPeek,它比较适合初学者。现在我们已经算是半个入门者了,在 Linux 下使用 tcpdump 压力不大。
/* * sudo tcpdump, 表示需要 root 权限运行 * (tcp) [-ttt] [-i ens33] and (host sun) and (port 8000) * and 表示使用与的方式进行过滤 * tcp 表示只抓取 tcp 包 * [-ttt] [-i ens33] 是可选项,意思就是可以不写 * -i ens33 表示使用 ens33 这个网卡。如何查看你的网卡名称?使用 ifconfig 命令就能看见。 * host sun 表示只抓取 sun 这台机器上的数据包,你也可以使用 ip 地址而不是主机名 * port 8000 表示只抓取目的端口或源端口 8000 上的数据包 */$ sudo tcpdump tcp -ttt -i ens33 and host sun and port 8000
- 在 sun 主机上打开服务器,记得打开
'-r'
选项
$ ./echo -s -h sun -r
- 在 flower 主机上打开客户端,记得打开
'-r'
选项
$ ./echo -h sun -r
3. 实验结果
- 服务器
图1 服务器在 readline 处收到异常
- 客户端
图2 客户端 connect 成功后立即发送 RST
- tcpdump
图3 sun 主机(服务器端)上抓取的数据
- 结果分析
我使用的 Linux 内核版本是 3.10,CentOS 7。很遗憾的是,并没有在 accept 函数处捕捉到异常。反而程序在第一次 readline 的时候,返回了错误。
先来看看图 3,我简化一下:
// 左侧时间表示时间差,距离上一个报文多久后发送的(1) 0 ms: flower -> sun: [S], seq(2) 0.127 ms: sun -> flower: [S], seq, ack(3) 11.55 ms: flower -> sun: [.], ack(4) 0.065 ms: flower -> sun: [R], seq
可以看到,flower 主机(客户端)在三次握手完成后,立即发送了一个 RST 段(4 号数据包)。而此时,服务器还未执行 accept(正处于 10 s 等待中),sleep 返回后,立即 accept,不幸的是,accept 成功返回了!!!服务器并没有因为收到了 RST 段让 accept 报错。
实际上,这是由操作系统实现来决定的,有些操作系统可能在 accept 时,悄无声息的把这个连接给 kill 掉,有些可能会让 accept 返回 ECONNABORTED。man 手册上给出的解释是:不同的 Linux 内核也可能会返回 ECONNABORTED。
不过,服务器在接收到 RST 后,在 readline 时做出了响应,它返回了一个 ECONNRESET 错误。
4. 总结
- 掌握服务器接收到 RST 时,accept 和 read 的行为。
- 34-异常处理(accept 返回前连接中止)
- TCP异常处理(accept返回前连接中止)与SO_LINGER选项
- 在accept返回之前,连接终止
- android 异常处理--java.io.IOException: 您的主机中的软件中止了一个已建立的连接
- android 异常处理--java.io.IOException: 您的主机中的软件中止了一个已建立的连接
- ajax返回异常处理
- TCP accept返回的socket,服务端TCP连接数限制
- 利用accept函数来处理客户端请求的连接
- UNIX网络编程学习(10)续--处理accept返回EINTR错误的TCP服务器程序最终(正确)版本
- JDBC连接异常处理
- Response.Redirect 与 异常(线程正在中止)
- 关于“正在中止线程”异常的部分解决方案(一)
- C# Thread.Abort()出现异常“正在中止线程”
- win8下联通3G网卡使用异常:连接中止+DNS解析错误
- 华为e1750 3g上网卡连接被中止(搞定)
- TCP连接的建立与中止(非原创)
- 关于Response.redirect和Response.End出现线程中止异常的处理
- 关于Response.redirect和Response.End出现线程中止异常的处理
- xgboost入门以及windows下安装及使用二
- hdu 3033 I love sneakers! 分组背包
- C++ exception类
- Cocoapod的安装和使用
- 二叉树的后序遍历
- 34-异常处理(accept 返回前连接中止)
- c#中向ListView中添加一行数据的一种简单方法
- tomcat修改jsessionid在cookie中的名称
- 用Maven插件生成Mybatis代码
- PHP GC(garbage ccontroller)垃圾回收机制(1)
- 单例静态内部类实现【单例】
- Git3--工作区和暂存区
- dp——洛谷P2679子串
- 操作系统概论