I/O多路复用之poll

这篇具有很好参考价值的文章主要介绍了I/O多路复用之poll。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

poll() 函数是一个系统调用,用于在一个文件描述符数组中等待多个文件描述符上的 I/O 事件,并返回就绪的文件描述符的数量。它与 select() 函数类似,但提供了更好的性能。
poll() 函数原型如下:

#include <poll.h>

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

其中,各个参数的含义如下:

fds:一个指向 pollfd 结构体数组的指针,用于传递需要监听的文件描述符和对应的 I/O 操作类型(读、写和异常);
nfds:需要监听的文件描述符数目;
timeout:等待事件的超时时间,单位是毫秒。如果超过这个时间仍然没有任何事件发生,则 poll() 函数将返回 0,表示超时。

pollfd 结构体类型用于描述一个文件描述符和需要监听的事件类型。其原型定义如下:

struct pollfd {
    int   fd;         /* 文件描述符 */
    short events;     /* 需要监听的事件类型 */
    short revents;    /* 实际发生的事件类型 */
};

其中,fd 是需要监听的文件描述符;events 是需要监听的事件类型,包括 POLLIN(可读)、POLLOUT(可写)和 POLLERR(错误)等;revents 是实际发生的事件类型,对于需要监听的事件类型,如果事件发生了,对应的位将被设置为 1。
使用 poll() 函数的步骤如下:

构建 pollfd 结构体数组,初始化需要监听的文件描述符和对应的事件类型;
调用 poll() 函数进行监听;
根据 poll() 返回的结果和实际发生的事件类型,进行相应的处理。

以下是一个简单的示例程序,演示了如何使用 poll() 函数等待多个文件描述符上的事件:

#include <iostream>
#include <cstring>
#include <sys/poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
    // 创建两个 TCP 套接字
    int socket1 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    int socket2 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // 监听地址和端口
    struct sockaddr_in addr1 = {0};
    addr1.sin_family = AF_INET;
    addr1.sin_port = htons(8888);
    inet_pton(AF_INET, "0.0.0.0", &addr1.sin_addr);
    bind(socket1, (struct sockaddr*)&addr1, sizeof(addr1));

    struct sockaddr_in addr2 = {0};
    addr2.sin_family = AF_INET;
    addr2.sin_port = htons(8889);
    inet_pton(AF_INET, "0.0.0.0", &addr2.sin_addr);
    bind(socket2, (struct sockaddr*)&addr2, sizeof(addr2));

    // 设置套接字为监听状态,等待连接
    listen(socket1, 10);
    listen(socket2, 10);

    // 构建 pollfd 结构体数组
    struct pollfd fds[2];
    fds[0].fd = socket1;
    fds[0].events = POLLIN;
    fds[1].fd = socket2;
    fds[1].events = POLLIN;

    // 等待事件的发生
    int res = poll(fds, 2, -1);

    if (res == -1) {
        std::cerr << "poll error: " << std::strerror(errno) << std::endl;
        return 1;
    }

    if (res == 0) {
        std::cout << "poll timed out." << std::endl;
        return 0;
    }

    // 检查哪些文件描述符上发生了事件
    if (fds[0].revents & POLLIN) {
        std::cout << "socket 1 is readable." << std::endl;
        // TODO: 处理文件描述符 1 上的 I/O 事件
    }

    if (fds[1].revents & POLLIN) {
        std::cout << "socket 2 is readable." << std::endl;
        // TODO: 处理文件描述符 2 上的 I/O 事件
    }

    return 0;
}

在上面的示例中,我们创建了两个 TCP 套接字,并将它们添加到一个 pollfd 数组中进行监听。我们在代码中使用 poll() 函数来阻塞等待多个文件描述符上的 I/O 事件的发生,直到有文件描述符上发生了事件,poll() 函数才会返回。在检查 revents 指示的实际发生的事件类型,根据处理需要,可以在对应的条件分支中添加事件处理的代码。
需要注意的是,相比于 select() 函数,poll() 函数可以动态地添加和删除事件,因此可以用于动态管理和扩展任务列表。因此,如果我们需要处理动态管理的套接字,应该使用 poll() 函数。
总结来说,poll() 函数是实现高效网络编程中 I/O 多路复用的一种方法,它提供了更好的性能和更灵活的动态事件管理方式。但是与 epoll() 相比,它在处理大量文件描述符时性能较低,因此在高并发的情况下,应该优先选择使用 epoll()。

服务端和客户端示例代码

server

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/fcntl.h>

// ulimit -n
#define MAXNFDS  1024

// 初始化服务端的监听端口。
int initserver(int port);

int main(int argc,char *argv[])
{
  if (argc != 2)
  {
    printf("usage: ./tcppoll port\n"); return -1;
  }

  // 初始化服务端用于监听的socket。
  int listensock = initserver(atoi(argv[1]));
  printf("listensock=%d\n",listensock);

  if (listensock < 0)
  {
    printf("initserver() failed.\n"); return -1;
  }

  int maxfd;   // fds数组中需要监视的socket的大小。
  struct pollfd fds[MAXNFDS];  // fds存放需要监视的socket。

  for (int ii=0;ii<MAXNFDS;ii++) fds[ii].fd=-1; // 初始化数组,把全部的fd设置为-1。

  // 把listensock添加到数组中。
  fds[listensock].fd=listensock;
  fds[listensock].events=POLLIN;  // 有数据可读事件,包括新客户端的连接、客户端socket有数据可读和客户端socket断开三种情况。
  maxfd=listensock;

  while (1)
  {
    int infds = poll(fds,maxfd+1,5000);
    // printf("poll infds=%d\n",infds);

    // 返回失败。
    if (infds < 0)
    {
      printf("poll() failed.\n"); perror("poll():"); break;
    }

    // 超时。
    if (infds == 0)
    {
      printf("poll() timeout.\n"); continue;
    }

    // 检查有事情发生的socket,包括监听和客户端连接的socket。
    // 这里是客户端的socket事件,每次都要遍历整个集合,因为可能有多个socket有事件。
    for (int eventfd=0; eventfd <= maxfd; eventfd++)
    {
      if (fds[eventfd].fd<0) continue;

      if ((fds[eventfd].revents&POLLIN)==0) continue;

      fds[eventfd].revents=0;  // 先把revents清空。

      if (eventfd==listensock)
      {
        // 如果发生事件的是listensock,表示有新的客户端连上来。
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int clientsock = accept(listensock,(struct sockaddr*)&client,&len);
        if (clientsock < 0)
        {
          printf("accept() failed.\n"); continue;
        }

        printf ("client(socket=%d) connected ok.\n",clientsock);

        if (clientsock>MAXNFDS)
        {    
          printf("clientsock(%d)>MAXNFDS(%d)\n",clientsock,MAXNFDS); close(clientsock); continue;
        }

        fds[clientsock].fd=clientsock;
        fds[clientsock].events=POLLIN; 
        fds[clientsock].revents=0; 
        if (maxfd < clientsock) maxfd = clientsock;

        printf("maxfd=%d\n",maxfd);
        continue;
      }
      else 
      {
        // 客户端有数据过来或客户端的socket连接被断开。
        char buffer[1024];
        memset(buffer,0,sizeof(buffer));

        // 读取客户端的数据。
        ssize_t isize=read(eventfd,buffer,sizeof(buffer));

        // 发生了错误或socket被对方关闭。
        if (isize <=0)
        {
          printf("client(eventfd=%d) disconnected.\n",eventfd);

          close(eventfd);  // 关闭客户端的socket。

          fds[eventfd].fd=-1;

          // 重新计算maxfd的值,注意,只有当eventfd==maxfd时才需要计算。
          if (eventfd == maxfd)
          {
            for (int ii=maxfd;ii>0;ii--)
            {
              if ( fds[ii].fd != -1)
              {
                maxfd = ii; break;
              }
            }

            printf("maxfd=%d\n",maxfd);
          }

          continue;
        }

        printf("recv(eventfd=%d,size=%d):%s\n",eventfd,isize,buffer);

        // 把收到的报文发回给客户端。
        write(eventfd,buffer,strlen(buffer));
      }
    }
  }

  return 0;
}

// 初始化服务端的监听端口。
int initserver(int port)
{
  int sock = socket(AF_INET,SOCK_STREAM,0);
  if (sock < 0)
  {
    printf("socket() failed.\n"); return -1;
  }

  // Linux如下
  int opt = 1; unsigned int len = sizeof(opt);
  setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len);
  setsockopt(sock,SOL_SOCKET,SO_KEEPALIVE,&opt,len);

  struct sockaddr_in servaddr;
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(port);

  if (bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0 )
  {
    printf("bind() failed.\n"); close(sock); return -1;
  }

  if (listen(sock,5) != 0 )
  {
    printf("listen() failed.\n"); close(sock); return -1;
  }

  return sock;
}

client

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
  if (argc != 3)
  {
    printf("usage:./tcpclient ip port\n"); return -1;
  }

  int sockfd;
  struct sockaddr_in servaddr;
  char buf[1024];
 
  if ((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) { printf("socket() failed.\n"); return -1; }
	
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family=AF_INET;
  servaddr.sin_port=htons(atoi(argv[2]));
  servaddr.sin_addr.s_addr=inet_addr(argv[1]);

  if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)
  {
    printf("connect(%s:%s) failed.\n",argv[1],argv[2]); close(sockfd);  return -1;
  }

  printf("connect ok.\n");

  for (int ii=0;ii<10000;ii++)
  {
    // 从命令行输入内容。
    memset(buf,0,sizeof(buf));
    printf("please input:"); scanf("%s",buf);
    // sprintf(buf,"1111111111111111111111ii=%08d",ii);

    if (write(sockfd,buf,strlen(buf)) <=0)
    { 
      printf("write() failed.\n");  close(sockfd);  return -1;
    }
		
    memset(buf,0,sizeof(buf));
    if (read(sockfd,buf,sizeof(buf)) <=0) 
    { 
      printf("read() failed.\n");  close(sockfd);  return -1;
    }

    printf("recv:%s\n",buf);

    // close(sockfd); break;
  }
} 

测试结果
I/O多路复用之poll文章来源地址https://www.toymoban.com/news/detail-409368.html

到了这里,关于I/O多路复用之poll的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • IO多路复用中select的TCP服务器模型和poll服务模型

    服务器端 客户端 poll客户端

    2024年02月12日
    浏览(51)
  • 【高并发网络通信架构】引入IO多路复用(select,poll,epoll)实现高并发tcp服务端

    目录 一,往期文章 二,基本概念 IO多路复用 select 模型 poll 模型 epoll 模型 select,poll,epoll 三者对比 三,函数清单 1.select 方法 2.fd_set 结构体 3.poll 方法 4.struct pollfd 结构体 5.epoll_create 方法 6.epoll_ctl 方法 7.epoll_wait 方法 8.struct epoll_event 结构体 四,代码实现 select 操作流程 s

    2024年02月12日
    浏览(61)
  • 【高并发网络通信架构】3.引入IO多路复用(select,poll,epoll)实现高并发tcp服务端

    目录 一,往期文章 二,基本概念 IO多路复用 select 模型 poll 模型 epoll 模型 select,poll,epoll 三者对比 三,函数清单 1.select 方法 2.fd_set 结构体 3.poll 方法 4.struct pollfd 结构体 5.epoll_create 方法 6.epoll_ctl 方法 7.epoll_wait 方法 8.struct epoll_event 结构体 四,代码实现 select 操作流程 s

    2024年02月14日
    浏览(48)
  • 多路IO—POll函数,epoll服务器开发流程

    \\\"在计算机网络编程中,多路IO技术是非常常见的一种技术。其中,Poll函数和Epoll函数是最为常用的两种多路IO技术。这两种技术可以帮助服务器端处理多个客户端的并发请求,提高了服务器的性能。本文将介绍Poll和Epoll函数的使用方法,并探讨了在服务器开发中使用这两种技

    2024年02月06日
    浏览(43)
  • 操作系统- IO多路复用

    1) IO多路复用是操作系统的原理,但是很多中间件的实现都是基于它去做的,IO多复用需要 知道整个链路是样子的,输入是什么,输出是什么 2) 了解IO多路复用作用的位置是哪里 3.1.1 IO多路复用的输入 IO多路复用的输入是Socket文件 3.1.2 IO多路复用的输出 IO多路复用的输出是读

    2023年04月08日
    浏览(37)
  • 网络通信基础 - 多路复用技术(频分多路复用、时分多路复用、波分多路复用)

    多路复用技术:把多个低速信道组合成一个高速信道的技术 这种技术要用到两个设备,统称为 多路器(MUX) 多路复用器(Multiplexer) :在发送端根据某种约定的规则把多个低带宽的信号复合成一个高带宽的信号 多路分配器(Demultiplexer) :在接收端根据同一规则把高带宽信

    2023年04月23日
    浏览(45)
  • Linux 多路转接 —— poll

    小编是双非本科大二菜鸟不赘述,欢迎米娜桑来指点江山哦 1319365055 🎉🎉非科班转码社区诚邀您入驻🎉🎉 小伙伴们,满怀希望,所向披靡,打码一路向北 一个人的单打独斗不如一群人的砥砺前行 这是和梦想合伙人组建的社区,诚邀各位有志之士的加入!! 社区用户好文

    2024年02月10日
    浏览(53)
  • 使用Linux系统IO多路复用中eopll创建基于TCP通信协议的多人聊天室

    一.1.搭建好TCP的通信模型 2.创建红黑树根节点 3.将套接字事件添加到红黑树中,使其被监听 4.当套接字事件发生,表示有客户端连接,将连接事件加入到红黑树节点当中 5.每当连接事件发生时,表示客户端发送信息到服务器 6.每当有事件准备就绪时,将对应的红黑树节点信息

    2024年02月13日
    浏览(45)
  • 【网络】多路转接——poll | epoll

    🐱作者:一只大喵咪1201 🐱专栏:《网络》 🔥格言: 你只管努力,剩下的交给时间! 书接上文五种IO模型 | select。 poll 也是一种多路转接的方案,它专门用来解决 select 的两个问题: 等待fd有上限的问题。 每次调用都需要重新设置 fd_set 的问题。 如上图所示便是 poll 系统调

    2024年02月10日
    浏览(42)
  • 调试linux内核(2): poll系统调用的实现

    linux内核为用户态进程提供了一组IO相关的系统调用: select/poll/epoll, 这三个系统调用功能类似, 在使用方法和性能等方面存在一些差异. 使用它们, 用户态的进程可以\\\"监控\\\"自己感兴趣的文件描述符, 当这些文件描述符的状态发生改变时, 比如可读或者可写了, 内核会通知进程去处

    2024年02月11日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包