网络编程:TCP故障模式
故障模式总结
异常情况可归结为两大类:

第一类,是对端无FIN包发送出来的情况;第二类是对端有FIN包发出来
对端无FIN包发送出
- 网络终端造成对端无FIN包
很多原因都会造成网络中断,这种情况,TCP程序并不能及时感知异常信息。除非网络中的其他设备,如路由器发送出一条ICMP报文,说明目的网络或主机不可达,此时,通过read或write调用就会返回unreachable的错误
在没有ICMP报文的情况下,TCP程序并不能感应到连接异常。如果程序是阻塞在read调用上,程序无法从异常中恢复,可以通过read操作设置超时来解决
如果程序先调用了write操作发送了一段数据流,接下来阻塞在read调用上,结果又是另一种情况。Linux系统的TCP协议栈会不断尝试将发送缓冲区的数据发送出去,大概在重传12次,合计时间约9分钟之后,协议栈会标识连接异常,此时,阻塞的read调用会返回一个TIMEOUT的错误信息,如果程序还继续往这条连接上写数据,写操作会立即失败,返回一个SIGPIPE信号给应用程序
- 系统崩溃造成的对端无FIN包
当系统突然奔溃,如断电,网络连接来不及发出任何东西,和通过应用调用杀死应用程序非常不同的是,没有任何FIN包被发送出来。
这种情况和网络中端造成的结果非常类似,在没有ICMP报文的情况下,TCP程序只能通过read和write调用的到网络连接异常的消息,超时错误是一种常见的结果。
不过还有一种情况需要考虑,那就是系统在崩溃之后又重启,当重传的 TCP 分组到达重启后的系统,由于系统中没有该 TCP 分组对应的连接数据,系统会返回一个 RST 重置分节,TCP 程序通过 read 或 write 调用可以分别对 RST 进行错误处理。如果是阻塞的 read 调用,会立即返回一个错误,错误信息为连接重置(Connection Reset)。如果是一次 write 操作,也会立即失败,应用程序会被返回一个 SIGPIPE 信号。
对端有FIN包发出
对端如果有 FIN 包发出,可能的场景是对端调用了** close 或 shutdown** 显式地关闭了连接,也可能是对端应用程序崩溃,操作系统内核代为清理所发出的。从应用程序角度上看,无法区分是哪种情形。
阻塞的 read 操作在完成正常接收的数据读取之后,FIN 包会通过返回一个 EOF 来完成通知,此时,read 调用返回值为 0。这里强调一点,收到 FIN 包之后 read 操作不会立即返回。你可以这样理解,收到 FIN 包相当于往接收缓冲区里放置了一个 EOF 符号,之前已经在接收缓冲区的有效数据不会受到影响。
服务端程序:
//服务端程序
int main(int argc, char **argv) {
int connfd;
char buf[1024];
connfd = tcp_server(SERV_PORT);
for (;;) {
int n = read(connfd, buf, 1024);
if (n < 0) {
error(1, errno, "error read");
} else if (n == 0) {
error(1, 0, "client closed \n");
}
sleep(5);
int write_nc = send(connfd, buf, n, 0);
printf("send bytes: %zu \n", write_nc);
if (write_nc < 0) {
error(1, errno, "error write");
}
}
exit(0);
}
服务端程序是一个简单的应答程序,在收到数据流之后回显给客户端,在此之前,休眠 5 秒,以便完成后面的实验验证。
客户端程序从标准输入读入,将读入的字符串传输给服务器端:
客户端程序:
//客户端程序
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: reliable_client01 <IPaddress>");
}
int socket_fd = tcp_client(argv[1], SERV_PORT);
char buf[128];
int len;
int rc;
while (fgets(buf, sizeof(buf), stdin) != NULL) {
len = strlen(buf);
rc = send(socket_fd, buf, len, 0);
if (rc < 0)
error(1, errno, "write failed");
rc = read(socket_fd, buf, sizeof(buf));
if (rc < 0)
error(1, errno, "read failed");
else if (rc == 0)
error(1, 0, "peer connection closed\n");
else
fputs(buf, stdout);
}
exit(0);
}
- read直接感知FIN包
依次启动服务器端和客户端程序,在客户端输入 good 字符之后,迅速结束掉服务器端程序,这里需要赶在服务器端从睡眠中苏醒之前杀死服务器程序。
屏幕上打印出:peer connection closed。客户端程序正常退出
$./reliable_client01 127.0.0.1
$ good
$ peer connection closed
这说明客户端程序通过 read 调用,感知到了服务端发送的 FIN 包,于是正常退出了客户端程序。

- 通过 write 产生 RST,read 调用感知 RST
们仍然依次启动服务器端和客户端程序,在客户端输入 bad 字符之后,等待一段时间,直到客户端正确显示了服务端的回应“bad”字符之后,再杀死服务器程序。客户端再次输入 bad2,这时屏幕上打印出”peer connection closed“。
$./reliable_client01 127.0.0.1
$bad
$bad
$bad2
$peer connection closed

在很多书籍和文章中,对这个程序的解读是,收到 FIN 包的客户端继续合法地向服务器端发送数据,服务器端在无法定位该 TCP 连接信息的情况下,发送了 RST 信息,当程序调用 read 操作时,内核会将 RST 错误信息通知给应用程序。这是一个典型的 write 操作造成异常,再通过 read 操作来感知异常的样例。
*向一个已关闭连接连续写,最终导致 SIGPIPE
为模拟该过程,对服务端程序和客户端程序进行了修改:
服务端:
nt main(int argc, char **argv) {
int connfd;
char buf[1024];
int time = 0;
connfd = tcp_server(SERV_PORT);
while (1) {
int n = read(connfd, buf, 1024);
if (n < 0) {
error(1, errno, "error read");
} else if (n == 0) {
error(1, 0, "client closed \n");
}
time++;
fprintf(stdout, "1K read for %d \n", time);
usleep(1000);
}
exit(0);
}
服务器端每次读取 1K 数据后休眠 1 秒,以模拟处理数据的过程。
客户端:
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: reliable_client02 <IPaddress>");
}
int socket_fd = tcp_client(argv[1], SERV_PORT);
signal(SIGPIPE, SIG_IGN);
char *msg = "network programming";
ssize_t n_written;
int count = 10000000;
while (count > 0) {
n_written = send(socket_fd, msg, strlen(msg), 0);
fprintf(stdout, "send into buffer %ld \n", n_written);
if (n_written <= 0) {
error(1, errno, "send error");
return -1;
}
count--;
}
return 0;
}
果在服务端读取数据并处理过程中,突然杀死服务器进程,我们会看到客户端很快也会退出,并在屏幕上打印出“Connection reset by peer”的提示。
$./reliable_client02 127.0.0.1
$send into buffer 5917291
$send into buffer -1
$send: Connection reset by peer
这是因为服务端程序被杀死之后,操作系统内核会做一些清理的事情,为这个套接字发送一个 FIN 包,但是,客户端在收到 FIN 包之后,没有 read 操作,还是会继续往这个套接字写入数据。这是因为根据 TCP 协议,连接是双向的,收到对方的 FIN 包只意味着对方不会再发送任何消息。 在一个双方正常关闭的流程中,收到 FIN 包的一端将剩余数据发送给对面(通过一次或多次 write),然后关闭套接字。
当数据到达服务器端时,操作系统内核发现这是一个指向关闭的套接字,会再次向客户端发送一个 RST 包,对于发送端而言如果此时再执行 write 操作,立即会返回一个 RST 错误信息。
全过程的描述图:

小结
因为可能故障的存在,TCP并不是那么的“可靠”。故障分为两大类,一类是对端无 FIN 包,需要通过巡检或超时来发现;另一类是对端有 FIN 包发出,需要通过增强 read 或 write 操作的异常处理,帮助我们发现此类异常。
网络编程:TCP故障模式的更多相关文章
- C#网络编程TCP通信实例程序简单设计
C#网络编程TCP通信实例程序简单设计 采用自带 TcpClient和TcpListener设计一个Tcp通信的例子 只实现了TCP通信 通信程序截图: 压力测试服务端截图: 俩个客户端链接服务端测试 ...
- Socket网络编程(TCP/IP/端口/类)和实例
Socket网络编程(TCP/IP/端口/类)和实例 原文:C# Socket网络编程精华篇 转自:微冷的雨 我们在讲解Socket编程前,先看几个和Socket编程紧密相关的概念: TCP/IP层次 ...
- 网络编程——TCP协议、UDP协议、socket套接字、粘包问题以及解决方法
网络编程--TCP协议.UDP协议.socket套接字.粘包问题以及解决方法 TCP协议(流式协议) 当应用程序想通过TCP协议实现远程通信时,彼此之间必须先建立双向通信通道,基于该双向通道实现数 ...
- Socket网络编程-TCP编程
Socket网络编程-TCP编程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.socket介绍 1>.TCP/IP协议 2>.跨网络的主机间通讯 在建立通信连接的 ...
- GO语言练习:网络编程 TCP 示例
1.代码 2.编译及运行 1.网络编程 TCP 示例 simplehttp.go 代码 package main import ( "net" "os" &qu ...
- 网络编程TCP协议-聊天室
网络编程TCP协议-聊天室(客户端与服务端的交互); <span style="font-size:18px;">1.客户端发数据到服务端.</span> ...
- 32.网络编程TCP/UDP服务
网络编程TCP: 服务器端口了解: port:0~65535 web服务:80 邮箱服务:556 0~1024:为服务默认的公认端口,一般我们不能用 套接字:socket socket作用 ip:po ...
- 网络编程TCP/IP详解
网络编程TCP/IP详解 1. 网络通信 中继器:信号放大器 集线器(hub):是中继器的一种形式,区别在于集线器能够提供多端口服务,多口中继器,每个数据包的发送都是以广播的形式进行的,容易阻塞网络. ...
- 网络编程TCP总结及实践-C语言
网络变成首先要注意IP和port的转换,如今电脑基本上是主机字节序,存储依照小端方式,而在网络中传输统一使用大端方式,所以网络变成首先要注意字节序的转换. 一个经常使用的ip转换程序的实现: #inc ...
- 36 - 网络编程-TCP编程
目录 1 概述 2 TCP/IP协议基础 3 TCP编程 3.1 通信流程 3.2 构建服务端 3.3 构建客户端 3.4 常用方法 3.4.1 makefile方法 3.5 socket交互 3.4 ...
随机推荐
- Docker 的基本概念和优势,以及在应用程序开发中的实际应用
Docker是一个开源的容器化平台,它可以将应用程序及其所有依赖关系打包为一个独立的容器,从而实现应用程序的快速部署.可移植性和可扩展性. Docker的基本概念包括以下几个方面: 镜像(Image) ...
- Java进阶 - [1-5] 集合容器
ArrayList add 1.先确认是否需要扩容,如果需要,则进行扩容操作ensureExplicitCapacity. 2.进行赋值 elementData[size++] = e; 扩容 1.如 ...
- Zookeeper - [04] 分布式安装部署
一.集群规划 序号 主机名 JDK Zookeeper 1 node01 ○ ○ 2 node02 ○ ○ 3 node03 ○ ○ 二.安装部署 1.将zookeeper安装包解压到合适的目录,如/ ...
- 【BUUCTF】Blacklist
[BUUCTF]Blacklist (SQL注入) 题目来源 收录于:BUUCTF GYCTF2020 题目描述 纯粹的SQL注入题 当触发黑名单时返回如下 过滤了以下关键字 set prepare ...
- AWVS安装使用
AWVS安装使用 1.双击exe文件,然后点击下一步. 2.选择我接受,然后下一步. 3.选择路径(我选择的默认路径)然后下一步. 4.还是下一步. 5.设置邮箱,用户名密码,用户名12345678@ ...
- 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
前言 在人工智能技术日新月异的今天,DeepSeek-R1模型以其卓越的性能和广泛的应用场景,成为了众多用户心中的明星模型.它不仅具备强大的日常写作.翻译.问答等基础功能,更引入了独特的深度思考模式, ...
- Windows 终端远程连接 Ubuntu
博客地址:https://www.cnblogs.com/zylyehuo/ 查看 ip 的方式参考以下链接 Ubuntu 下查看 ip - zylyehuo - 博客园 在 windows 终端中执 ...
- arthas定位接口的耗时排查问题
下载Arthas的jar包 https://arthas.aliyun.com/ 上传jar包到你的服务器启动 启动 arthas 在命令行下面执行(使用和目标进程一致的用户启动,否则可能 attac ...
- Tomcat之——宕机自动重启和每日定时启动tomcat
在项目后期维护中会遇到这样的情况,tomcat在内存溢出的时候就出现死机的情况和遇到长时间不响应,需要人工手动关闭和重启服务,针对这样的突发情况,希望程序能自动处理问题而不需要人工关于,所以才有了目前 ...
- linux下nginx常用命令
1.查找nginx路径:whereis nginx2.启动 service nginx start3.查看Nginx的版本号:nginx -V4.停止 nginx -s stop5.退出 nginx ...