《UNIX网络编程 卷1》 笔记: 信号驱动式I/O
来源:互联网 发布:java中final和static 编辑:程序博客网 时间:2024/06/05 01:58
信号驱动式I/O是指进程预先告知内核,使得当某个描述符上发生事件时,内核使用信号通知该进程。
针对一个套接字使用信号驱动式I/O(SIGIO)的步骤如下:
1. 建立SIGIO信号的处理函数
2. 设置该套接字的属主,通常使用fcntl的F_SETFL命令设置。
3. 开启套接字的信号驱动式I/O,通常使用fcntl的F_SETFL命令打开O_ASYNC标志。
对于TCP套接字,信号式驱动几乎无用,因为很多事件发生都会产生SIGIO信号,而我们无法区分。
对于UDP套接字,SIGIO信号在发生以下事件时产生:
1. 数据报到达套接字
2. 套接字上发生异步错误(前提是套接字已连接)
因此我们在SIGIO信号处理函数中调用recvfrom就能读取到达的数据报或者获取异步错误。
在基本UDP套接字编程一节中,我们实现了一个UDP回射服务器,它是以等-停方式工作的,即读取一个数据报,将它回射回去,再等待读取下一个数据报。
本节我们使用信号驱动式I/O实现一个UDP回射服务器程序,它完全由信号SIGIO驱动。一个SIGIO信号产生,说明套接字收到了一个或多个数据报(Linux信号不排队),为了读取这些数据报,我们把套接字设置成非阻塞,调用recvfrom循环读取数据报放到一个队列中,直到返回EWOULDBLOCK。主函数(dg_echo函数)调用sigsuspend等待SIGIO信号发生,然后从队列中取出所有的数据报回射出去。代码如下:
#include "unp.h"static int sockfd;#define QSIZE 8#define MAXDG 4096typedef struct {void *dg_data; /*存放数据报的缓冲区*/size_t dg_len; /*数据长度*/struct sockaddr *dg_sa; /*指向客户套接字地址结构*/socklen_t dg_salen; /*套接字地址结构大小*/} DG;static DG dg[QSIZE]; /*处理数据报队列的大小*/static int iget;static int iput;static int nqueue; /*队列大小*/static socklen_t clilen;static void sig_io(int);static void sig_hup(int);void dg_echo(int sockfd_arg, SA *pcliaddr, socklen_t clilen_arg){int i;int on = 1;sigset_t zeromask, newmask, oldmask;sockfd = sockfd_arg;clilen = clilen_arg;/*初始化队列*/for (i = 0; i < QSIZE; i++) {dg[i].dg_data = Malloc(MAXDG);dg[i].dg_sa = Malloc(clilen);dg[i].dg_salen = clilen;}iget = iput = nqueue = 0;Signal(SIGIO, sig_io);Fcntl(sockfd, F_SETOWN, getpid()); /*设置套接字属主*/Ioctl(sockfd, FIOASYNC, &on); /*设置套接字异步访问标志*/Ioctl(sockfd, FIONBIO, &on); /*设置套接字非阻塞标志*/Sigemptyset(&zeromask);Sigemptyset(&oldmask);Sigemptyset(&newmask);Sigaddset(&newmask, SIGIO);/*阻塞SIGIO信号*/Sigprocmask(SIG_BLOCK, &newmask, &oldmask);for ( ; ; ) {while (nqueue == 0) /*当队列为空,等待信号发生*/sigsuspend(&zeromask);/*解阻塞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;/*阻塞SIGIO信号*/Sigprocmask(SIG_BLOCK, &newmask, &oldmask);nqueue--;}}static void sig_io(int sigio){ssize_t len;int nread;DG *ptr;for (nread = 0; ; ) {if (nqueue >= QSIZE)err_quit("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;elseerr_sys("recvfrom error");}ptr->dg_len = len;nread++;nqueue++;if (++iput >= QSIZE)iput = 0;}}这段代码有几个很关键的地方:
1. 主程序for循环前要先阻塞SIGIO信号。如果没有阻塞SIGIO信号,在执行for循环的前两行语句,我们可能测试nqueue时发现它为0,但是刚测试完毕SIGIO信号就递交了,导致nqueue被设置为1.我们接着调用sigsuspend进入睡眠,这样实际上我们就错过了这个信号,除非还有信号发生,否则我们将永远不能从sigsuspend调用中被唤醒。也就是代码包含有竞争条件。
2. sigsuspend函数的功能是原子性地将进程投入睡眠,并把它的信号掩码设置成zeromask,直到某个信号发生并且该信号的信号处理函数返回之后才返回,并将信号掩码恢复成调用该函数之前的信号掩码。由于在信号处理函数中已经读取了数据报,nqueue不为零,所以while循环必然不成立。之后就从队列中取出数据报发送出去。
3. nqueue变量由主函数和信号处理函数共享,所以在修改时,一定要先阻塞SIGIO信号。而iget变量由主函数独有,所以在修改之前不需要阻塞SIGIO信号。
- 《UNIX网络编程 卷1》 笔记: 信号驱动式I/O
- UNIX网络编程——信号驱动式I/O
- 《UNIX网络编程 卷1》 笔记: 高级I/O函数
- 《Unix网络编程》卷1:套接字联网API(第3版):广播、多播、信号驱动I/O、线程
- 《UNIX网络编程 卷1》 笔记: 非阻塞式I/O
- 《UNIX网络编程 卷1》 笔记: I/O复用 select函数
- Linux网络编程---信号驱动I/O
- UNIX网络编程卷1:套接字联网-第16章:非阻塞式I/O
- UNIX网络编程卷一 笔记 第六章 第6章 I/O复用
- 《UNIX网络编程 卷2》 笔记: 使用内存映射I/O实现信号量
- 《UNIX网络编程 卷2》 笔记: 使用内存映射I/O实现消息队列
- UNIX网络编程卷一:第八章 I/O UDP
- UNIX网络编程卷一:第十四章 高级I/O
- 《UNIX网络编程卷1》读书笔记--第六章I/O复用:select和poll函数
- Unix网络编程(卷1)—笔记
- 《UNIX网络编程 卷1》 笔记: 广播
- 《UNIX网络编程 卷1》 笔记: 线程
- Linux/UNIX网络编程笔记 - I/O复用
- 回顾基础知识--第一章:Activity的生命周期和启动模式
- 用SqlDataReader填充Combobox
- java锁机制
- Spring AOP——面向切面编程(下)
- Linux常用命令
- 《UNIX网络编程 卷1》 笔记: 信号驱动式I/O
- 深度学习 —— 多层感知机
- EA&UML日拱一卒--序列图(Sequence Diagram)::连续
- 商城项目实战05:zookeeper集群
- PCB中如何区分高速信号与低速信号?
- 51nod 1806 wangyurzee的树(purfer,容斥原理)
- java Serializable详解
- 论文相关
- tf函数