服务器之家:专注于服务器技术及软件下载分享
分类导航

Linux|Centos|Ubuntu|系统进程|Fedora|注册表|Bios|Solaris|Windows7|Windows10|Windows11|

服务器之家 - 服务器系统 - Linux - IO多路复用之poll全面总结(必看篇)

IO多路复用之poll全面总结(必看篇)

2021-12-13 20:59Linux教程网 Linux

下面小编就为大家带来一篇IO多路复用之poll全面总结(必看篇)。小编觉得挺不错的。现在就分享给大家。也给大家做个参考。一起跟随小编过来看看吧

1、基本知识

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

2、poll函数

函数格式如下所示:

# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

pollfd结构体定义如下:

?
1
2
3
4
5
6
struct pollfd {
 
int fd;     /* 文件描述符 */
short events;     /* 等待的事件 */
short revents;    /* 实际发生了的事件 */
} ;

 

每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:

pollin 有数据可读。

pollrdnorm   有普通数据可读。

pollrdband  有优先数据可读。

pollpri 有紧迫数据可读。

pollout      写数据不会导致阻塞。

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

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

pollmsgsigpoll 消息可用。

此外,revents域中还可能返回下列事件:

poller   指定的文件描述符发生错误。

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

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

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

使用poll()和select()不一样,你不需要显式地请求异常情况报告。

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

timeout参数指定等待的毫秒数,无论i/o是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好i/o的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。

返回值和错误代码

成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:
ebadf       一个或多个结构体中指定的文件描述符无效。

efaultfds 指针指向的地址超出进程的地址空间。

eintr  请求的事件之前产生一个信号,调用可以重新发起。

einvalnfds参数超出plimit_nofile值。

enomem     可用内存不足,无法完成请求。

3、测出程序

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

服务器端程序如下:

?
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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
 
#include <netinet/in.h>
#include <sys/socket.h>
#include <poll.h>
#include <unistd.h>
#include <sys/types.h>
 
#define ipaddress  "127.0.0.1"
#define port    8787
#define maxline   1024
#define listenq   5
#define open_max  1000
#define inftim   -1
 
//函数声明
//创建套接字并进行绑定
static int socket_bind(const char* ip,int port);
//io多路复用poll
static void do_poll(int listenfd);
//处理多个连接
static void handle_connection(struct pollfd *connfds,int num);
 
int main(int argc,char *argv[])
{
  int listenfd,connfd,sockfd;
  struct sockaddr_in cliaddr;
  socklen_t cliaddrlen;
  listenfd = socket_bind(ipaddress,port);
  listen(listenfd,listenq);
  do_poll(listenfd);
  return 0;
}
 
static int socket_bind(const char* ip,int port)
{
  int listenfd;
  struct sockaddr_in servaddr;
  listenfd = socket(af_inet,sock_stream,0);
  if (listenfd == -1)
  {
    perror("socket error:");
    exit(1);
  }
  bzero(&servaddr,sizeof(servaddr));
  servaddr.sin_family = af_inet;
  inet_pton(af_inet,ip,&servaddr.sin_addr);
  servaddr.sin_port = htons(port);
  if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1)
  {
    perror("bind error: ");
    exit(1);
  }
  return listenfd;
}
 
static void do_poll(int listenfd)
{
  int connfd,sockfd;
  struct sockaddr_in cliaddr;
  socklen_t cliaddrlen;
  struct pollfd clientfds[open_max];
  int maxi;
  int i;
  int nready;
  //添加监听描述符
  clientfds[0].fd = listenfd;
  clientfds[0].events = pollin;
  //初始化客户连接描述符
  for (i = 1;i < open_max;i++)
    clientfds[i].fd = -1;
  maxi = 0;
  //循环处理
  for ( ; ; )
  {
    //获取可用描述符的个数
    nready = poll(clientfds,maxi+1,inftim);
    if (nready == -1)
    {
      perror("poll error:");
      exit(1);
    }
    //测试监听描述符是否准备好
    if (clientfds[0].revents & pollin)
    {
      cliaddrlen = sizeof(cliaddr);
      //接受新的连接
      if ((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen)) == -1)
      {
        if (errno == eintr)
          continue;
        else
        {
          perror("accept error:");
          exit(1);
        }
      }
      fprintf(stdout,"accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
      //将新的连接描述符添加到数组中
      for (i = 1;i < open_max;i++)
      {
        if (clientfds[i].fd < 0)
        {
          clientfds[i].fd = connfd;
          break;
        }
      }
      if (i == open_max)
      {
        fprintf(stderr,"too many clients.\n");
        exit(1);
      }
      //将新的描述符添加到读描述符集合中
      clientfds[i].events = pollin;
      //记录客户连接套接字的个数
      maxi = (i > maxi ? i : maxi);
      if (--nready <= 0)
        continue;
    }
    //处理客户连接
    handle_connection(clientfds,maxi);
  }
}
 
static void handle_connection(struct pollfd *connfds,int num)
{
  int i,n;
  char buf[maxline];
  memset(buf,0,maxline);
  for (i = 1;i <= num;i++)
  {
    if (connfds[i].fd < 0)
      continue;
    //测试客户描述符是否准备好
    if (connfds[i].revents & pollin)
    {
      //接收客户端发送的信息
      n = read(connfds[i].fd,buf,maxline);
      if (n == 0)
      {
        close(connfds[i].fd);
        connfds[i].fd = -1;
        continue;
      }
      // printf("read msg is: ");
      write(stdout_fileno,buf,n);
      //向客户端发送buf
      write(connfds[i].fd,buf,n);
    }
  }
}

客户端代码如下所示:

?
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
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
 
#define maxline   1024
#define ipaddress  "127.0.0.1"
#define serv_port  8787
 
#define max(a,b) (a > b) ? a : b
 
static void handle_connection(int sockfd);
 
int main(int argc,char *argv[])
{
  int         sockfd;
  struct sockaddr_in servaddr;
  sockfd = socket(af_inet,sock_stream,0);
  bzero(&servaddr,sizeof(servaddr));
  servaddr.sin_family = af_inet;
  servaddr.sin_port = htons(serv_port);
  inet_pton(af_inet,ipaddress,&servaddr.sin_addr);
  connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
  //处理连接描述符
  handle_connection(sockfd);
  return 0;
}
 
static void handle_connection(int sockfd)
{
  char  sendline[maxline],recvline[maxline];
  int   maxfdp,stdineof;
  struct pollfd pfds[2];
  int n;
  //添加连接描述符
  pfds[0].fd = sockfd;
  pfds[0].events = pollin;
  //添加标准输入描述符
  pfds[1].fd = stdin_fileno;
  pfds[1].events = pollin;
  for (; ;)
  {
    poll(pfds,2,-1);
    if (pfds[0].revents & pollin)
    {
      n = read(sockfd,recvline,maxline);
      if (n == 0)
      {
          fprintf(stderr,"client: server is closed.\n");
          close(sockfd);
      }
      write(stdout_fileno,recvline,n);
    }
    //测试标准输入是否准备好
    if (pfds[1].revents & pollin)
    {
      n = read(stdin_fileno,sendline,maxline);
      if (n == 0)
      {
        shutdown(sockfd,shut_wr);
    continue;
      }
      write(sockfd,sendline,n);
    }
  }
}

4、程序测试结果

IO多路复用之poll全面总结(必看篇)

IO多路复用之poll全面总结(必看篇)

IO多路复用之poll全面总结(必看篇)

以上就是小编为大家带来的io多路复用之poll全面总结(必看篇)全部内容了,希望大家多多支持服务器之家~

延伸 · 阅读

精彩推荐
  • LinuxLinux配置实现免密钥登录过程解析

    Linux配置实现免密钥登录过程解析

    这篇文章主要介绍了Linux配置实现免密钥登录过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以...

    Zs夏至3242020-06-15
  • Linuxlinux下安装nginx(图文教程)

    linux下安装nginx(图文教程)

    本篇文章主要介绍了linux下安装nginx,Nginx是一款轻量级的Web服务器。有需要的朋友可以了解一下。...

    智慧云端日记11382021-11-22
  • LinuxLinux系统中的gdb程序调试工具的命令知识介绍

    Linux系统中的gdb程序调试工具的命令知识介绍

     gdb调试运行程序时,用gdb .exe加载程序,gdb调试core dump时,用gdb .exe .core加载程序,执行r命令可以开始运行程序。在gdb内编辑完代码后,不需要退出gdb,...

    Linux教程网2282019-11-28
  • LinuxLinux系统如何制作Video将摄像头的内容显示出来

    Linux系统如何制作Video将摄像头的内容显示出来

    使用摄像头进行录像做成Video视频,那么在Linux系统下要如何实现呢?需要对内核进行升级,下文介绍内核升级步骤及将摄像头的内容显示出来的方法 ...

    Linux教程网5272019-10-12
  • Linux使用John the ripper工具来尝试破解Linux密码

    使用John the ripper工具来尝试破解Linux密码

    这篇文章主要介绍了使用John the ripper工具来尝试破解Linux密码的方法,这款工具可能主要被用来破解系统用户的密码以获得文件操作权限,需要的朋友可以参考...

    服务器之家5512019-07-06
  • LinuxLinux下NFS网络文件系统的基本使用教程

    Linux下NFS网络文件系统的基本使用教程

    这篇文章主要介绍了Linux下NFS网络文件系统的基本使用教程,配置方面写得非常详细,同时给出了一些常见错误的解决方法以及需要注意的地方,非常推荐,需要...

    luckyhe5632019-06-19
  • LinuxLinux中安装oray服务的步骤分享

    Linux中安装oray服务的步骤分享

    家里面的路由器ddns经常不能用,索性在系统中直接安了一个oray的客户端。具体安装步骤如下 ...

    王恒的博客5782019-12-21
  • Linux整理Linux中常用的一些grep命令

    整理Linux中常用的一些grep命令

    这篇文章主要介绍了整理Linux中常用的一些grep命令,本文列举了14个用于进行查找操作的grep命令,需要的朋友可以参考下 ...

    开源中文社区3172019-10-08