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。

http://blog.csdn.net/ctthuangcheng/article/details/9569265

时间: 2024-10-29 19:11:09

UNIX网络编程——客户/服务器心搏函数 (转)的相关文章

Linux网络编程客户\服务器设计范式

1.前言 网络编程分为客户端和服务端,服务器通常分为迭代服务器和并发服务器.并发服务器可以根据多进程或多线程进行细分,给每个连接创建一个独立的进程或线程,或者预先分配好多个进程或线程等待连接的请求.今天探讨三种设计范式 (1)迭代服务器 (2)并发服务器,为每个客户请求创建一个进程或线程 (3)预先分配子进程或线程,每个子进程或线程调用accept 3.测试用例: 客户端代码: 1 #include <sys/wait.h> 2 #include <string.h> 3 #inc

Unix网络编程随手记——IP处理函数inet_aton()、gethostbyname()等

IP地址实质上就是一个32位的无符号整数,用如下结构体存放 1 struct in_addr 2 { 3 unsigned int s_addr; 4 }; 由于历史原因,虽然IP地址只是个标量,却用一个结构体来存储. 由于主机可以有不同的主机字节顺序,即大端机或小端机.但TCP/IP定义了统一的网络字节顺序,大端字节顺序. Unix提供了两个函数可在主机字节和网络字节间实现转换: 一.htonl()和ntohl() 在Linux系统下: #include <arpa/inet.h> 有些系统

UNIX网络编程3.5字节操纵函数3.7inet_pton,inet_ntop

【UNIX网络编程(三)】TCP客户/服务器程序示例

上一节给出了TCP网络编程的函数,这一节使用那些基本函数编写一个完成的TCP客户/服务器程序示例. 该例子执行的步骤如下: 1.客户从标准输入读入一行文本,并写给服务器. 2.服务器从网络输入读入这行文本,并回射给客户. 3.客户从网络输入读入这行回射文本,并显示在标准输出上. 用图描述如下: 编写TCP回射服务器程序如下: #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <st

UNIX网络编程笔记(4)—TCP客户/服务器程序示例

TCP客户/服务器程序示例 这一章信息量开始大起来了,粗略来看它实现了简单的TCP客户/服务器程序,里面也有一些费解的细节. 1.概述 完整的TCP客户/服务器程序示例.这个简单的例子将执行如下步骤的一个回射服务器(这里的回射服务器就是服务简单的把客户端发送的消息返回给客户): 1)客户从标准输入读入一行文本,并写给服务器 2)服务器从网络输入读入这行文本,并回射给客户 3)客户从网络输入读入这行回射文本,并显示在标准输出上 这样实际上就构成了一个全双工的TCP连接. 本章就围绕了这个简单的TC

unix网络编程各种TCP客户-服务器程序设计实例附环境搭建和编译方法(一)

一,到http://download.csdn.net/detail/ts173383201/4505201去下载源代码,然后解压: 二,cd到你解压后的文件夹下,就是有configure的那个目录下,执行命令./configure: 三,执行cd lib跳到lib目录下,执行make命令,会在上层目录(就是刚才有configure那个目录)生成libunp.a文件 四,复制这个静态库libunp.a到/usr/lib/和/usr/lib64/中; 五,接下来在目录中找到unp.h和config

UNIX网络编程入门——TCP客户/服务器程序详解

前言 最近刚开始看APUE和UNP来学习socket套接字编程,因为网络这方面我还没接触过,要等到下学期才上计算机网络这门课,所以我就找了本教材啃了一两天,也算是入了个门. 至于APUE和UNP这两本书,书是好书,网上也说这书是给进入unix网络编程领域初学者的圣经,这个不可置否,但这个初学者,我认为指的是接受过完整计算机本科教育的研究生初学者,需要具有完整计算机系统,体系结构,网络基础知识.基础没打好就上来啃书反而会适得其反,不过对于我来说也没什么关系,因为基础课也都上得差不多了,而且如果书读

UNIX网络编程卷1 回射客户程序 TCP客户程序设计范式

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 下面我会介绍同一个使用 TCP 协议的客户端程序的几个不同版本,分别是停等版本.select 加阻塞式 I/O 版本. 非阻塞式 I/O 版本.fork 版本.线程化版本.它们都由同一个 main 函数调用来实现同一个功能,即回射程序客户端. 它从标准输入读入一行文本,写到服务器上,读取服务器对该行的回射,并把回射行写到标准输出上. 其中,非阻塞式 I/O 版本是所有版本中执行速度最快的,

【LINUX/UNIX网络编程】之简单多线程服务器(多人群聊系统)

RT,Linux下使用c实现的多线程服务器.这个真是简单的不能再简单的了,有写的不好的地方,还希望大神轻拍.(>﹏<) 本学期Linux.unix网络编程的第四个作业. 先上实验要求: [实验目的] 1.熟练掌握线程的创建与终止方法: 2.熟练掌握线程间通信同步方法: 3.应用套接字函数完成多线程服务器,实现服务器与客户端的信息交互. [实验内容] 通过一个服务器实现最多5个客户之间的信息群发. 服务器显示客户的登录与退出: 客户连接后首先发送客户名称,之后发送群聊信息: 客户输入bye代表退