UNIX网络编程——客户/服务器心搏函数
阅读此博客时,可以参考以前的博客<<UNIX网络编程——socket的keep-alive>>和<<UNIX网络编程——套接字选项(心跳检测、绑定地址复用)>>。
下面是关于回送客户和服务器程序开发一些简单的心搏函数。这些函数可以发现对端主机或到对端的通信路径的过早失效。
在给出这些函数之前我们必须提出一些警告。首先,有人会想到使用TCP的保持存活特性(SO_KEEPALIVE套接字选项)来提供这种功能,然而TCP得在连接已经闲置2小时之后才发送一个保持存活探测段。意识到这一点以后,他们的下一个问题是如何把保持存活参数改为一个小得多的值(往往是在秒钟的量级),以便更快的检测到失效。尽管缩短TCP的保持存活定时器参数在许多系统上确实可行,但是这些参数通常是按照内核而不是按照每个套接字维护的,因此改动他们将影响所有开启该选项的套接字。另外保持存活选项的用意绝不是这个目的(搞频率的轮询)。
其次,两个端系统之间短暂的连接性丢失并非总是坏事。TCP一开始就设计成能够对付临时断连,而源自Berkeley的TCP实现将重传8-10分钟才放弃某个连接。较新的IP路由协议能够发现链接的失效,并且有可能在短时间内(譬如在秒钟量级上)启用候选的路径。因此应用程序开发人员必须审查想要引入心搏机制的具体应用,确实在没有听到对端应答的持续时间超过5-10S之后终止相应连接是件好事还是坏事。有些应用系统需要这种功能,不过大多数却并不需要。
我们将使用TCP的紧急模式周期地轮询对端;在下面的讲解中我们假设每1S轮询一次,若持续5S没有听到对端应答则认为对端已不再存活,不过这些值可以由应用程序改动。
在这个例子中,客户每隔1S向服务器发送一个带外字节,服务器取该字节将导致它向客户发送回一个带外字节。每端都需要知道对端是否不复存在或者不再可达。客户和服务器每1S递增他们的cnt变量一次,每收到一个带外字节又把该变量重置为0。如果该计数器达到5(也就是说本进程已有5S没有收到来自对端的带外字节),那就认定连接失效。当有带外字节到达时,客户和服务器都是用SIGURG信号得以通知。我们在该图中间指出:数据,回送数据和带外字节都通过单个TCP连接交换。
如下是我们的heatbeat_cli函数设置客户的心搏特性,其中第二个参数是以秒为单位的轮询频率,第三个参数是放弃当前连接之前应该经历的持续无响应轮询次数。
#include "unp.h"
/* 给heartbeat_cli的参数的拷贝: 套接口描述字(信号处理程序需用它来发送和接收带外数据),SIGALRM的频率,在客户认为服务器或连接死掉之前没有来子服务器的响应的SIGALRM的总数,总量nprobes记录从最近一次服务器应答以来的SIGALRM的数目 */
static int servfd;
static int nsec; /* #seconds between each alarm */
static int maxnprobes; /* #probes w/no response before quit */
static int nprobes; /* #probes since last server response */
static void sig_urg(int), sig_alrm(int);
void heartbeat_cli(int servfd_arg, int nsec_arg, int maxnprobes_arg)
{/* heartbeat_cli函数检查并且保存参数,给SIGURG和SIGALRM建立信号处理函数,将套接口的属主设为进程ID,alarm调度一个SIGALRM */
servfd = servfd_arg; /* set globals for signal handlers */
if( (nsec = nsec_arg) < 1)
nsec = 1;
if( (maxnprobes = maxnprobes_arg) < nsec)
maxnprobes = nsec;
nprobes = 0;
Signal(SIGURG, sig_urg);
Fcntl(servfd, F_SETOWN, getpid() );
Signal(SIGALRM, sig_alrm);
alarm(nesc);
}
static void sig_urg(int signo)
{/* 当一个带外通知到来时,就会产生这个信号。我们试图去读带外字节,但如果还没有到(EWOULDBLOCK)也没有关系。由于系统不是在线接收带外数据,因此不会干扰客户读取它的普通数据。既然服务器仍然存活,nprobes就重置为0 */
int n;
char c;
if( ( n = recv(servfd, &c, 1, MSG_OOB) ) < 0 )
{
if(errno != EWOULDBLOCK)
err_sys("recv error");
}
nprobes = 0; /* reset counter */
return; /* may interrupt client code */
}
static void sig_alrm(int signo)
{/* 这个信号以有规律间隔产生。计数器nprobes增1, 如果达到了maxnprobes,我们认为服务器或者崩溃或者不可达。在这个例子中,我们结束客户进程,尽管其他的设计也可以使用:可以发送给主循环一个信号,或者作为另外一个参数给heartbeat_cli提供一个客户函数,当服务器看来死掉时调用它 */
if( ++nprobes > maxnprobes)
{
fprintf(stderr, "server is unreachable \n");
exit(0);
}
Send(servfd, "1", 1, MSG_OOB);
alarm(nsec);
return; /* may interrupt client code */
}
全局变量
3-6 前3个变量是heartbeat_cli函数参数的副本:套接字描述符(信号处理函数用它来发送和接收带外数据),SIGALRM的频率,在客户认为服务器或连接不复存活之前处理的无服务器响应的SIGALRM总数。变量nprobes计量从收到来自服务器的最后一个应答以来处理的SIGALRM数目。
heartbeat_cli函数
8-20 heartbeat_cli函数检查并保存参数,给SIGURG和SIGALRM建立信号处理函数,并把套接字的属主设置为本进程ID。执行alarm以调度第一个SIGALRM.
SIGURG处理函数
21-32 本信号在某个带外通知到达时产生。我们尝试读入相应的带外字节,不过如果它还没有到达(EWOULDBLOCK),那也没有关系。注意,我们不采用在线接收带外数据方式,因为这种方式会干扰客户读取它的正常数据。既然服务器仍然存活着,我们把nprobes重置为0.
SIGALRM处理函数
33-43 本信号以恒定的间隔产生。递增计数器nprobes,如果达到maxnprobes,我们就认定服务器主机或者已经崩溃,或者不再可达。我们这里是直接结束客户进程。作为带外数据发送一个含有字符1的字节(该值没有任何隐含意义),再执行alarm调度下一个SIGALRM。
下面是服务器程序的心搏函数。
#include "unp.h"
static int servfd;
static int nsec; /* #seconds between each alarm */
static int maxnalarms; /* #alarms w/no client probe before quit */
static int nprobes; /* #alarms since last client probe */
static void sig_urg(int), sig_alrm(int);
void heartbeat_serv(int servfd_arg, int nsec_arg, int maxnalarms_arg)
{
servfd = servfd_arg; /* set globals for signal handlers */
if( (nsec = nsec_arg) < 1 )
nsec = 1;
if( (maxnalarms = maxnalarms_arg) < nsec)
maxnalarms = nsec;
Signal(SIGURG, sig_urg);
Fcntl(servfd, F_SETOWN, getpid());
Signal(SIGALRM, sig_alrm);
alarm(nsec);
}
static void sig_urg(int signo)
{ /* 当一个带外通知收到时, 服务器试图读入它。就像客户一样,如果带外字节没有到达没有什么关系。带外字节被作为带外数据返回给客户。注意,如果recv返回EWOULDBLOCK错误,那么自动变量c碰巧是什么就送给客户什么。由于我们不用带外字节的值,所以这没有关系。重要的是发送1字节的带外数据,而不管该字节是什么。由于刚收到通知,客户仍存活,所以重置nprobes为0 */
int n;
char c;
if( (n = recv(servfd, &c, 1, MSG_OOB)) < 0)
{
if(errno != EWOULDBLOCK)
err_sys("recv error");
}
Send(servfd, &c, 1, MSG_OOB); /* echo back out-of-hand byte */
nprobes = 0; /* reset counter */
return; /* may interrupt server code */
}
static void sig_alrm(int signo)
{ /* nprobes增1, 如果它到达了调用者指定的值maxnalarms,服务器进程将被终止。否则调度一下SIGALRM */
if( ++nprobes > maxnalarms)
{
printf("no probes from client\n");
exit(0);
}
alarm(nsec);
return; /* may interrupt server code */
}
heartbeat_serv函数
7-18 声明变量,函数heartbeat_serv几乎与客户的心搏初始化函数一样。
SIGURG处理函数
19-31 服务器收到一个带外通知后就尝试读入相应的带外字节。就像客户一样,如果该带外字节还没有到达,那也没有声明关系。服务器把读入的带外字节作为带外数据回送给客户。注意,如果recv返回EWOULDBLOCK错误,那么自动变量C碰巧是什么就回送什么。既然我们不把带外字节的值用于任何目的,这么处置就不会有问题。重要的是发送1字节的带外数据本身,而不是该字节到底是什么。既然刚收到客户仍然存活着的通知,我们把nprobes重置为0.
SIGALRM处理函数
32-41 递增nprobes,如果达到由调用者指定的maxnalarms值,那就终止服务器进程,否则调度下一个SIGALRM。
UNIX网络编程——客户/服务器心搏函数的更多相关文章
- UNIX网络编程——客户/服务器心搏函数 (转)
下面是关于回送客户和服务器程序开发一些简单的心搏函数.这些函数可以发现对端主机或到对端的通信路径的过早失效. 在给出这些函数之前我们必须提出一些警告.首先,有人会想到使用TCP的保持存 ...
- UNIX网络编程——客户/服务器程序设计示范(总结)
(1)当系统负载较轻是,每来一个客户请求现场派生一个子进程为之服务的传统并发服务器程序模型就足够了.这个模型甚至可以与inetd结合使用,也就是inetd处理每个连接的接收.我们的其他意见是就重负荷运 ...
- UNIX网络编程——客户/服务器程序设计示范(七)
TCP预先创建线程服务器程序,每个线程各自accept 前面讨论过预先派生一个子进程池快于为每个客户线程派生一个子进程.在支持线程的系统上,我们有理由预期在服务器启动阶段预先创建一个线程池以取 ...
- UNIX网络编程——客户/服务器程序设计示范(六)
TCP并发服务器程序,每个客户一个线程 前面讲述了,每个客户一个进程的服务器,或为每个客户现场fork一个子进程,或者预先派生一定数目的子进程.如果服务器主机支持线程,我们就可以改用线程以取代子进程. ...
- UNIX网络编程——客户/服务器程序设计示范(五)
TCP预先派生子进程服务器程序,传递描述符 对预先派生子进程服务器程序的最后一个修改版本是只让父进程调用accept,然后把所接受的已连接套接字"传递"给某个子进程.这么做 ...
- UNIX网络编程——客户/服务器程序设计示范(三)
TCP预先派生子进程服务器程序,accept无上锁保护 我们的第一个"增强"型服务器程序使用称为预先派生子进程的技术.使用该技术的服务器不像传统意义的并发服务器那样为每个客户现场派 ...
- UNIX网络编程——客户/服务器程序设计示范(二)
TCP并发服务器程序,每个客户一个子进程 传统上并发服务器调用fork派生一个子进程来处理每个客户.这使得服务器能够同时为多个客户服务,每个进程一个客户.客户数目的唯一限制是操作系统对以其名义 ...
- UNIX网络编程——客户/服务器程序设计示范(八)
TCP预先创建线程服务器程序,主线程统一accept 最后一个使用线程的服务器程序设计示范是在程序启动阶段创建一个线程池之后只让主线程调用accept并把每个客户连接传递给池中某个可用线程. ...
- UNIX网络编程——客户/服务器程序设计示范(四)
TCP预先派生子进程服务器程序,accept使用线程上锁保护 我们使用线程上锁保护accept,因为这种方法不仅适用于同一进程内各线程之间的上锁,而且适用于不同进程之间的上锁. ...
随机推荐
- [暑假的bzoj刷水记录]
(这篇我就不信有网站来扣) 这个暑假打算刷刷题啥的 但是写博客好累啊 堆一起算了 隔一段更新一下. 7月27号之前刷的的就不写了 , 写的累 代码不贴了,可以找我要啊.. 2017.8.27upd ...
- [bzoj1187][HNOI2007]神奇游乐园
来自FallDream的博客,未经允许,请勿转载,谢谢, 经历了一段艰辛的旅程后,主人公小P乘坐飞艇返回.在返回的途中,小P发现在漫无边际的沙漠中,有一块狭长的绿地特别显眼.往下仔细一看,才发现这是一 ...
- bzoj1227 组合数学+bit
1227: [SDOI2009]虔诚的墓主人 Time Limit: 5 Sec Memory Limit: 259 MBSubmit: 1412 Solved: 664[Submit][Stat ...
- Postgres中postmaster代码解析(中)
今天我们对postmaster的以下细节进行讨论: backend的启动和client的连接请求的认证 客户端取消查询时的处理 接受pg_ctl的shutdown请求进行shutdown处理 2.与前 ...
- java9学习之模块化
截止到目前JDK的版本已经更新到10了,虽然java9的生命周期才半年,但是我认为这个版本带来的变革是不可磨灭的,它是第一次深层次的针对架构以及依赖上的革新.下面我们就来学习一下. 一.模块化项目构建 ...
- Python【第二课】 字符串,列表,字典,集合,文件操作
本篇内容 字符串操作 列表,元组操作 字典操作 集合操作 文件操作 其他 1.字符串操作 1.1 字符串定义 特性:不可修改 字符串是 Python 中最常用的数据类型.我们可以使用引号('或&quo ...
- 以太坊MetaMask钱包插件简介
MetaMask是一个以太坊钱包插件,虽然只能在Chrome浏览器中使用,但作为以太坊钱包的metamask却很受以太坊开发者欢迎. MetaMask 评价(5★):安装设置:★★★★界面操作:★★★ ...
- Java 反射 Method threw 'java.lang.InstantiationException' exception.
查看这个InstantiationException:异常的api所说的是: 当应用程序试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象无法被实例化时,抛出该 ...
- Linux学习之CentOS(六)---mount挂载设备(u盘,光盘,iso等 )
对于新手学习,mount 命令,一定会有很多疑问.其实我想疑问来源更多的是对linux系统本身特殊性了解问题. linux是基于文件系统,所有的设备都会对应于:/dev/下面的设备.如: [cheng ...
- React 关于组件(界面)更新
在最近在学 React , 将组件的UI更新稍微整理了一下.根据业务要求,可能会出现如下的技术实现要求:1.更新自己2.更新子组件3.更新兄弟组件4.更新父组件5.父 call 子 function ...