阅读此博客时,可以参考以前的博客<<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网络编程——客户/服务器心搏函数的更多相关文章

  1. UNIX网络编程——客户/服务器心搏函数 (转)

    下面是关于回送客户和服务器程序开发一些简单的心搏函数.这些函数可以发现对端主机或到对端的通信路径的过早失效.         在给出这些函数之前我们必须提出一些警告.首先,有人会想到使用TCP的保持存 ...

  2. UNIX网络编程——客户/服务器程序设计示范(总结)

    (1)当系统负载较轻是,每来一个客户请求现场派生一个子进程为之服务的传统并发服务器程序模型就足够了.这个模型甚至可以与inetd结合使用,也就是inetd处理每个连接的接收.我们的其他意见是就重负荷运 ...

  3. UNIX网络编程——客户/服务器程序设计示范(七)

        TCP预先创建线程服务器程序,每个线程各自accept 前面讨论过预先派生一个子进程池快于为每个客户线程派生一个子进程.在支持线程的系统上,我们有理由预期在服务器启动阶段预先创建一个线程池以取 ...

  4. UNIX网络编程——客户/服务器程序设计示范(六)

    TCP并发服务器程序,每个客户一个线程 前面讲述了,每个客户一个进程的服务器,或为每个客户现场fork一个子进程,或者预先派生一定数目的子进程.如果服务器主机支持线程,我们就可以改用线程以取代子进程. ...

  5. UNIX网络编程——客户/服务器程序设计示范(五)

        TCP预先派生子进程服务器程序,传递描述符 对预先派生子进程服务器程序的最后一个修改版本是只让父进程调用accept,然后把所接受的已连接套接字"传递"给某个子进程.这么做 ...

  6. UNIX网络编程——客户/服务器程序设计示范(三)

    TCP预先派生子进程服务器程序,accept无上锁保护 我们的第一个"增强"型服务器程序使用称为预先派生子进程的技术.使用该技术的服务器不像传统意义的并发服务器那样为每个客户现场派 ...

  7. UNIX网络编程——客户/服务器程序设计示范(二)

        TCP并发服务器程序,每个客户一个子进程 传统上并发服务器调用fork派生一个子进程来处理每个客户.这使得服务器能够同时为多个客户服务,每个进程一个客户.客户数目的唯一限制是操作系统对以其名义 ...

  8. UNIX网络编程——客户/服务器程序设计示范(八)

        TCP预先创建线程服务器程序,主线程统一accept 最后一个使用线程的服务器程序设计示范是在程序启动阶段创建一个线程池之后只让主线程调用accept并把每个客户连接传递给池中某个可用线程.  ...

  9. UNIX网络编程——客户/服务器程序设计示范(四)

        TCP预先派生子进程服务器程序,accept使用线程上锁保护 我们使用线程上锁保护accept,因为这种方法不仅适用于同一进程内各线程之间的上锁,而且适用于不同进程之间的上锁.        ...

随机推荐

  1. 【poj 1087 a plug for UNIX】

    在大米饼的帮助下,终于找到了大米饼程序中如同大米饼一般的错误! 考点在问题转化,然后就跑一个你喜欢的最大流算法(二分图可以啵?) 再来一个例子吧: [纯手绘大米饼图片] 其中有的边权是1,否则就是in ...

  2. 例 7-10 uva12212(迭代加深搜索)

    题意:对于一段数字,每次可以剪切一段连续的自然数,粘贴到任意部分,使其变成升序 思路: 考虑的是进行搜索,深搜并不能保证是最短,广搜感觉每层的情况太多. 迭代加深:枚举搜索深度,然后进行深搜. 这种方 ...

  3. 勤拂拭软件 java web 开发教程(1) - 开发环境搭建

    勤拂拭软件系列教程 之 Java Web开发之旅(1) Java Web开发环境搭建 1 前言 工作过程中,遇到不少朋友想要学习jsp开发,然而第一步都迈不出,连一个基本的环境都没有,试问,如何能够继 ...

  4. 10分钟 5步 发布以太坊 ERC20 代币

    1.安装 METAMASK Brings Ethereum to your browser 一个可以浏览器上进行操作的以太坊钱包,推荐 Chrome. Chrome 插件安装地址: https://c ...

  5. sqlserver 取日期年份月份

    select convert(varchar(10),datepart(YYYY,a.fssj)) as years,--得到年份convert(varchar(10),datepart(mm,a.f ...

  6. 72. Edit Distance(困难,确实挺难的,但很经典,双序列DP问题)

    Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2 ...

  7. 41. First Missing Positive(困难, 用到 counting sort 方法)

    Given an unsorted integer array, find the first missing positive integer. For example, Given [1,2,0] ...

  8. java中的构造,封装

    今天给大家讲一下面向对象中的构造,封装: 构造:构造方法有以下几个特点:1.方法名和类名一致.2.无返回类型.接下来的几种构造样式,直接上代码吧: //这是一个宠物类 有一个属性:名字(name) p ...

  9. 基本数据类型 异常 数组排序 JVM区域划分

               Day01 1.基本数据类型各占几个字节 Byte 1 short2 int4 long8 float4 double6 char2 boolean1 Byte b1=3,b2= ...

  10. 项目实战15.2—企业级堡垒机 jumpserver快速入门

    必备条件 硬件条件 ① 一台安装好 Jumpserver 系统的可用主机(堡垒机) ② 一台或多台可用的 Linux.Windows资产设备(被管理的资产) 服务条件 (1)coco服务 ① 鉴于心态 ...