信号驱动式I/O
信号驱动式I/O是指进程预先告知内核,使得当某个描述符上发生某事时,内核使用信号通知相关进程。
异步I/O是进程执行I/O系统调用(读或写)告知内核启动某个I/O操作,内核启动I/O操作后立刻返回到进程,进程在I/O操作发生期间继续执行,当操作完成或遭遇错误时,内核以进程在I/O系统调用中指定的某种方式通知进程,
对一个套接字使用信号驱动式I/O
- 建立SIGIO信号的信号处理函数。
- 设置该套接字的属主,通常使用fcntl的F_SETOWN命令设置。(因该在设置套接字属主之前建立信号处理函数,因为在调用fcntl后调用signal之前有较小的机会产生SIGIO信号,此时信号被丢弃)
- 开启该套接字的信号驱动式I/O,通常通过使用fcntl的F_SETFL命令打开O_ASYNC标志完成。
UDP套接字的SIGIO信号
在UDP上使用信号驱动式I/O是简单的。SIGIO信号在发生以下事件时产生:
- 数据报到达套接字
- 套接字上发生异步错误
因此当捕获对于某个UDP套接字的SIGIO信号时,我们调用recvfrom或者读入到达的数据报,或者获取发生的异步错误(发生异步错误的前提是udp套接字已连接)
TCP套接字的SIGIO信号
不幸的是,信号驱动式I/O对于TCP套接字近乎无用。问题在于该信号产生的过于频繁,并且它的出现并没有告诉我们发生了什么事情。下列条件均导致对于一个TCP套接字产生SIGIO信号(假设该套接字的信号驱动式I/O已经开启):
- 监听套接字上某个连接请求已经完成
- 某个断连请求已经发起
- 某个断连请求已经完成
- 某个连接之半已经关闭
- 数据到达套接字
- 数据已经从套接字发送走
- 发生某个异步错误
如果一个进程既读又写一个tcp套接字(此时应该设置成非阻塞套接字,防止read或write阻塞),那么当有新数据到达时或数据写出前SIGIO信号均会产生,而且信号处理函数无法区分这两种情况。
应该只对监听TCP套接字使用SIGIO,因为对于监听套接字产生SIGIO的唯一条件是某个新的连接已完成。
使用SIGIO的UDP回射服务器程序
当一个新数据报到达时,SIGIO处理函数读入该数据报,同时记录它到达的时刻,然后将它置于进程内核的另一个队列中,以便服务器循环移走并处理(下图)
client:
#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 ()
typedef struct sockaddr SA; void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + ]; while (fgets(sendline, MAXLINE, fp) != NULL) { sendto(sockfd, sendline, strlen(sendline), , pservaddr, servlen); n = recvfrom(sockfd, recvline, MAXLINE, , NULL, NULL); recvline[n] = ; /* null terminate */
fputs(recvline, stdout);
}
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr; if (argc != )
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[], &servaddr.sin_addr); sockfd = socket(AF_INET, SOCK_DGRAM, ); dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); exit();
}
server:
#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+]; /* 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 = ;
sigset_t zeromask, newmask, oldmask; sockfd = sockfd_arg;
clilen = clilen_arg; for (i = ; 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 = ; 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 == )
sigsuspend(&zeromask); /* wait for datagram to process */ /* 4unblock SIGIO */
sigprocmask(SIG_SETMASK, &oldmask, NULL); sendto(sockfd, dg[iget].dg_data, dg[iget].dg_len, ,
dg[iget].dg_sa, dg[iget].dg_salen); if (++iget >= QSIZE)
iget = ; /* 4block SIGIO */
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
nqueue--;
}
}
static void
sig_io(int signo)
{
ssize_t len;
int nread;
DG *ptr; for (nread = ; ; ) {
if (nqueue >= QSIZE)
ERR_EXIT("receive overflow"); ptr = &dg[iput];
ptr->dg_salen = clilen;
len = recvfrom(sockfd, ptr->dg_data, MAXDG, ,
ptr->dg_sa, &ptr->dg_salen);
if (len < ) {
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 = ; }
cntread[nread]++; /* histogram of # datagrams read per signal */
}
static void
sig_hup(int signo)
{
int i; for (i = ; i <= QSIZE; i++)
printf("cntread[%d] = %ld\n", i, cntread[i]);
}
已收取数据报队列
SIGIO信号处理函数把到达的数据报放入一个队列。该队列是一个DG结构数组,我们把它作为一个环形缓冲区处理。每个DG结构包括指向所收取数据报的一个指针,该数据报的长度,指向含有客户协议地址的某个套接字地址结构的一个指针,该协议地址的大小。静态分配QSIZE个DG结构,dg_echo函数调用malloc动态分配所有数据报和套接字地址结构的内存空间。我们还分配一个诊断用计数器cntread。下图展示了这个DG结构数组,其中假设第一个元素指向一个150字节的数据报,与它关联的套接字地址结构长度为16.
数组下标
iget是主循环将处理的下一个数组元素的下标,iput是信号处理函数将存放到的下一个数组元素的下标,nqueue是队列中供主循环处理的数据报的总数。
初始化已接收数据报队列
把套接字描述符保存在一个全局变量中,因为信号处理函数需要它。初始化已接收数据报队列。
建立信号处理函数并设置套接字标志
为SIGHUP(用于诊断目的)和SIGIO建立信号处理函数。使用fcntl设置套接字的属主,使用ioctl设置信号驱动和非阻塞式I/O标志。
初始化信号集
初始化三个信号集:zeromask(从不改变),oldmask(记录我们阻塞SIGIO时原来的信号掩码)和newmask。使用sigaddset打开newmask中与SIGIO对应的位。
阻塞SIGIO并等待有事可做
调用sigprocmask把进程的当前信号掩码保存到oldmask中,然后把newmask逻辑或到当前信号掩码。这将阻塞SIGIO并返回当前信号掩码。接着进入for循环,并测试nqueue计数器。只要该计数器为0,进程就无事可做,这时我们可以调用sigsuspend。该POSIX函数先内部保存当前信号掩码,再把当前信号掩码设置为它的参数(zeromask)。既然zeromask是一个空信号集,因而所有信号都被开通。sigsuspend在进程捕获一个信号并且该信号的处理函数返回之后才返回。(它是一个不寻常的函数,因为它总是返回EINTR错误)。在返回之前sigsuspend总是把当前信号掩码恢复为调用时刻的值,在本例中就是newmask的值,从而确保sigsuspend返回之后SIGIO继续被阻塞。这时我们可以测试计数器nqueue的理由,因为我们知道测试它时SIGIO信号不可能被递交。
解阻塞SIGIO并发送应答
调用sigprocmask把进程的信号掩码设置为先前保存的值(oldmask),从而解除SIGIO的阻塞。然后调用sendto发送应答。递增iget下标,若其值等于DG结构数组元素数目则将其值置回0。因为我们把该数组作为环形缓冲区对待。注意:修改iget时我们不必阻塞SIGIO,因为只有主循环使用这个下标,信号处理函数从不改动它。
阻塞SIGIO
阻塞SIGIO,递减nqueue。修改nqueue时我们必须阻塞SIGIO,因为它是主循环和信号处理函数共同使用的变量。我们在循环顶部测试nqueue时也需要SIGIO阻塞着。
我们也可以去掉for循环内的两个sigprocmask调用,整个循环期间SIGIO一直阻塞,从而降低了信号处理函数的及时性,数据报不应该因此变动而丢失(假设套接字缓冲区足够大),但是SIGIO信号向进程的递交将在整个阻塞期间一直被拖延。
然而当一个数据报到达导致SIGIO被递交,它的信号处理函数读入该数据报并把它放到供主循环读取的队列中,然而在信号处理函数执行期间,另有两个数据包到达,这一点意味着如果我们在信号处理函数执行(期间确保该信号被阻塞),期间该信号又发生了2次,那么它实际只被递交1次。让我们考虑下述情形。一个数据报到达导致SIGIO被递交。它的信号处理函数读入该数据报并把它放到供主循环读取的队列中。然而在信号处理函数执行期间,另有两个数据报到达,导致SIGIO再产生两次。由于SIGIO被阻塞,当他的信号处理函数返回时,该处理函数仅仅再被调用一次。该信号处理函数的第二次执行读入第二个数据报,第三个数据报则仍然留在套接字接收队列中。第三个数据报被读入的前提条件时由第四个数据报到达。当第四个数据报到达时,被读入并放到供主循环读取的队列中的是第三个而不是第四个数据报。
既然信号时不排队的,开启信号驱动式I/O的描述符通常也被设置为非阻塞式。这个前提下,我们把SIGIO信号处理函数编写成在一个循环中执行读入操作,知道该操作返回EWOULDBLOCK时菜结束循环。
检查队列溢出
如果DG结构数组队列已满,进程就终止。
读入数据报
在非阻塞套接字上调用recvfrom。下标为iput的数组元素用于存放读入的数据报。如果没有可读的数据报,那就break出for循环。
递增计数器和下标
nread是一个计量每次信号递交读入数据报数目的诊断计数器。nqueue是有待主循环处理的数据报数目。
在信号处理函数返回之前,递增与每次信号递交读入数据报数目对应的计数器。当SIGHUP信号被递交时。
信号驱动式I/O的更多相关文章
- UNIX网络编程——信号驱动式I/O
信号驱动式I/O是指进程预先告知内核,使得当某个描述符上发生某事时,内核使用信号通知相关进程. 针对一个套接字使用信号驱动式I/O,要求进程执行以下3个步骤: 建立SIGIO信号的信号处理函数. 设置 ...
- udp套接字使用信号驱动式I/O
信号驱动式I/O的本质就是:进程预先告知内核当某个描写叙述符发生事件时,内核会向该进程发送SIGIO信号通知进程,进程可在信号处理函数中进行处理 进程能够通过fcntl打开O_ASYNC标志或ioct ...
- UNP学习笔记(第二十五章 信号驱动式I/O)
信号驱动式I/O是指进程预先告知内核,使得当某个描述符发生某事时,内核使用信号通知相关进程. 套接字的信号驱动式I/O 针对一个套接字使用信号驱动式I/O(SIGIO)要求进程执行以下3个步骤: 1. ...
- IO模型浅析-阻塞、非阻塞、IO复用、信号驱动、异步IO、同步IO
最近看到OVS用户态的代码,在接收内核态信息的时候,使用了Epoll多路复用机制,对其十分不解,于是从网上找了一些资料,学习了一下<UNIX网络变成卷1:套接字联网API>这本书对应的章节 ...
- UDP信号驱动IO
SIGIO信号 信号驱动式I/O不适用于TCP套接字, 因为产生的信号过于频繁且不能准确判断信号产生的原因. 设置信号驱动需把sockfd的非阻塞与信号驱动属性都打开 server sockfd单独提 ...
- 🍛 餐厅吃饭版理解 IO 模型:阻塞 / 非阻塞 / IO 复用 / 信号驱动 / 异步
IO 概念 一个基本的 IO,它会涉及到两个系统对象,一个是调用这个 IO 的进程对象,另一个就是系统内核 (kernel).当一个 read 操作发生时,它会经历两个阶段: 通过 read 系统调用 ...
- 《Linux/UNIX系统编程手册》第63章 IO多路复用、信号驱动IO以及epoll
关键词:fasync_helper.kill_async.sigsuspend.sigaction.fcntl.F_SETOWN_EX.F_SETSIG.select().poll().poll_wa ...
- UNIX网络编程读书笔记:I/O模型(阻塞、非阻塞、I/O复用、信号驱动、异步)
I/O模型 UNIX下可用的5种I/O模型: (1)阻塞I/O (2)非阻塞I/O (3)I/O复用(select和poll) (4)信号驱动I/O(SIGIO) (5)异步I/O 对于一个套接口上的 ...
- 【网络IO系列】IO的五种模型,BIO、NIO、AIO、IO多路复用、 信号驱动IO
前言 在上一篇文章中,我们了解了操作系统中内核程序和用户程序之间的区别和联系,还提到了内核空间和用户空间,当我们需要读取一条数据的时候,首先需要发请求告诉内核,我需要什么数据,等内核准备好数据之后 , ...
随机推荐
- 番茄助手 最新 Visual Assist X 适应于VS2019 VS2017 VS2015 VS2013 亲测可用
番茄助手 最新 Visual Assist X 适应于VS2019 VS2017 VS2015 VS2013 亲测可用 如图: 颜色已经改变: 下载说明: /* INSTALLATION 0) Uni ...
- HTML基础——基础标签
一.HTML概述 htyper text markup language 即超文本标记语言. 超文本: 就是指页面内可以包含图片.链接,甚至音乐.程序等非文字元素. 标记语言: 标记(标签)构成的语 ...
- Dynamics CRM中的地址知多D?
关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复169或者20151105可方便获取本文,同时可以在第一时间得到我发布的最新的博文信息,follow me! CRM中的地址以前不是很了解,定 ...
- Python输出16进制不带0x补零,整数转16进制,字符串转16进制
Python输出16进制不带0x补零,整数转16进制,字符串转16进制 在开发中,我们偶尔会遇到需要将数据通过控制台打印出来,以检查数据传输的准确性.例如调试服务端刚接到的二进制数据(里面包含很多 ...
- django-xadmin自定义widget插件(自定义详情页字段的显示样式)
有时候我们想要修改xadmin详情页字段的显示方式,比如django默认的ImageField在后台显示的是image的url,我们更希望看到image的缩略图:再比如django将多对多字段显示为多 ...
- Cocos2d-x.3.0开发环境搭建
配置:win7 + VS2012 + Cocos2d-x.3.0 + Cocos Studio v1.4.0.1 前言:本文介绍在上述配置下进行游戏开发的环境搭建.开发语言为C++.如果读者不需要查看 ...
- 08-Node.js学习笔记-静态资源访问
静态资源 服务器端不需要处理,可以直接响应给客户端的资源就是静态资源,例如css,javaScript,image文件 动态资源 相同的请求地址不同的响应资源,这种资源就是动态资源 http://ww ...
- canvas绘制线条详解
canvas详解----绘制线条 <!DOCTYPE html> <html> <head> <title>canvas详解</title> ...
- Flink概述| 配置
流处理技术的演变 在开源世界里,Apache Storm项目是流处理的先锋.Storm提供了低延迟的流处理,但是它为实时性付出了一些代价:很难实现高吞吐,并且其正确性没能达到通常所需的水平,换句话说, ...
- Python集合类型的操作与应用
Python集合类型的操作与应用 一.Python集合类型 Python中的集合类型是一个包含0个或多个数据项的无序的.不重复的数据组合,其中,元素类型只能是固定数据类型,如整数.浮点数.字符串.元组 ...