信号驱动式I/O是指进程预先告知内核,使得当某个描述符发生某事时,内核使用信号通知相关进程。
套接字的信号驱动式I/O
针对一个套接字使用信号驱动式I/O(SIGIO)要求进程执行以下3个步骤:
1.建立SIGIO信号的信号处理函数
2.设置该套接字的属主,通常使用fcntl的F_SETOWN命令设置
3.开启该套接字的信号驱动式I/O,通常通过使用fcntl的F_SETFL命令打开O_ASYNC标志完成
对于UDP套接字的SIGIO信号
在UDP上使用信号驱动式I/O是简单得。SIGIO信号在发生以下事件时产生:
1.数据报到达套接字
2.套接字上发生异步错误
因此当捕获对于某个UDP套接字的SIGIO信号时,我们调用recvfrom读入到达的数据报或者获取发生的异步错误。
使用SIGIO的UDP回射服务器程序
下面是构建一个UDP服务器的两种方式,我们的程序使用的右面的方式
1.全局声明
SIGIO信号处理函数把到达的数据放入一个队列。该队列是一个DG数据数组,我们把它作为一个环形缓冲区处理
每个DG结构包括指向所收取数据报的一个指针、该数据包的长度、指向含有客户协议地址的某个套接字地址结构的一个指针、该协议地址的大小。
iget是主循环将处理的下一个数组元素的下标,iput是信号处理函数将存放到下一个数组元素的下标,nqueue是队列中共主循环处理的数据报的总数
1 #include "unp.h" 2 3 static int sockfd; 4 5 #define QSIZE 8 /* size of input queue */ 6 #define MAXDG 4096 /* max datagram size */ 7 8 typedef struct { 9 void *dg_data; /* ptr to actual datagram */ 10 size_t dg_len; /* length of datagram */ 11 struct sockaddr *dg_sa; /* ptr to sockaddr{} w/client's address */ 12 socklen_t dg_salen; /* length of sockaddr{} */ 13 } DG; 14 static DG dg[QSIZE]; /* queue of datagrams to process */ 15 static long cntread[QSIZE+1]; /* diagnostic counter */ 16 17 static int iget; /* next one for main loop to process */ 18 static int iput; /* next one for signal handler to read into */ 19 static int nqueue; /* # on queue for main loop to process */ 20 static socklen_t clilen;/* max length of sockaddr{} */ 21 22 static void sig_io(int); 23 static void sig_hup(int);
2.dg_echo函数
1 void 2 dg_echo(int sockfd_arg, SA *pcliaddr, socklen_t clilen_arg) 3 { 4 int i; 5 const int on = 1; 6 sigset_t zeromask, newmask, oldmask; 7 8 sockfd = sockfd_arg; 9 clilen = clilen_arg; 10 11 for (i = 0; i < QSIZE; i++) { /* init queue of buffers */ 12 dg[i].dg_data = Malloc(MAXDG); 13 dg[i].dg_sa = Malloc(clilen); 14 dg[i].dg_salen = clilen; 15 } 16 iget = iput = nqueue = 0; 17 18 Signal(SIGHUP, sig_hup); 19 Signal(SIGIO, sig_io); 20 Fcntl(sockfd, F_SETOWN, getpid()); 21 Ioctl(sockfd, FIOASYNC, &on); 22 Ioctl(sockfd, FIONBIO, &on); 23 24 Sigemptyset(&zeromask); /* init three signal sets */ 25 Sigemptyset(&oldmask); 26 Sigemptyset(&newmask); 27 Sigaddset(&newmask, SIGIO); /* signal we want to block */ 28 29 Sigprocmask(SIG_BLOCK, &newmask, &oldmask); 30 for ( ; ; ) { 31 while (nqueue == 0) 32 sigsuspend(&zeromask); /* wait for datagram to process */ 33 34 /* 4unblock SIGIO */ 35 Sigprocmask(SIG_SETMASK, &oldmask, NULL); 36 37 Sendto(sockfd, dg[iget].dg_data, dg[iget].dg_len, 0, 38 dg[iget].dg_sa, dg[iget].dg_salen); 39 40 if (++iget >= QSIZE) 41 iget = 0; 42 43 /* 4block SIGIO */ 44 Sigprocmask(SIG_BLOCK, &newmask, &oldmask); 45 nqueue--; 46 } 47 }
3.sig_io信号处理函数
因为信号时不排队的,开启信号驱动式I/O的描述符通常也被设置为非阻塞式。
这个前提下,我们把SIGIO信号处理函数编写成一个循环中执行读入操作,直到操作返回EWOULDBLOCK时才结束循环。
1 static void 2 sig_io(int signo) 3 { 4 ssize_t len; 5 int nread; 6 DG *ptr; 7 8 for (nread = 0; ; ) { 9 if (nqueue >= QSIZE) 10 err_quit("receive overflow"); 11 12 ptr = &dg[iput]; 13 ptr->dg_salen = clilen; 14 len = recvfrom(sockfd, ptr->dg_data, MAXDG, 0, 15 ptr->dg_sa, &ptr->dg_salen); 16 if (len < 0) { 17 if (errno == EWOULDBLOCK) 18 break; /* all done; no more queued to read */ 19 else 20 err_sys("recvfrom error"); 21 } 22 ptr->dg_len = len; 23 24 nread++; 25 nqueue++; 26 if (++iput >= QSIZE) 27 iput = 0; 28 29 } 30 cntread[nread]++; /* histogram of # datagrams read per signal */ 31 }
4.sig_hup信号处理函数
1 static void 2 sig_hup(int signo) 3 { 4 int i; 5 6 for (i = 0; i <= QSIZE; i++) 7 printf("cntread[%d] = %ld\n", i, cntread[i]); 8 }
对于TCP套接字的SIGIO信号
因为对于TCP套接字,该信号产生得过于频繁,并且它的出现并没有告诉我们发生了上面事情。因此信号驱动式I/O对于TCP套接字几乎没用。