UNP学习笔记(第二十五章 信号驱动式I/O)

信号驱动式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);
View Code

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 }
View Code

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 }
View Code

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
", i, cntread[i]);
8 }
View Code

对于TCP套接字的SIGIO信号

因为对于TCP套接字,该信号产生得过于频繁,并且它的出现并没有告诉我们发生了上面事情。因此信号驱动式I/O对于TCP套接字几乎没用。

原文地址:https://www.cnblogs.com/runnyu/p/4670358.html