针对TCP连接异常断开的分析
我们知道,一个基于TCP/IP的客户端-服务器的程序中,正常情况下,我会是启动服务器使其在一个端口上监听请求,等待客户端的连接;通过TCP的三次握手,客户端能够通过socket建立一个到服务器的连接;然后,两者就可以基于这个socket连接通信了。连接结束后,客户端(进程)会退出;在不需要继续处理客户请求的情况下,服务器(进程)也将退出。而且,当一个进程退出的时候,内核会关闭所有由这个进程打开的套接字,这里将触发TCP的四次挥手进而关闭一个socket连接。但是,在一些异常的情况下,譬如:服务器进程终止、服务器主机奔溃/奔溃后重启、服务器关机的情况下,客户端向服务器发起请求的时候,将会发生什么呢?下边,我们来看看这几种情况。
注意:一下描述的各种情况所使用的示例程序在文章的最后贴出
一、服务器进程终止
我们启动客户/服务器对,然后杀死子进程(模拟服务器进程崩溃的情形,我们可从中查看客户端将发生什么)。
1:在同一个主机上启动服务器和客户,并在客户上输入一行文本,以验证一切正常。正常情况下,改行文本将由服务器回射给客户。
2:找到服务器子进程的ID,通过kill命令杀死它。作为进程终止处理的部分工作,子进程中所有打开着的描述字都被关闭。这就导致向客户发送一个FIN,而客户TCP则响应以一个ACK。这就是TCP连接终止的前一半工作。
3:子进程终止时,内核将给父进程递交SIGCHLD信号。
4:客户上没有发生任何特殊之事。客户TCP接受来自服务器TCP的FIN并响应一个ACK,然后问题是客户进程此时阻塞在fgets调用上,等待从终端接受一行文本。它是看不到这个FIN的。
5:此时我们如果运行netstat命令,可以看到如下的套接口的状态:

FIN_WAIT2即为我们杀掉的那个子进程的,因为我们知道主动关闭的那端在发送完fin并接受对端的ack后将进入fin_wait2状态,此时它在等待对端的fin。
6:现在我们在客户上在输入一行文本,我们可以看到如下的输出:

当我们输入“after server close”时,客户TCP接着把数据发送给服务器,TCP允许这么做,因为客户TCP接受到FIN只是表示服务器进程已关闭了连接的服务端,从而不再往其中发送任何数据而已。FIN的接受并没有告知客户TCP服务器进程已经终止(在这个例子中它缺失是终止了)。当服务器TCP接收到来自客户的数据时,既然先前打开那个套接口的进程已经终止,于是响应一个RST。
7:然而客户进程看不到这个RST,因为它在调用write后立即调用read,并且由于第2步中接收到FIN,所调用的read立即返回0(表示)EOF。我们的客户此时并未预期收到EOF,于是以出错信息“server term prematurely.”(服务器过早终止)退出。
8:当客户终止时,它所有打开着的描述字都被关闭。
我们的上述讨论还取决于程序的时序。客户调用read既可能发生在服务器的RST被客户收到之前,也可能发生在收到之后。如果read发生在收到RST之前(如本例子所示),那么结果是客户得到一个未预期的EOF;否则结果是由readline返回一个ECONNRESET(“connection reset by peer”对方复位连接)错误。
本例子的问题在于:当FIN到达套接口时,客户正阻塞在fgets调用上。客户实际上在应对两个描述字——套接口和用户输入,它不能单纯阻塞在这两个源中某个特定源的输入上,而是应该阻塞在其任何一个源的输入上。(可用select等io复用的函数实现)
二、服务器主机崩溃
我们接着查看当服务器主机崩溃时会发生什么。为了模拟这种情形,我们需要在不同的机器上运行客户与服务器,在首次确认客户服务器能正常工作后,我们从网络上断开服务器主机,并在客户上再输入一行文本。这里同时也模拟了当客户发送数据时服务器主机不可达的情形(机建立连接后某些中间路由器不工作)
1:当服务器主机崩溃时,已有的网络连接上发不出任何东西。这里我们假设的是主机崩溃,而不是执行了关机命令。
2:我们在客户上输入一行文本,它由write写入内核,再由客户TCP作为一个数据分节送出。客户随后阻塞于read调用,等待服务器的应答。
3:这种情况下,客户TCP持续重传数据分节,试图从服务器上接受一个ACK。(源自Berkeley的实现重传该数据分节12次,共等待约9分钟才放弃重传。)当客户TCP最终放弃时(假设这段时间内,服务器主机没有重新启动或者如果是服务器主机为崩溃但从网络上不可达的情况,那么假设主机仍然不可达),返回客户进程一个错误。既然客户阻塞在readline调用上,该调用将返回一个错误。假设服务器已崩溃,从而对客户的数据分节根本没有响应,那么所返回的错误是ETIMEDOUT。然而如果某个中间路由器判定服务器主机已不可达,从而响应以一个“destination unreachable”,那么所返回的错误是EHOSTUNREACH或ENETUNREACH。
尽管我们的客户最后还是发现对端主机已崩溃或不可达,不过有时候我们需要更快地检测出这种情况,而不是不得不等待9分钟。所用的方法就是对read调用设置一个超时。
另外我们刚讨论的情形只有在向服务器主机发送数据时,才能检测出它已经崩溃,如果我们不主动发送主句也想检测出服务器主机的崩溃,那么就需要用到SO_KEEPALIVE这个套接口选项。
三、服务器主机崩溃后重启
在前一节的分析中,当我们发送数据时,服务器主机仍然处于崩溃状态;这节,我们将在发送数据前重新启动崩溃了的服务器主机。模拟这种情况的简单方法就是:建立连接,再从网络上端口服务器主机,将它关机后再重启,最后把它重新连接到网络中。
如前一节所述,如果在服务器主机崩溃时客户不主动给服务器发送数据,那么客户不会知道服务器主机已经崩溃。所发生的步骤如下:
1:启动客户服务器,在客户上输入一行文本已确认连接已建立。
2:服务器主机崩溃并重启。
3:在客户上输入一行文本,它将作为一个TCP数据分节发送到服务器主机。
4:当服务器主机崩溃后重启时,它的TCP丢失了崩溃前的所有连接信息,因此服务器TCP对于所收到的来自客户的数据分节响应以一个RST。
5:当客户TCP收到该RST时,客户正阻塞于read调用,导致该调用返回ECONNRESET错误。
四、服务器主机关机
这节我们看看当服务器关机时将会发生什么。
Unix系统关机时,init进程通常先给所有进程发送SIGTERM信号(该信号可被捕获),再等待一段固定的时间(一般在5~20秒之间),然后给所有仍在运行的进程发送SIGKILL信号(该信号不能被捕获)。这么做是留给所有运行中的进程一小段时间来清除和终止。如果我们不捕获SIGTERM信号并终止,我们的服务器将由SIGKILL信号终止。当服务器进程终止时,它的所有打开着的描述字都被关闭,随后发生的步骤与第一节中讨论过的一样。正如第一节中所述的情形,我们必须在客户中使用select或poll函数,使得服务器进程的终止已经发生,客户马上检测到。
五、示例程序
//client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <errno.h>
#include <error.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h> void str_cli(FILE *fp, int sfd ) {
char sendline[], recvline[];
memset(recvline, , sizeof(sendline));
memset(sendline, , sizeof(recvline));
while( fgets(sendline, , fp) != NULL ) {
write(sfd, sendline, strlen(sendline));
if( read(sfd, recvline, ) == ) {
printf("server term prematurely.\n");
}
fputs(recvline, stdout);
memset(recvline, , sizeof(sendline));
memset(sendline, , sizeof(recvline));
}
} int main() {
int s;
if( (s = socket(AF_INET, SOCK_STREAM, )) < ) {
int e = errno;
perror("create socket fail.\n");
exit();
} struct sockaddr_in server_addr, child_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons();
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); if( connect(s, (struct sockaddr *)&server_addr, sizeof(server_addr)) < ) {
perror("connect fail.");
exit();
}
str_cli(stdin, s);
exit();
}
//server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <errno.h>
#include <error.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h> //using namespace std; typedef void sigfunc(int); void func_wait(int signo) {
pid_t pid;
int stat;
pid = wait(&stat);
printf( "child %d exit\n", pid );
return;
}
void func_waitpid(int signo) {
pid_t pid;
int stat;
while( (pid = waitpid(-, &stat, WNOHANG)) > ) {
printf( "child %d exit\n", pid );
}
return;
} sigfunc* signal( int signo, sigfunc *func ) {
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = ;
if ( signo == SIGALRM ) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
} else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 4.4BSD */
#endif
}
if ( sigaction(signo, &act, &oact) < ) {
return SIG_ERR;
}
return oact.sa_handler;
} void str_echo( int cfd ) {
ssize_t n;
char buf[];
//char t[] = "SERVER ECHO: ";
again:
memset(buf, , sizeof(buf));
while( (n = read(cfd, buf, )) > ) {
write(cfd, buf, n);
}
if( n < && errno == EINTR ) {
goto again;
} else {
printf("str_echo: read error\n");
}
} int main() { signal(SIGCHLD, &func_waitpid); int s, c;
pid_t child;
if( (s = socket(AF_INET, SOCK_STREAM, )) < ) {
int e = errno;
perror("create socket fail.\n");
exit();
} struct sockaddr_in server_addr, child_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons();
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); if( bind(s, (struct sockaddr *)&server_addr, sizeof(server_addr)) < ) {
int e = errno;
perror("bind address fail.\n");
exit();
} if( listen(s, ) < ) {
int e = errno;
perror("listen fail.\n");
exit();
}
while() {
socklen_t chilen = sizeof(child_addr);
if ( (c = accept(s, (struct sockaddr *)&child_addr, &chilen)) < ) {
perror("listen fail.");
exit();
} if( (child = fork()) == ) {
close(s);
str_echo(c);
exit();
}
close(c);
}
}
针对TCP连接异常断开的分析的更多相关文章
- TCP连接异常断开检测(转)
TCP是一种面向连接的协议,连接的建立和断开需要通过收发相应的分节来实现.某些时候,由于网络的故障或是一方主机的突然崩溃而另一方无法检测到,以致始终保持着不存在的连接.下面介绍一种方法来检测这种异常断 ...
- (转)TCP连接异常断开检测
TCP是一种面向连接的协议,连接的建立和断开需要通过收发相应的分节来实现.某些时候,由于网络的故障或是一方主机的突然崩溃而另一方无法检测到,以致始终保持着不存在的连接.下面介绍一种方法来检测这种异常断 ...
- wireshark抓包分析tcp连接与断开
其实对于网络通信的学习,最好还是能够自己抓到包详细地一下,不然只单单通过文字和图的描述印象不够深刻.本文通过实际的抓包操作来看一下tcp的连接与断开是怎样的. 首先需要去https://www.wir ...
- 4个实验,彻底搞懂TCP连接的断开
前言 看到这个标题你可能会说,TCP 连接的建立与断开,这个我熟,不就是三次握手与四次挥手嘛.且慢,脑海中可以先尝试回答这几个问题: 四次挥手是谁发起的? 如果断电/断网了连接会断开吗? 什么情况下没 ...
- TCP连接异常:broken pipe 和EOF
本文介绍3种TCP连接异常的情况. 1.server端没有启动,client尝试连接 ./client dial failed: dial tcp 127.0.0.1:8080: connect: c ...
- 设置TCP_USER_TIMEOUT参数来判断tcp连接是否断开
[TOC] 1. bug描述 前段时间遇到这样的一个问题,openstack一个控制节点宕机后,在宕机后一段时间内创建的虚拟机,一直卡在创建中的状态.有的甚至要等到16分钟之后虚拟机才会切换到下一个状 ...
- TCP连接与断开详解(socket通信)
http://blog.csdn.net/Ctrl_qun/article/details/52518479 一.TCP数据报结构以及三次握手 TCP(Transmission Control Pro ...
- 关于心跳ajax请求pending状态(被挂起),stalled时间过长的问题。涉及tcp连接异常。
环境:景安快云服务器(听说很垃圾,但是公司买的,我也刚来),CentOS-6.8-x86_64,Apache,MySQL5.1,PHP5.3. 问题:现公司有一个php系统,需要重复向后台发送ajax ...
- Linux 建立 TCP 连接的超时时间分析(解惑)
Linux 系统默认的建立 TCP 连接的超时时间为 127 秒,对于许多客户端来说,这个时间都太长了, 特别是当这个客户端实际上是一个服务的时候,更希望能够尽早失败,以便能够选择其它的可用服务重新尝 ...
随机推荐
- 1003 我要通过!| PAT (Basic Level) Practice
1003 我要通过! (20 分) "答案正确"是自动判题系统给出的最令人欢喜的回复.本题属于 PAT 的"答案正确"大派送 -- 只要读入的字符串满足下列条件 ...
- lintcode-513-完美平方
513-完美平方 给一个正整数 n, 找到若干个完全平方数(比如1, 4, 9, ... )使得他们的和等于 n.你需要让平方数的个数最少. 样例 给出 n = 12, 返回 3 因为 12 = 4 ...
- Sprint Boot入门(1):创建第一个Spring Boot应用
搭建工程 注:建议使用eclipse的STS插件创建Spring项目,而不是下面的Gradle项目,否则会导致有一些Spring文件不存在. new Gradle Project,如下 点next,如 ...
- SQLSERVER 升级版本的方法
1. 以SQLSERVER2014为例说明 SQLSERVER升级版本的方法, 也适用于evaluation 版本超过180天之后的处理. 2. 打开所有的应用 看到有一个 sqlserver2008 ...
- From 百度知道 SQLSERVER 字符集排序规则简单说明
https://zhidao.baidu.com/question/390314825002277485.html 学习一下, 以后说不定用得到. collate Latin1_General_CS_ ...
- 类似jq的即点即改
<?php namespace app\controllers; use Yii;use yii\filters\AccessControl;use yii\web\Controller;use ...
- JAVA相关概念(一)
依赖注入和控制反转 首先,这两个词是同一个概念的不同角度的说法,依赖注入感觉是对描述了如何实现,而控制反转则像是描述了一种思想. 依赖注入的流行可以说是由spring的流行带动的,只要是做过sprin ...
- P4291 [HAOI2008]排名系统
题目描述 排名系统通常要应付三种请求:上传一条新的得分记录.查询某个玩家的当前排名以及返回某个区段内的排名记录.当某个玩家上传自己最新的得分记录时,他原有的得分记录会被删除.为了减轻服务器负担,在返回 ...
- NAT alg 和 ASPF
NAT alg 和 ASPF 参考:https://handbye.cn/719.html 来源:https://www.jianshu.com/p/8a8eb36eef7d NAT的部署已经在企业网 ...
- c++11 模板的别名
c++11 模板的别名 #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #inclu ...