UNIX网络编程——信号驱动式I/O
信号驱动式I/O是指进程预先告知内核,使得当某个描述符上发生某事时,内核使用信号通知相关进程。
针对一个套接字使用信号驱动式I/O,要求进程执行以下3个步骤:
- 建立SIGIO信号的信号处理函数。
- 设置该套接字的属主,通常使用fcntl的F_SETOWN命令设置。
- 开启该套接字的信号驱动式I/O,通常通过使用fcntl的F_SETFL命令打开O_ASYNC标志完成。
1.对于UDP套接字的SIGIO信号
在UDP上使用信号驱动式I/O是简单的。SIGIO信号在发生以下事件时产生:
(1)数据报到达套接字;
(2)套接字上发生异步错误。
因此当捕获对于某个UDP套接字的SIGIO信号时,我们调用recvfrom或者读入到达的数据报,或者获取发生的异步错误。
2.对于TCP套接字的SIGIO信号
不幸的是,信号驱动式I/O对于TCP套接字近乎无用。问题在于该信号产生的过于频繁,并且它的出现并没有告诉我们发生了什么事情。下列条件均导致对于一个TCP套接字产生SIGIO信号(假设该套接字的信号驱动式I/O已经开启):
(1)监听套接字上某个连接请求已经完成;
(2)某个断连请求已经发起;
(3)某个断连请求已经完成;
(4)某个连接之半已经关闭;
(5)数据到达套接字;
(6)数据已经从套接字发送走
(7)发生某个异步错误。
3.使用SIGIO的UDP回射服务器程序
客户端程序:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<signal.h>
#include <fcntl.h>
#define MAXLINE 1024
#define SERV_PORT 3333 #define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
typedef struct sockaddr SA; void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1]; while (fgets(sendline, MAXLINE, fp) != NULL) { sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); recvline[n] = 0; /* null terminate */
fputs(recvline, stdout);
}
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr; if (argc != 2)
ERR_EXIT("usage: udpcli <IPaddress>"); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr); sockfd = socket(AF_INET, SOCK_DGRAM, 0); dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); exit(0);
}
服务器程序:
#include "unp.h" static int sockfd; #define QSIZE 8 /* size of input queue */
#define MAXDG 4096 /* max datagram size */ typedef struct {
void *dg_data; /* ptr to actual datagram */
size_t dg_len; /* length of datagram */
struct sockaddr *dg_sa; /* ptr to sockaddr{} w/client's address */
socklen_t dg_salen; /* length of sockaddr{} */
} DG;
static DG dg[QSIZE]; /* queue of datagrams to process */
static long cntread[QSIZE+1]; /* diagnostic counter */ static int iget; /* next one for main loop to process */
static int iput; /* next one for signal handler to read into */
static int nqueue; /* # on queue for main loop to process */
static socklen_t clilen;/* max length of sockaddr{} */ static void sig_io(int);
static void sig_hup(int);
void
dg_echo(int sockfd_arg, SA *pcliaddr, socklen_t clilen_arg)
{
int i;
const int on = 1;
sigset_t zeromask, newmask, oldmask; sockfd = sockfd_arg;
clilen = clilen_arg; for (i = 0; i < QSIZE; i++) { /* init queue of buffers */
dg[i].dg_data = malloc(MAXDG);
dg[i].dg_sa = malloc(clilen);
dg[i].dg_salen = clilen;
}
iget = iput = nqueue = 0; signal(SIGHUP, sig_hup);
signal(SIGIO, sig_io);
fcntl(sockfd, F_SETOWN, getpid());
ioctl(sockfd, FIOASYNC, &on);
ioctl(sockfd, FIONBIO, &on); sigemptyset(&zeromask); /* init three signal sets */
sigemptyset(&oldmask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGIO); /* signal we want to block */ sigprocmask(SIG_BLOCK, &newmask, &oldmask);
for ( ; ; ) {
while (nqueue == 0)
sigsuspend(&zeromask); /* wait for datagram to process */ /* 4unblock SIGIO */
sigprocmask(SIG_SETMASK, &oldmask, NULL); sendto(sockfd, dg[iget].dg_data, dg[iget].dg_len, 0,
dg[iget].dg_sa, dg[iget].dg_salen); if (++iget >= QSIZE)
iget = 0; /* 4block SIGIO */
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
nqueue--;
}
}
static void
sig_io(int signo)
{
ssize_t len;
int nread;
DG *ptr; for (nread = 0; ; ) {
if (nqueue >= QSIZE)
ERR_EXIT("receive overflow"); ptr = &dg[iput];
ptr->dg_salen = clilen;
len = recvfrom(sockfd, ptr->dg_data, MAXDG, 0,
ptr->dg_sa, &ptr->dg_salen);
if (len < 0) {
if (errno == EWOULDBLOCK)
break; /* all done; no more queued to read */
else
ERR_EXIT("recvfrom error");
}
ptr->dg_len = len; nread++;
nqueue++;
if (++iput >= QSIZE)
iput = 0; }
cntread[nread]++; /* histogram of # datagrams read per signal */
}
static void
sig_hup(int signo)
{
int i; for (i = 0; i <= QSIZE; i++)
printf("cntread[%d] = %ld\n", i, cntread[i]);
}
已收取数据报队列
5-15 SIGIO信号处理函数把到达的数据报放入一个队列。该队列是一个DG结构数组,我们把它作为一个环形缓冲区处理。每个DG结构包括指向所收取数据报的一个指针,该数据报的长度,指向含有客户协议地址的某个套接字地址结构的一个指针,该协议地址的大小。静态分配QSIZE个DG结构,dg_echo函数调用malloc动态分配所有数据报和套接字地址结构的内存空间。我们还分配一个诊断用计数器cntread。下图展示了这个DG结构数组,其中假设第一个元素指向一个150字节的数据报,与它关联的套接字地址结构长度为16.
数组下标
17-19 iget是主循环将处理的下一个数组元素的下标,iput是信号处理函数将存放到的下一个数组元素的下标,nqueue是队列中供主循环处理的数据报的总数。
初始化已接收数据报队列
34-39 把套接字描述符保存在一个全局变量中,因为信号处理函数需要它。初始化已接收数据报队列。
建立信号处理函数并设置套接字标志
41-45 为SIGHUP(用于诊断目的)和SIGIO建立信号处理函数。使用fcntl设置套接字的属主,使用ioctl设置信号驱动和非阻塞式I/O标志。
初始化信号集
47-50 初始化三个信号集:zeromask(从不改变),oldmask(记录我们阻塞SIGIO时原来的信号掩码)和newmask。使用sigaddset打开newmask中与SIGIO对应的位。
阻塞SIGIO并等待有事可做
52-55 调用sigprocmask把进程的当前信号掩码保存到oldmask中,然后把newmask逻辑或到当前信号掩码。这将阻塞SIGIO并返回当前信号掩码。接着进入for循环,并测试nqueue计数器。只要该计数器为0,进程就无事可做,这时我们可以调用sigsuspend。该POSIX函数先内部保存当前信号掩码,再把当前信号掩码设置为它的参数(zeromask)。既然zeromask是一个空信号集,因而所有信号都被开通。sigsuspend在进程捕获一个信号并且该信号的处理函数返回之后才返回。(它是一个不寻常的函数,因为它总是返回EINTR错误)。在返回之前sigsuspend总是把当前信号掩码恢复为调用时刻的值,在本例中就是newmask的值,从而确保sigsuspend返回之后SIGIO继续被阻塞。这时我们可以测试计数器nqueue的理由,因为我们知道测试它时SIGIO信号不可能被递交。
解阻塞SIGIO并发送应答
57-66 调用sigprocmask把进程的信号掩码设置为先前保存的值(oldmask),从而解除SIGIO的阻塞。然后调用sendto发送应答。递增iget下标,若其值等于DG结构数组元素数目则将其值置回0。因为我们把该数组作为环形缓冲区对待。注意:修改iget时我们不必阻塞SIGIO,因为只有主循环使用这个下标,信号处理函数从不改动它。
阻塞SIGIO
67-68 阻塞SIGIO,递减nqueue。修改nqueue时我们必须阻塞SIGIO,因为它是主循环和信号处理函数共同使用的变量。我们在循环顶部测试nqueue时也需要SIGIO阻塞着。
编写本信号处理函数时我们遇到的问题是POSIX信号通常不排队。这一点意味着如果我们在信号处理函数执行(期间确保该信号被阻塞),期间该信号又发生了2次,那么它实际只被递交1次。让我们考虑下述情形。一个数据报到达导致SIGIO被递交。它的信号处理函数读入该数据报并把它放到供主循环读取的队列中。然而在信号处理函数执行期间,另有两个数据报到达,导致SIGIO再产生两次。由于SIGIO被阻塞,当他的信号处理函数返回时,该处理函数仅仅再被调用一次。该信号处理函数的第二次执行读入第二个数据报,第三个数据报则仍然留在套接字接收队列中。第三个数据报被读入的前提条件时由第四个数据报到达。当第四个数据报到达时,被读入并放到供主循环读取的队列中的是第三个而不是第四个数据报。
既然信号时不排队的,开启信号驱动式I/O的描述符通常也被设置为非阻塞式。这个前提下,我们把SIGIO信号处理函数编写成在一个循环中执行读入操作,知道该操作返回EWOULDBLOCK时菜结束循环。
检查队列溢出
79-80 如果DG结构数组队列已满,进程就终止。
读入数据报
82-92 在非阻塞套接字上调用recvfrom。下标为iput的数组元素用于存放读入的数据报。如果没有可读的数据报,那就break出for循环。
递增计数器和下标
92-97 nread是一个计量每次信号递交读入数据报数目的诊断计数器。nqueue是有待主循环处理的数据报数目。
100 在信号处理函数返回之前,递增与每次信号递交读入数据报数目对应的计数器。当SIGHUP信号被递交时。
UNIX网络编程——信号驱动式I/O的更多相关文章
- UNIX网络编程——非阻塞式I/O(套接字)
套接字的默认状态是阻塞的.这就意味着当发出一个不能立即完成的套接字调用时,其进程将被投入睡眠,等待相应的操作完成.可能阻塞的套接字调用可分为以下4类: (1)输入操作,包括read,readv,rec ...
- Unix网络编程--卷一:套接字联网API
UNIX网络编程--卷一:套接字联网API 本书面对的读者是那些希望自己编写的程序能够使用成为套接字(socket)的API进行彼此通信的人. 目录: 0.准备环境 1.简介 2.传输层:TCP.UD ...
- 【unix网络编程第三版】阅读笔记(五):I/O复用:select和poll函数
本博文主要针对UNP一书中的第六章内容来聊聊I/O复用技术以及其在网络编程中的实现 1. I/O复用技术 I/O多路复用是指内核一旦发现进程指定的一个或者多个I/O条件准备就绪,它就通知该进程.I/O ...
- UNIX网络编程——I/O复用:select和poll函数
我们看到TCP客户同时处理两个输入:标准输入和TCP套接字.我们遇到的问题是就在客户阻塞于(标准输入上)fgets调用,服务器进程会被杀死.服务器TCP虽然正确的给客户TCP发送了一个FIN,但是既然 ...
- 《Unix网络编程》读书笔记
UDP和TCP UDP(User Datagram Protocol,用户数据报协议)是一个无连接协议,不保证UDP数据报会到达其最终目的地,不保证各数据报的先后顺序跨网络后保持不变,也不保证每个数据 ...
- 《Unix网络编程卷1:套接字联网API》读书笔记
第一部分:简介和TCP/IP 第1章:简介 第2章:传输层:TCP.UDP和SCTP TCP:传输控制协议,复杂.可靠.面向连接协议 UDP:用户数据报协议,简单.不可靠.无连接协议 SCTP:流控制 ...
- [转载] 读《UNIX网络编程 卷1:套接字联网API》
原文: http://cstdlib.com/tech/2014/10/09/read-unix-network-programming-1/ 文章写的很清楚, 适合初学者 最近看了<UNIX网 ...
- UNIX网络编程——非阻塞connect:时间获取客户程序
#include "unp.h" int connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec) ...
- UNIX网络编程——fcntl函数
fcntl函数提供了与网络编程相关的如下特性: 非阻塞式I/O. 通过使用F_SETFL命令设置O_NONBLOCK文件状态标志,我们可以把一个套接字设置为非阻塞型. 信号驱动式I/O. 通过使用F ...
随机推荐
- 后缀自动机模板(SPOJ1811)
用后缀自动机实现求两个串的最长公共子串. #include <cstdio> #include <algorithm> ; char s[N]; ]; int main() { ...
- day4 liaoxuefeng---模块
一.模块 二.常用内建模块 三.常用第三方模块
- 面试题2:实现Singleton模式
题目:设计一个类,我们只能生成该类的一个实例.
- ELK 6.2.4搭建
开源实时日志分析ELK平台能够完美的解决我们上述的问题,ELK由ElasticSearch.Logstash和Kiabana三个开源工具组成.官方网站:https://www.elastic.co/p ...
- 用js实现排列组合
在leetcode上看到一个题,代码实现排列组合的. 记得大学上课时候,就用c写过,现在用js试试,顺便看看耗时. 先看看3的阶乘: function permute(temArr,testArr){ ...
- zookeeper工作机制
Zookeeper Zookeeper概念简介: Zookeeper是为用户的分布式应用程序提供协调服务的 zookeeper是为别的分布式程序服务的 Zookeeper本身就是一个分布式程序(只要有 ...
- 基于无域故障转移群集 配置高可用SQLServer 2016数据库
基于上次的文章搭建的环境,可以在这里:http://www.cnblogs.com/DragonStart/p/8275182.html看到上次的文章. 演示环境 1. 配置一览 Key Value ...
- Python中strip()、lstrip()、rstrip()用法详解
Python中有三个去除头尾字符.空白符的函数,它们依次为: strip: 用来去除头尾字符.空白符(包括\n.\r.\t.' ',即:换行.回车.制表符.空格)lstrip:用来去除开头字符.空白符 ...
- Linux下常用的配置
本文主要给出的都是一些常用的Linux配置,系统版本是基于CentOs6.3,供自己复习和新人学习,不当之处还请指正. vmware tools安装 虚拟机--->安装vmware tools ...
- 0428css样式
CSS样式表|-引入的三种方式|--内联样式|----标签内部(空格style)|--内嵌样式|----<head></head>标签内部(<style></ ...