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函数设置客户的心搏特性,其中第二个参数是以秒为单位的轮询频率,第三个参数是放弃当前链接以前应该经历的持续无响应轮询次数。socket

#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.ui

SIGURG处理函数
21-32   本信号在某个带外通知到达时产生。咱们尝试读入相应的带外字节,不过若是它尚未到达(EWOULDBLOCK),那也没有关系。注意,咱们不采用在线接收带外数据方式,由于这种方式会干扰客户读取它的正常数据。既然服务器仍然存活着,咱们把nprobes重置为0.spa

SIGALRM处理函数
33-43   本信号以恒定的间隔产生。递增计数器nprobes,若是达到maxnprobes,咱们就认定服务器主机或者已经崩溃,或者再也不可达。咱们这里是直接结束客户进程。做为带外数据发送一个含有字符1的字节(该值没有任何隐含意义),再执行alarm调度下一个SIGALRM。.net

 

      下面是服务器程序的心搏函数。设计

#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几乎与客户的心搏初始化函数同样。code

SIGURG处理函数
19-31   服务器收到一个带外通知后就尝试读入相应的带外字节。就像客户同样,若是该带外字节尚未到达,那也没有声明关系。服务器把读入的带外字节做为带外数据回送给客户。注意,若是recv返回EWOULDBLOCK错误,那么自动变量C碰巧是什么就回送什么。既然咱们不把带外字节的值用于任何目的,这么处置就不会有问题。重要的是发送1字节的带外数据自己,而不是该字节究竟是什么。既然刚收到客户仍然存活着的通知,咱们把nprobes重置为0.

SIGALRM处理函数32-41    递增nprobes,若是达到由调用者指定的maxnalarms值,那就终止服务器进程,不然调度下一个SIGALRM。

相关文章
相关标签/搜索