故障模式总结

异常情况可归结为两大类:



第一类,是对端无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故障模式的更多相关文章

  1. C#网络编程TCP通信实例程序简单设计

    C#网络编程TCP通信实例程序简单设计 采用自带 TcpClient和TcpListener设计一个Tcp通信的例子 只实现了TCP通信 通信程序截图: 压力测试服务端截图: 俩个客户端链接服务端测试 ...

  2. Socket网络编程(TCP/IP/端口/类)和实例

    Socket网络编程(TCP/IP/端口/类)和实例 原文:C# Socket网络编程精华篇 转自:微冷的雨 我们在讲解Socket编程前,先看几个和Socket编程紧密相关的概念: TCP/IP层次 ...

  3. 网络编程——TCP协议、UDP协议、socket套接字、粘包问题以及解决方法

    网络编程--TCP协议.UDP协议.socket套接字.粘包问题以及解决方法 TCP协议(流式协议) ​ 当应用程序想通过TCP协议实现远程通信时,彼此之间必须先建立双向通信通道,基于该双向通道实现数 ...

  4. Socket网络编程-TCP编程

    Socket网络编程-TCP编程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.socket介绍 1>.TCP/IP协议 2>.跨网络的主机间通讯 在建立通信连接的 ...

  5. GO语言练习:网络编程 TCP 示例

    1.代码 2.编译及运行 1.网络编程 TCP 示例 simplehttp.go 代码 package main import ( "net" "os" &qu ...

  6. 网络编程TCP协议-聊天室

    网络编程TCP协议-聊天室(客户端与服务端的交互); <span style="font-size:18px;">1.客户端发数据到服务端.</span> ...

  7. 32.网络编程TCP/UDP服务

    网络编程TCP: 服务器端口了解: port:0~65535 web服务:80 邮箱服务:556 0~1024:为服务默认的公认端口,一般我们不能用 套接字:socket socket作用 ip:po ...

  8. 网络编程TCP/IP详解

    网络编程TCP/IP详解 1. 网络通信 中继器:信号放大器 集线器(hub):是中继器的一种形式,区别在于集线器能够提供多端口服务,多口中继器,每个数据包的发送都是以广播的形式进行的,容易阻塞网络. ...

  9. 网络编程TCP总结及实践-C语言

    网络变成首先要注意IP和port的转换,如今电脑基本上是主机字节序,存储依照小端方式,而在网络中传输统一使用大端方式,所以网络变成首先要注意字节序的转换. 一个经常使用的ip转换程序的实现: #inc ...

  10. 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 ...

随机推荐

  1. Deepseek学习随笔(11)--- 普通人如何抓住DeepSeek红利(附网盘链接)

    一.文档简介 这个文档是清华大学新闻与传播学院新媒体研究中心发布的<普通人如何抓住DeepSeek红利>,该文件详细介绍了DeepSeek的功能.应用场景.使用技巧以及如何通过提示词驱动提 ...

  2. Vue3组件通信全攻略:多种方式详解+实战场景,轻松玩转复杂数据流!

    一.组件通信为何如此重要? 在大型Vue项目中,组件通信如同神经网络般贯穿整个应用.良好的通信机制能: 实现组件解耦 提升代码可维护性 构建清晰数据流 支撑复杂业务场景 二.父子组件通信:核心通信模式 ...

  3. 【论文随笔】推荐系统综述_推荐模型、推荐技术与应用领域(A Survey of Recommendation Systems_ Recommendation Models, Techniques, and Application Fields)

    前言 今天读的论文为一篇于2022年1月3日发表的论文,这篇文章是关于推荐系统的综述,主要研究了推荐系统在不同服务领域的应用趋势,包括推荐模型.技术和应用领域.通过分析2010年至2021年间发表的顶 ...

  4. Vulnhub-FristiLeaks_1.3

    一.靶机搭建 选择扫描虚拟机 选择路径即可 二.信息收集 靶机信息 产品名称:Fristileaks 1.3 作者:Ar0xA 发布日期: 2015 年 12 月 14 日 目标:获取root(uid ...

  5. JMeter 性能优化

    Jmeter 性能优化:(3优化 + 1补充)   1.在 jmx 文件中 Disable 所有的结果输出,如: View Results Tree / Graph Results / Aggrega ...

  6. 使用Istio灰度发布

    目录 灰度发布 1. Istio 1.1 Istio介绍 1.2 Istio是如何工作的 2. 安装Istio 2.1 环境 2.2 得到二进制文件 2.3 安装istio 3. 部署bookinfo ...

  7. PIL或Pillow学习2

    接着学习下Pillow常用方法: PIL_test1.py : ''' 9, Pillow图像降噪处理 由于成像设备.传输媒介等因素的影响,图像总会或多或少的存在一些不必要的干扰信息,我们将这些干扰信 ...

  8. 项目管理协作工具对比:PingCode vs Leangoo

    多语言适配能力 在全球化协作场景下,多语言支持成为跨国团队的硬性指标.PingCode目前仅支持中文界面,对于涉及多国语言协作的团队存在使用局限.对比Leangoo提供中英文双语界面切换功能,可满足基 ...

  9. 查看oracle数据库编码格式;ORACLE数据库NLS_CHARACTERSET和NLS_NCHAR_CHARACTERSET区别

    查看Oracle数据库字符编码格式得方法,有以下两种,第二种方法有注释,第一种没有Select * from nls_database_parameter;Select * from sys.prop ...

  10. oracle的各版本的名称

    我最早接触的是oracle的版本8那个时候是8i i是internet后来是9i然后到10,就是版本10g g是grid的意思然后是11g然后12就变成了C,就是12c c是cloud的意思然后后面的 ...