TCP中的两类故障模式

简介: 【4月更文挑战第10天】故障分为两大类,一类是对端无 FIN 包,需要通过巡检或超时来发现;另一类是对端有 FIN 包发出,需要通过增强 read 或 write 操作的异常处理,帮助我们发现此类异常。

1、网络中断造成的对端无 FIN 包

很多原因都会造成网络中断,在这种情况下,TCP 程序并不能及时感知到异常信息。除非网络中的其他设备,如路由器发出一条 ICMP 报文,说明目的网络或主机不可达,这个时候通过 read 或 write 调用就会返回 Unreachable 的错误。


可惜大多数时候并不是如此,在没有 ICMP 报文的情况下,TCP 程序并不能理解感应到连接异常。如果程序是阻塞在 read 调用上,那么很不幸,程序无法从异常中恢复。这显然是非常不合理的,不过,我们可以通过给 read 操作设置超时来解决。


如果程序先调用了 write 操作发送了一段数据流,接下来阻塞在 read 调用上,结果会非常不同。Linux 系统的 TCP 协议栈会不断尝试将发送缓冲区的数据发送出去,大概在重传 12 次、合计时间约为 9 分钟之后,协议栈会标识该连接异常,这时,阻塞的 read 调用会返回一条 TIMEOUT 的错误信息。如果此时程序还执着地往这条连接写数据,写操作会立即失败,返回一个 SIGPIPE 信号给应用程序。


2、系统崩溃造成的对端无 FIN 包

当系统突然崩溃,如断电时,网络连接上来不及发出任何东西。这里和通过系统调用杀死应用程序非常不同的是,没有任何 FIN 包被发送出来。


这种情况和网络中断造成的结果非常类似,在没有 ICMP 报文的情况下,TCP 程序只能通过 read 和 write 调用得到网络连接异常的信息,超时错误是一个常见的结果。


不过还有一种情况需要考虑,那就是系统在崩溃之后又重启,当重传的 TCP 分组到达重启后的系统,由于系统中没有该 TCP 分组对应的连接数据,系统会返回一个 RST 重置分节,TCP 程序通过 read 或 write 调用可以分别对 RST 进行错误处理。


如果是阻塞的 read 调用,会立即返回一个错误,错误信息为连接重置(Connection Reset)。


如果是一次 write 操作,也会立即失败,应用程序会被返回一个 SIGPIPE 信号。


3、对端有 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);
}

4、read 直接感知 FIN 包

我们依次启动服务器端和客户端程序,在客户端输入 good 字符之后,迅速结束掉服务器端程序,这里需要赶在服务器端从睡眠中苏醒之前杀死服务器程序。


屏幕上打印出:peer connection closed。客户端程序正常退出。

$./reliable_client01 127.0.0.1
$ good
$ peer connection closed

这说明客户端程序通过 read 调用,感知到了服务端发送的 FIN 包,于是正常退出了客户端程序。

5、通过 write 产生 RST,read 调用感知 RST

这一次,我们仍然依次启动服务器端和客户端程序,在客户端输入 bad 字符之后,等待一段时间,直到客户端正确显示了服务端的回应“bad”字符之后,再杀死服务器程序。客户端再次输入 bad2,这时屏幕上打印出”peer connection closed“。


屏幕输出和时序图。

$./reliable_client01 127.0.0.1
$bad
$bad
$bad2
$peer connection closed


故障分为两大类,一类是对端无 FIN 包,需要通过巡检或超时来发现;另一类是对端有 FIN 包发出,需要通过增强 read 或 write 操作的异常处理,帮助我们发现此类异常。

相关文章
|
8月前
|
网络协议
使用netwox/TCP协议检测网络性能
使用netwox/TCP协议检测网络性能
191 0
|
3月前
|
存储 网络协议 Java
【网络】UDP和TCP之间的差别和回显服务器
【网络】UDP和TCP之间的差别和回显服务器
83 1
|
8月前
|
存储 网络协议 Java
【TCP 连接手段】C++编程视角下的TCP:短连接与长连接深度解析
【TCP 连接手段】C++编程视角下的TCP:短连接与长连接深度解析
203 1
|
8月前
|
网络协议 Linux
Linux网络编程(TCP状态转换关系)
Linux网络编程(TCP状态转换关系)
66 0
|
网络协议 大数据 程序员
TCP的十个核心机制(二)
TCP的十个核心机制
97 0
|
网络协议 网络性能优化
TCP的十个核心机制(一)
TCP的十个核心机制
61 0
|
网络协议 算法
如何保证TCP的稳定性和流速控制
TCP粘包和拆包中保证顺序的具体算法是TCP滑动窗口算法。 TCP作为一个传输层协议,最核心的能力是传输。传输需要保证可靠性,还需要控制流速,这两个核心能力均由滑动窗口提供。 滑动窗口数据结构
100 0
|
网络协议 Linux 应用服务中间件
一台linux服务器最多能支持多少个TCP连接?(要区分客户端还是服务端)
一台linux服务器最多能支持多少个TCP连接?(要区分客户端还是服务端)
1253 0
|
网络协议 Linux 网络安全
【TCP/IP】【调试】丢包、流不通、错包等问题查证手段系列之二——防火墙
【TCP/IP】【调试】丢包、流不通、错包等问题查证手段系列之二——防火墙
618 0
【TCP/IP】【调试】丢包、流不通、错包等问题查证手段系列之二——防火墙
|
网络协议
【TCP/IP】【调试】丢包、流不通、错包等问题查证手段系列之一——日志方式
【TCP/IP】【调试】丢包、流不通、错包等问题查证手段系列之一——日志方式
261 0