我们知道,一个基于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连接异常断开的分析的更多相关文章

  1. TCP连接异常断开检测(转)

    TCP是一种面向连接的协议,连接的建立和断开需要通过收发相应的分节来实现.某些时候,由于网络的故障或是一方主机的突然崩溃而另一方无法检测到,以致始终保持着不存在的连接.下面介绍一种方法来检测这种异常断 ...

  2. (转)TCP连接异常断开检测

    TCP是一种面向连接的协议,连接的建立和断开需要通过收发相应的分节来实现.某些时候,由于网络的故障或是一方主机的突然崩溃而另一方无法检测到,以致始终保持着不存在的连接.下面介绍一种方法来检测这种异常断 ...

  3. wireshark抓包分析tcp连接与断开

    其实对于网络通信的学习,最好还是能够自己抓到包详细地一下,不然只单单通过文字和图的描述印象不够深刻.本文通过实际的抓包操作来看一下tcp的连接与断开是怎样的. 首先需要去https://www.wir ...

  4. 4个实验,彻底搞懂TCP连接的断开

    前言 看到这个标题你可能会说,TCP 连接的建立与断开,这个我熟,不就是三次握手与四次挥手嘛.且慢,脑海中可以先尝试回答这几个问题: 四次挥手是谁发起的? 如果断电/断网了连接会断开吗? 什么情况下没 ...

  5. TCP连接异常:broken pipe 和EOF

    本文介绍3种TCP连接异常的情况. 1.server端没有启动,client尝试连接 ./client dial failed: dial tcp 127.0.0.1:8080: connect: c ...

  6. 设置TCP_USER_TIMEOUT参数来判断tcp连接是否断开

    [TOC] 1. bug描述 前段时间遇到这样的一个问题,openstack一个控制节点宕机后,在宕机后一段时间内创建的虚拟机,一直卡在创建中的状态.有的甚至要等到16分钟之后虚拟机才会切换到下一个状 ...

  7. TCP连接与断开详解(socket通信)

    http://blog.csdn.net/Ctrl_qun/article/details/52518479 一.TCP数据报结构以及三次握手 TCP(Transmission Control Pro ...

  8. 关于心跳ajax请求pending状态(被挂起),stalled时间过长的问题。涉及tcp连接异常。

    环境:景安快云服务器(听说很垃圾,但是公司买的,我也刚来),CentOS-6.8-x86_64,Apache,MySQL5.1,PHP5.3. 问题:现公司有一个php系统,需要重复向后台发送ajax ...

  9. Linux 建立 TCP 连接的超时时间分析(解惑)

    Linux 系统默认的建立 TCP 连接的超时时间为 127 秒,对于许多客户端来说,这个时间都太长了, 特别是当这个客户端实际上是一个服务的时候,更希望能够尽早失败,以便能够选择其它的可用服务重新尝 ...

随机推荐

  1. Beta阶段团队项目开发篇章2

    例会时间: 2016.12.4 例会照片 个人工作 上阶段任务验收: 组员任务都已完成. 任务分配 组员 任务内容 韩慧敏 对调查问卷的结果进行分析和总结,确定Beta阶段各任务的优先级,撰写相关博客 ...

  2. selenium之数据驱动框架应用WPS个人中心自动签到

    wps在注册后,有个每日签到的功能,签到后有几率送wps的专属金币[稻米],为了免费获得,又不想每天都是人工去执行签到动作,所以用selenium写了个小脚本,准备用数据驱动框架来完成这个事情,数据驱 ...

  3. 关于es6箭头函数

    1  基本用法 ES6 允许使用 “ 箭头 ” (=>)定义函数. var f = v => v; //上面的箭头函数等同于: var f = function(v) { return v ...

  4. 【转载】Vue项目中的文件/文件夹命名规范

    文件或文件夹的命名遵循以下原则: index.js 或者 index.vue,统一使用小写字母开头的(kebab-case)命名规范 属于组件或类的,统一使用大写字母开头的(PascalCase)命名 ...

  5. [转帖]脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

    脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手   http://www.52im.net/thread-1729-1-1.html     1.引言 网络编程中TCP协议的三次握手和 ...

  6. C++ Primer 中文版 5th Edition 练习15.8和练习15.9的解答

    练习15.8:给出静态类型和动态类型的定义. 答: 静态类型:是变量声明时的类型,或者是表达式生成的类型,这样的类型在编译时已知. 动态类型:是变量或者表达式表示的内存中的对象的类型,直到运行时才可知 ...

  7. java.lang.NoSuchMethodError: org.hibernate.integrator.internal.IntegratorServiceImpl.<init>(Ljava/util/LinkedHashSet;Lorg/hibernate/boot/registry/classloading/spi/ClassLoaderService;)

    需要:4.3及以上的版本才能用StandardServiceRegistryBuilder() hibernate-core-4.3.11.Final.jar version:4.3 ServiceR ...

  8. DataTable学习笔记 - 01

    DataTable 是 jQuery 的一个插件. 代码上来吧, <!DOCTYPE html> <html> <head> <meta charset=&q ...

  9. js & auto copy

    js & auto copy https://developer.mozilla.org/zh-CN/docs/Web/Events/copy Ctrl + C Command + C doc ...

  10. 自定义样式,使用浏览器阅读epub格式的电子书

    epub格式的电子式一般用专门的阅读器打开,但是如果可以使用浏览器打开,就可以随意更改css了,获得极致的体验效果. 比如可以自定义字体.行间距.背景色.字体大小.缩进等等... 当然,如果您不需要添 ...