I/O多路复用详解(二)

孔令春 posted @ 2009年10月15日 20:57 in 内功修行 with tags pselect poll , 418 阅读

 2、pselect函数

     pselect函数是由POSIX发明的,如今许多Unix变种都支持它。 

?
1
2
3
4
5
6
#include <sys/select.h>
#include <signal.h>
#include <time.h>
  
int pselect( int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timespec *timeout, const sigset_t *sigmask);
                返回:就绪描述字的个数,0-超时,-1-出错

pselect相对于通常的select有两个变化:

1、pselect使用timespec结构,而不使用timeval结构。timespec结构是POSIX的又一个发明。

     struct timespec{

            time_t tv_sec;     //seconds

            long    tv_nsec;    //nanoseconds

     };

     这两个结构的区别在于第二个成员:新结构的该成员tv_nsec指定纳秒数,而旧结构的该成员tv_usec指定微秒数。

2、pselect函数增加了第六个参数:一个指向信号掩码的指针。该参数允许程序先禁止递交某些信号,再测试由这些当前被禁止的信号处理函数设置的全局变量,然后调用pselect,告诉它重新设置信号掩码。

      关于第二点,考虑下面的例子,这个程序的SIGINT信号处理函数仅仅设置全局变量intr_flag并返回。如果我们的进程阻塞于select调用,那么从信号处理函数的返回将导致select返回EINTR错误。然而调用select时,代码看起来大体如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
if ( intr_flag )
    handle_intr();
  
if ( (nready = select(...)) < 0 )
{
    if ( errno == EINTR )
    {
       if ( intr_flag )
          handle_intr();
    }
    ...
}

    问题是在测试intr_flag和调用select之间如果有信号发生,那么要是select永远阻塞,该信号就会丢失。有了pselect后,我们可以如下可靠地编写这个例子的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sigset_t newmask, oldmask, zeromask;
  
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
  
sigprocmask(SIG_BLOCK, &newmask, &oldmask); //block SIGINT
if (intr_flag)
    handle_intr();
  
if ( (nready = pselect(...,&zeromask)) < 0 )
{
     if ( errno == EINTR)
     {
        if (intr_flag)
            handle_intr();
     }
     ...
}

       在测试intr_flag变量之前,我们阻塞SIGINT。当pselect被调用时,它先以空集(zeromask)取代进程的信号掩码,再检查描述字,并可能进入睡眠。然而当pselect函数返回时,进程的信号掩码又被重置为调用pselect之前的值(即SIGINT被阻塞)。

 

 3、poll函数

      poll函数起源于SVR3,最初局限于流设备。SVR4取消了这种限制,允许poll工作在任何描述字上。poll提供的功能与select类似,不过在处理流设备时,它能够提供额外的信息。

?
1
2
3
4
#include <poll.h>
  
int poll( struct pollfd *fdarray, unsigned long nfds, int timeout);
                返回:就绪描述字的个数,0-超时,-1-出错

      第一个参数是指向一个结构数组第一个元素的指针。每个数组元素都是一个pollfd结构,用于指定测试某个给定描述字fd的条件。

           struct pollfd{

                    int fd;              //descriptor to check

                    short events;    //events of interest on fd

                    short revents;   //events that occurred on fd 

            };

       要测试的条件由events成员指定,而返回的结果则在revents中存储。常用条件及含意说明如下:

poll函数可用的测试值
常量 说明
POLLIN 普通或优先级带数据可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读
POLLPRI 高优先级数据可读
POLLOUT 普通数据可写
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写
POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件

 注意:后三个只能作为描述字的返回结果存储在revents中,而不能作为测试条件用于events中。

 

     第二个参数nfds是用来指定数组fdarray的长度。

     最后一个参数timeout是指定poll函数返回前等待多长时间。它的取值如下:

timeout值 说明
INFTIM 永远等待
0 立即返回,不阻塞进程
>0 等待指定数目的毫秒数

    一个使用poll的网络程序例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/**
   *TCP回射服务器的服务端程序
   */ 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>
#include <poll.h>   //for poll 
  
#define LISTENQ 1024
#define MAXLINE 1024
#define OPEN_MAX 50000
#define SERVER_PORT 3333  
  
#ifndef INFTIM     /*按照书上解释:POSIX规范要求INFTIM在头文件<poll.h>中定义,不过*/
#define INFTIM -1  /*许多系统仍然把它定义在头文件<sys/stropts.h>中,但是经过我的测试*/
#endif             /*即使都包含这两个文件,编译器也找不到,不知何解。索性自己定义了。*/
  
int main( int argc, char *argv[])
{
     int i, maxi, listenfd, connfd, sockfd;
     int nready;
     ssize_t n;
     socklen_t clilen;
     struct sockaddr_in servaddr, cliaddr;
     struct hostent  *hp;
     char buf[BUFSIZ];
     struct pollfd client[OPEN_MAX]; /*用于poll函数第一个参数的数组*/
  
     if ( argc != 2 )
     {
         printf ( "Please input %s <hostname>/n" , argv[0]);
         exit (1);
     }
      
     //创建socket
     if ( (listenfd = socket(AF_INET, SOCK_STREAM,0)) < 0 )
     {
         printf ( "Create socket error!/n" );
         exit (1);
     }
  
     //设置服务器地址结构
     bzero(&servaddr, sizeof (servaddr));
     servaddr.sin_family = AF_INET;
     if ( (hp = gethostbyname(argv[1])) != NULL )
     {
         bcopy(hp->h_addr, ( struct sockaddr*)&servaddr.sin_addr, hp->h_length);
     }
     else if (inet_aton(argv[1], &servaddr.sin_addr) < 0 )
     {
         printf ( "Input Server IP error!/n" );
         exit (1);
     }
     servaddr.sin_port = htons(SERVER_PORT);
  
     //绑定地址
     if ( bind(listenfd, ( struct sockaddr*)&servaddr, sizeof (servaddr)) < 0 )
     {
         printf ( "IPaddress bound failure!/n" );
         exit (1);
     }
      
     //开始监听
     listen(listenfd, LISTENQ);
  
     client[0].fd = listenfd;         /*将数组中的第一个元素设置成监听描述字*/
  
     client[0].events = POLLIN;  /*将测试条件设置成普通或优先级带数据可读,此处书中为POLLRDNORM,
                                       但是怎么也编译不过去 ,编译器就是找不到,所以就临时改成了POLLIN这个条件,
                                       希望以后能弄清楚。
                                      */
  
     for (i = 1;i < OPEN_MAX; ++i)     /*数组中的其它元素将暂时设置成不可用*/
         client[i].fd = -1;
     maxi = 0;
  
     while (1)
     {
         nready = poll(client, maxi+1,INFTIM); //将进程阻塞在poll上
         if ( client[0].revents & POLLIN /*POLLRDNORM*/ ) /*先测试监听描述字*/
         {
             connfd = accept(listenfd,( struct sockaddr*)&servaddr, &clilen);
             for (i = 1; i < OPEN_MAX; ++i)
                 if ( client[i].fd < 0 )
                 {
                     client[i].fd = connfd;  /*将新连接加入到测试数组中*/
                     client[i].events = POLLIN; //POLLRDNORM; /*测试条件普通数据可读*/
                     break ;
                 }
             if ( i == OPEN_MAX )
             {
                 printf ( "too many clients" ); //连接的客户端太多了,都达到最大值了
                 exit (1);
             }
  
             if ( i > maxi )
                 maxi = i;  //maxi记录的是数组元素的个数
  
             if ( --nready <= 0 )
                 continue ;   //如果没有可读的描述符了,就重新监听连接
         }
  
         for (i = 1; i <= maxi; i++)  /*测试除监听描述字以后的其它连接描述字*/
         {
             if ( (sockfd = client[i].fd) < 0) /*如果当前描述字不可用,就测试下一个*/
                 continue ;
  
             if (client[i].revents & (POLLIN /*POLLRDNORM*/ | POLLERR)) /*如果当前描述字返回的是普通数据可读或出错条件*/
             {
                 if ( (n = read(sockfd, buf, MAXLINE)) < 0) //从套接口中读数据
                 {
                     if ( errno == ECONNRESET) //如果连接断开,就关闭连接,并设当前描述符不可用
                     {
                         close(sockfd);
                         client[i].fd = -1;
                     }
                     else
                         perror ( "read error" );
                 }
                 else if (n == 0) //如果数据读取完毕,关闭连接,设置当前描述符不可用
                 {
                     close(sockfd);
                     client[i].fd = -1;
                 }
                 else
                     write(sockfd, buf, n); //打印数据
  
                 if (--nready <= 0)
                     break ;
                  
             }
         }
     }
  
     exit (0);
}

你可能感兴趣的:(I/O)