I/O复用之poll系统调用

    poll系统调用和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试器中是否由就绪者。poll原型如下所示:

 

 C++ Code 
1
2
3
#include <poll.h>
int poll( struct pollfd *fds, nfds_t nfds,  int timeout);

   1)fds参数是一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读、可写和异常等事件。pollfd结构体的定义如下:

 C++ Code 
1
2
3
4
5
6
struct pollfd
{
     int fd;        //文件描述符
     short events;  //注册的事件
     short revents;  //实际发生事件,由内核填充
};

    其中,fd成员指定文件描述符:events成员告诉poll监听fd上的那些事件,它是一系列事件的按位或,revents成员则由内核修改,以通知应用程序fd上世纪发生了那些事件。poll支持的事件类型如下所示:

    

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

    通常,应用程序需要根据recv调用的返回值来区分socket上接收到的是有效数据还是对方关闭连接的请求,并做相应的处理。不过,自Linux内核2.6.17开始,GNU为poll系统条用增加了一个POLLRDHUP事件,它在socket上接收到对方关闭连接的请求之后出发。这位我们区分上述两种情况提供了一种更简单的方式。但是用POLLRDHUP事件时,我们需要再代码最开始定义_GNU_SOURCE。

   2)nfds参数指定被监听事件集合fds的大小。其类型nfds_t的定义如下:

 C++ Code 
1
typedef  unsigned  long  int nfds_t;
   3)timeout参数指定poll的超时值,单位为毫秒。当timeout为-1时,poll调用将永远阻塞,知道某个事件发生;当timeout为0时,poll调用将立即返回。

   poll系统调用的返回值的含义与select相同。


聊天室程序:

    像ssh这样的登录程序通常要同时处理网络连接和用户输入,这也可以使用I/O复用来实现。本节我们以poll为例子实现一个简单的聊天室程序,以阐述如何使用I/O复用技术来同时处理网络连接和用户输入。该聊天室程序能让所有用户同时在线群聊,它分为客户端和服务器两部分。其中客户端程序有两个功能:一是从标准输入终端读入用户数据,并将用户数据发送至服务器;二是往标准输出终端打印服务器发送给它的数据。服务器的功能是接收客户数据,并把客户数据发送给每一个登录到该服务器上的客户端(数据发送者除外),下面我们依次给出客户端程序和服务器程序的代码。

  1、客户端

   客户端程序使用poll同时监听用户输入和网络连接,并利用splice函数将用户输入内容直接定向到网络连接上以发送之,从而实现数据零拷贝,提高程序执行效率。客户端程序代码如下所示:

    

 C++ Code 
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
#define _GNU_SOURCE  1
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <fcntl.h>

#define BUFFER_SIZE  64

int main(  int argc,  char *argv[] )
{
     if( argc <=  2 )
    {
        printf(  "usage: %s ip_address port_number\n", basename( argv[ 0] ) );
         return  1;
    }
     const  char *ip = argv[ 1];
     int port = atoi( argv[ 2] );

     struct sockaddr_in server_address;
    bzero( &server_address,  sizeof( server_address ) );
    server_address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &server_address.sin_addr );
    server_address.sin_port = htons( port );

     int sockfd = socket( PF_INET, SOCK_STREAM,  0 );
    assert( sockfd >=  0 );
     if ( connect( sockfd, (  struct sockaddr * )&server_address,  sizeof( server_address ) ) <  0 )
    {
        printf(  "connection failed\n" );
        close( sockfd );
         return  1;
    }
     //注册文件描述符0(标准输入)和文件描述符sockfd上的可读事件
    pollfd fds[ 2];
    fds[ 0].fd =  0;
     //数据(包括普通数据和优先数据)可读
    fds[ 0].events = POLLIN;
    fds[ 0].revents =  0;
    fds[ 1].fd = sockfd;
     //TCP连接被对方关闭,或者对方关闭了写操作。
    fds[ 1].events = POLLIN | POLLRDHUP;
    fds[ 1].revents =  0;
     char read_buf[BUFFER_SIZE];
     int pipefd[ 2];
     //无名管道
     int ret = pipe( pipefd );
    assert( ret != - 1 );

     while1 )
    {
        ret = poll( fds,  2, - 1 );  //阻塞poll机制
         if( ret <  0 )
        {
            printf(  "poll failure\n" );
             break;
        }
         //检测服务关闭事件
         if( fds[ 1].revents & POLLRDHUP )
        {
            printf(  "server close the connection\n" );
             break;
        }
         //检测数据可读事件
         else  if( fds[ 1].revents & POLLIN )
        {
            memset( read_buf,  '\0', BUFFER_SIZE );
            recv( fds[ 1].fd, read_buf, BUFFER_SIZE -  10 );
            printf(  "%s\n", read_buf );
        }
         //检测命令行终端是否有数据可读
         if( fds[ 0].revents & POLLIN )
        {
             //使用splice将用户输入的数据直接写到sockfd上(零拷贝),通过管道
            ret = splice(  0NULL, pipefd[ 1],  NULL32768, SPLICE_F_MORE | SPLICE_F_MOVE );
            ret = splice( pipefd[ 0],  NULL, sockfd,  NULL32768, SPLICE_F_MORE | SPLICE_F_MOVE );
        }
    }

    close( sockfd );
     return  0;
}

    2、服务器

    服务程序使用poll同时管理监听socket和连接socket,并且使用牺牲空间换取时间的策略来提高服务器的性能。代码清单如下所示。

    

 C++ Code 
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#define _GNU_SOURCE  1
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <poll.h>
//最大用户数量
#define USER_LIMIT  5
//该缓冲区的大小
#define BUFFER_SIZE  64
//文件描述符的数量限制
#define FD_LIMIT  65535

struct client_data
{
     //客户数据:客户端socket地址
    sockaddr_in address;
     //待写到客户端数据的位置
     char *write_buf;
     //从客户端读入的数据
     char buf[ BUFFER_SIZE ];
};
//设置非阻塞
int setnonblocking(  int fd )
{
     int old_option = fcntl( fd, F_GETFL );
     int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
     return old_option;
}

int main(  int argc,  char *argv[] )
{
     if( argc <=  2 )
    {
        printf(  "usage: %s ip_address port_number\n", basename( argv[ 0] ) );
         return  1;
    }
     const  char *ip = argv[ 1];
     int port = atoi( argv[ 2] );

     int ret =  0;
     struct sockaddr_in address;
    bzero( &address,  sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );
     //创建TCP socket,并将其绑定到端口port上
     int listenfd = socket( PF_INET, SOCK_STREAM,  0 );
    assert( listenfd >=  0 );

    ret = bind( listenfd, (  struct sockaddr * )&address,  sizeof( address ) );
    assert( ret != - 1 );

    ret = listen( listenfd,  5 );
    assert( ret != - 1 );
     //创建user数组,分配FD_LIMIT各Client_data对象。
     //可以预期:每个可能的socket连接都可以获得一个这样的对象
     //并且socket的值可以直接用来索引socket连接对象的client_data对象
     //这是将socket和客户数据管理的简单而高效的方式
    client_data *users =  new client_data[FD_LIMIT];
     //尽管我们分配了足够多的client_data对象,但为了提供poll的性能,仍然有比亚限制用户的数量
    pollfd fds[USER_LIMIT +  1];
     int user_counter =  0;
     forint i =  1; i <= USER_LIMIT; ++i )
    {
        fds[i].fd = - 1;
        fds[i].events =  0;
    }
    fds[ 0].fd = listenfd;
    fds[ 0].events = POLLIN | POLLERR;  //用于数据读入和数据出错
    fds[ 0].revents =  0;

     while1 )
    {
        ret = poll( fds, user_counter +  1, - 1 ); //poll阻塞
         if ( ret <  0 )
        {
            printf(  "poll failure\n" );
             break;
        }
         //用for循环来检测每个结构体
         forint i =  0; i < user_counter +  1; ++i )
        {
             //监听是否由新的连接进来。
             if( ( fds[i].fd == listenfd ) && ( fds[i].revents & POLLIN ) )  //服务器监听端口
            {
                 struct sockaddr_in client_address;
                socklen_t client_addrlength =  sizeof( client_address );
                 int connfd = accept( listenfd,   //接受连接
                                     (  struct sockaddr * )&client_address, &client_addrlength );
                 if ( connfd <  0 )
                {
                    printf(  "errno is: %d\n", errno );
                     continue;
                }
                 //如果请求太多,则关闭新到的连接
                 if( user_counter >= USER_LIMIT )
                {
                     const  char *info =  "too many users\n";
                    printf(  "%s", info );
                    send( connfd, info, strlen( info ),  0 );
                    close( connfd );
                     continue;
                }
                 //对于新的连接,同时修改fds和users数组
                 //users[connfd]对应于新连接文件描述符connfd的客户数据
                user_counter++;
                users[connfd].address = client_address;
                setnonblocking( connfd );
                fds[user_counter].fd = connfd;
                fds[user_counter].events = POLLIN | POLLRDHUP | POLLERR;
                fds[user_counter].revents =  0;
                printf(  "comes a new user, now have %d users\n", user_counter );
            }
             //监听错误链接
             else  if( fds[i].revents & POLLERR )
            {
                printf(  "get an error from %d\n", fds[i].fd );
                 char errors[  100 ];
                memset( errors,  '\0'100 );
                socklen_t length =  sizeof( errors );
                 if( getsockopt( fds[i].fd, SOL_SOCKET, SO_ERROR, &errors, &length ) <  0 )
                {
                    printf(  "get socket option failed\n" );
                }
                 continue;
            }
             //监听客户端关闭
             else  if( fds[i].revents & POLLRDHUP )
            {
                users[fds[i].fd] = users[fds[user_counter].fd];
                close( fds[i].fd );
                fds[i] = fds[user_counter];
                i--;
                user_counter--;
                printf(  "a client left\n" );
            }
             //监听数据可读
             else  if( fds[i].revents & POLLIN )
            {
                 int connfd = fds[i].fd;
                memset( users[connfd].buf,  '\0', BUFFER_SIZE );
                ret = recv( connfd, users[connfd].buf, BUFFER_SIZE -  10 );
                printf(  "get %d bytes of client data %s from %d\n", ret, users[connfd].buf, connfd );
                 if( ret <  0 )
                {
                     if( errno != EAGAIN )
                    {
                        close( connfd );
                        users[fds[i].fd] = users[fds[user_counter].fd];
                        fds[i] = fds[user_counter];
                        i--;
                        user_counter--;
                    }
                }
                 else  if( ret ==  0 )
                {
                    printf(  "code should not come to here\n" );
                }
                 else
                {
                     //如果接收到客户数据,则通知其他socket连接准备写数据
                     forint j =  1; j <= user_counter; ++j )
                    {
                         if( fds[j].fd == connfd )
                        {
                             continue;
                        }

                        fds[j].events |= ~POLLIN;
                        fds[j].events |= POLLOUT;
                        users[fds[j].fd].write_buf = users[connfd].buf;
                    }
                }
            }
             else  if( fds[i].revents & POLLOUT )
            {
                 int connfd = fds[i].fd;
                 //非本机
                 if( ! users[connfd].write_buf )
                {
                     continue;
                }
                 //发送数据
                ret = send( connfd, users[connfd].write_buf, strlen( users[connfd].write_buf ),  0 );
                users[connfd].write_buf =  NULL;
                fds[i].events |= ~POLLOUT;
                fds[i].events |= POLLIN;
            }
        }
    }
     //删除空间
     delete [] users;
     //关闭连接
    close( listenfd );
     return  0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值