好好爱自己!

【转】IO多路复用之poll

原文: https://blog.51cto.com/lingdandan/1783952

 

____________________________________________

 

poll提供的功能与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,但poll比select的优点是,不限制所能监视的描述符的数目,但随着所监视描述符的数目的增加,性能也会下降

 

函数原型:

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

返回值:成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,返回0;失败返回-1

参数:

fds:结构体指针,该结构体结构如下:

        struct pollfd{

              int fd;               //所感兴趣的文件描述符

              short events;    //用于指定等待的事件

              short revents;   //用于指定poll返回时,在该文件描述符上实际发生了的事件

          };

每一个pollfd结构体制定了一个被监视文件描述符,可传递多个该结构体,指示poll监视多个文件描述符

nfds:要监视的描述符的个数

timeout:单位(微秒),timeout指定等待的毫秒数,无论I/O是否准备好,poll都会返回,指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件,立即返回

 

events域中请求的任何事件都可能在revents域中返回。合法的事件如下:

      POLLIN         有数据可读

      POLLPRI        有紧迫数据可读

  POLLOUT           写数据不会导致阻塞

  POLLRDNORM       有普通数据可读。

  POLLRDBAND      有优先数据可读。

  POLLWRNORM     写普通数据不会导致阻塞。

  POLLWRBAND       写优先数据不会导致阻塞。

  POLLMSGSIGPOLL    消息可用。

  此外,revents域中还可能返回下列事件:
  POLLER             指定的文件描述符发生错误。

  POLLHUP           指定的文件描述符挂起事件。

  POLLNVAL         指定的文件描述符非法。

      这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。

  POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。例如,要同时监视一个文件描述符是否可读和可写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。

 

示例代码如下:

编写一个echo server程序,功能是客户端向服务器发送信息,服务器接收输出并原样发送回给客户端,客户端接收到输出到终端

server_poll.c

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
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
 
#define _BLOCKLOG_ 6
 
void usage(char *_proc)
{
    printf("%s [ip] [port]\n",_proc);
}
 
int create(char *_ip,int _port)
{
    int listen_fd=socket(AF_INET,SOCK_STREAM,0);
    if(listen_fd<0){
        perror("socket");
        exit(1);
    }  
     
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(_port);
    local.sin_addr.s_addr=inet_addr(_ip);
 
    struct linger lig;
    int iLen;
    lig.l_onoff=1;
    lig.l_linger=0;
    iLen=sizeof(struct linger);
    setsockopt(listen_fd,SOL_SOCKET,SO_LINGER,(char *)&lig,iLen);
 
    if(bind(listen_fd,(struct sockaddr*)&local,sizeof(local))<0){
        perror("bind");
        exit(2);
    }
 
    if(listen(listen_fd,_BLOCKLOG_)<0){
        perror("listen");
        exit(3);
    }
 
    return listen_fd;
}
 
int main(int args,char *argv[])
{
    if(args!=3){
        usage(argv[0]);
        return 1;
    }  
 
    char *ip=argv[1];
    int port=atoi(argv[2]);
    //创建监听描述符并绑定
    int listen_fd=create(ip,port);
 
    nfds_t nfds=64;
    struct pollfd fds[nfds];
 
    //初始化描述符
    int i=0;
    for(;i<nfds;++i){
        fds[i].fd=-1;
    
     
    //添加监听描述符
    fds[0].fd=listen_fd;
    fds[0].events=POLLIN;
 
    struct sockaddr_in client;
    socklen_t len;
 
    char buf[1024];
    int maxi=0;
 
    int done=0;
    while(!done){
        int timeout=5000;
        switch(poll(fds,maxi+1,timeout)){
            case -1:
                perror("poll");
                break;
            case 0:
                printf("poll timeout...\n");
                break;
            default:
                {
                    int i=0;
                    for(;i<nfds;++i){
                        if(fds[i].fd==listen_fd && (fds[i].revents&POLLIN)){//listen event happend
                            int new_socket=accept(listen_fd,(struct sockaddr*)&client,&len);
                            if(new_socket<0){
                                perror("accept");
                                continue;
                            }else{
                                printf("get a connection...%s:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
                                int i=0;
                                //将新的连接和读事件添加到数组中
                                for(;i<nfds;++i){
                                    if(fds[i].fd<0){
                                        fds[i].fd=new_socket;
                                        fds[i].events=POLLIN;
                                        ++maxi;//更新客户连接的文件描述符的个数
                                        break;
                                    }
                                }
                            }
                        }else if(fds[i].fd>0 && (fds[i].revents&POLLIN)){//normal events happend
                                //接收客户端发来的消息
                            ssize_t _size=read(fds[i].fd,buf,sizeof(buf)-1);
                            if(_size<0){
                                perror("read");
                            }else if(_size==0){//client closed
                                printf("client shutdown\n");
                                close(fds[i].fd);
                                fds[i].fd=-1;
                                continue;
                            }else{
                                buf[_size]='\0';
                                printf("client# %s",buf);
                                //向客户端发送消息
                                if(write(fds[i].fd,buf,sizeof(buf)-1)<0){
                                    perror("write");
                                }
                            }
                        }
                    }
                    break;
                }
                break;
        }
    }
 
    return 0;
}
 

client_poll.c

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
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
 
void usage(char *proc)
{
    printf("%s [ip] [port]\n",proc);
}
 
int creat_socket()
{
    int fd=socket(AF_INET,SOCK_STREAM,0);
    if(fd<0){
        perror("socket");
        exit(1);
    }
 
    return fd;
}
 
int main(int argc,char* argv[])
{
    if(argc!=3){
        usage(argv[0]);
        exit(1);
    }
 
    int fd=creat_socket();
 
    int _port=atoi(argv[2]);
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(_port);
    inet_aton(argv[1],&addr.sin_addr);
    socklen_t addrlen=sizeof(addr);
    if(connect(fd,(struct sockaddr*)&addr,addrlen)<0){
        perror("connect");
        exit(2);
    }
 
    char buf[1024];
    while(1){
        memset(buf,'\0',sizeof(buf));
        printf("Please Enter:");
        fgets(buf,sizeof(buf)-1,stdin);
        if(send(fd,buf,sizeof(buf)-1,0)<0){
            perror("send");
            continue;
        }
 
        ssize_t _size=recv(fd,buf,sizeof(buf)-1,0);
        if(_size>0){
            buf[_size]='\0';
            printf("echo->%s",buf);
        }
  }
  return 0;
}
 

 

运行结果:

服务器端运行结果:

 

wKiom1dIV0bj6GdEAABqf4eMo80658.png

客户端1运行结果:

wKioL1dIWD7h13uEAABNGO5UdkI382.png

客户端2运行结果:

wKiom1dIV0ejtLrmAAA0NJf9h7I663.png

 

 

 

最后还有一点,poll()会自动把revents域设置为0,不需要我们手动的去设置

测试程序如下:

以下程序片段只是在上面代码的基础上添加了几行代码,为看的更清楚,我用红色注释了这几行代码

wKiom1dIY6iQq_tpAACPUZSG70g190.png

运行出来的结果如下:

wKiom1dIYxiTP7nOAABYWUWgQmU763.png

现在对上面运行结果进行分析,为简单起见,我们只关注四个结构体,所以以四行为单位

 

可看到第一次(前四行)的revents都为随机值

第二次(次四行)poll()函数把fd=3的描述符中revents设置为0

第四次poll()函数把fd=3的描述符中revents设置为1(因为此时监听到了客户端请求)所以接下来有:

get a connection...1.0.0.0:0  这条消息

第五次fd=3的revents还是为1,因为此时还没有执行poll(),fd=4的revents是随机值

第六次fd=3和fd=4的events都为1,它们都添加了读事件,而fd=3的revents被poll设置为了0,fd=4的revents为1(因为客户端发送了条消息,使读事件发生,因此有下面一条消息:)

client# hello 

第七次和第六次的一致,因为还未被poll()设置

第八次和第七次的一致,但此时fd=4的revents=1是因为客户端按下了Ctrl+c,所以接下来会有:

client shutdown  

第九次把fd=4从数组中去掉

 

 

《完》

posted @   立志做一个好的程序员  阅读(422)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
历史上的今天:
2019-12-09 【转】An introduction to using and visualizing channels in Go
2019-12-09 【转】 Anatomy of Channels in Go - Concurrency in Go
2016-12-09 php composer 安装
2016-12-09 服务器主从切换
2016-12-09 php-redis 下载地址
2016-12-09 【转】RestQL:现代化的 API 开发方式
2015-12-09 Html 中表单提交的一些知识总结——防止表单自动提交,以及submit和button提交表单的区别

不断学习创作,与自己快乐相处

点击右上角即可分享
微信分享提示