前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >什么是select/poll/epoll模型?

什么是select/poll/epoll模型?

作者头像
友儿
发布2023-03-11 17:38:59
3010
发布2023-03-11 17:38:59
举报
文章被收录于专栏:友儿

模型图

单线程处理

  • 我们自己会怎样写单线程处理多个客户端连接呢?我们知道在linux里面中每个网络连接在内核中都是文件描述符(Fd)的形式存在,为了使大家看得明白,我们使用一段伪代码来编写一个单线程网络服务器,以下伪代码中我们需要用程序判断当前Fdx是否有数据,这个其实过程还是有些慢的,下面让我们看一下select/poll/epoll的原代码是怎么写的。
代码语言:javascript
复制
while (1){
    for(Fdx in (Fd1~Fd5)) { # Fdx 为当前遍历到的文件;Fd1~Fd5为这5个网络连接在内核中的文件描述符
        if (Fdx) { # 是否有数据
            # 读取Fdx并处理数据
        }
    }
}

select 函数源码

代码语言:javascript
复制
# 准备文件描述符的数组 Fds

# 创建socket服务端
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof (addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(2000);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd,(struct sockaddr*)&addr ,sizeof(addr));
listen (sockfd, 5); 

# 创建了5个文件描述符
for (i=0;i<5;i++) 
  {
    memset(&client, 0, sizeof (client));
    addrlen = sizeof(client);
    fds[i] = accept(sockfd,(struct sockaddr*)&client, &addrlen);
    # 计算出一个最大的文件描述符
    if(fds[i] > max)
        max = fds[i]; 
  }

  # 通过以上程序我们准备好了一个文件描述符的集合假如是1 3 6 7 9 和 一个最大的文件描述符 9

  while(1){
    FD_ZERO(&rset);
      for (i = 0; i< 5; i++ ) {
          FD_SET(fds[i],&rset);
      }

       puts("round again");
  # select参数依次含义:最大的文件描述符 + 1 (用于内核截取文件描述符); 读文件描述符集合; 写文件描述符集合;异常文件描述符集合; 超时时间;  
                 
  # rset参数接受的并不是Fd集合,而是接受了FD_SET,FD_SET的类型是一个bitmap
  # 假如我们文件描述符集合是 1 3 6 7 9,那么我们bitmap里面存的值就是0101001101000...  (1024位01的位置坑位)。
  
  # 无数据时select阻塞
    select(max+1, &rset, NULL, NULL, NULL);
  # 有数据时select会把Fd置位并返回向下执行。
    
    for(i=0;i<5;i++) {
                # 循环判断哪个Fd被置位了,然后读取数据并且处理
        if (FD_ISSET(fds[i], &rset)){
            memset(buffer,0,MAXBUF);
            read(fds[i], buffer, MAXBUF);
            puts(buffer);
        }
    }    
  }
  • select 函数执行过程过程中会把用户态空间rset的bitmap类型的数据复制到内核态一份,由内核判断Fd是否有数据。我们知道用户态程序判断效率一定是低于内核去判断的,因为用户态判断也是在询问内核,它是有一个用户态到内核态的切换的,判断每一个Fd都需要用户态到内核态的切换。如果没数据的话,select函数是一个阻塞函数,会一直阻塞在select所在的行。如果有数据的话,内核会把有数据的Fd置位(可以理解为标识成有数据)并且返回,不在阻塞。
select 缺点
  • rset 的 bitmap 类型有大小限制1024位,即FD_SET的大小限制为1024位
  • FD_SET是不可重用,rset 的数据会被内核置位修改,每次while循环的时候都需要重新赋值
  • 虽然是比我们在用户态判断的消耗好些,但是 rset 数据从用户态复制到内核态仍然是有很大的开销的
  • select 函数返回的时候说明至少有一个Fd有数据(被置位了),但是我们并不知道是哪一个Fd或者那些Fd被置位了,需要我们再去遍历一边哪个被置位了,需要o(n)的复杂度的。

poll 函数源码

代码语言:javascript
复制
# 声明结构图pollfd

struct pollfd {
      int fd;
      short events; 
      short revents;
};
  # 构造pollfd数据
  for (i=0;i<5;i++) 
  {
    memset(&client, 0, sizeof (client));
    addrlen = sizeof(client);
    pollfds[i].fd = accept(sockfd,(struct sockaddr*)&client, &addrlen);
    # POLLIN 读事件
    pollfds[i].events = POLLIN;
  }
  sleep(1);

  while(1){
      puts("round again");
        # 传入pollfds 结构体
        # 5 传入的数量
        # 50000 超时时间
        # 也是阻塞函数
    poll(pollfds, 5, 50000);
         
    for(i=0;i<5;i++) {
        if (pollfds[i].revents & POLLIN){
            pollfds[i].revents = 0;
            memset(buffer,0,MAXBUF);
            read(pollfds[i].fd, buffer, MAXBUF);
            puts(buffer);
        }
    }
  }
  • 同select一样,poll函数也阻塞函数,只不过poll传入的是一个数组类型的结构体,poll函数也会把用户态数据复制到内核态置位,但是select置位的是bitmap(导致不可从重用),而poll置位的是结构体中的revents(初始化为0,有数据置位为POLLIN)字段。同理,无数据时poll堵塞,有数据时执行读操作并处理数据,并把revents设置为0,所以pollfd数据并没有发生改变可以复用。
poll函数解决了select的哪些问题
  • pollfd这种结构体,解决了bitmap 1024位的限制
  • 结构体的revents字段在有数据是被置位(POLLIN),我们遍历读取的时候重新设置为0,所以pollfds可以重用,解决了select中 FD_SET 不可重用的弊端。

epoll 函数源码

代码语言:javascript
复制
  # 创建一个epfd,在执行epoll_wait时需要,epfd 就相当于一种空的数据格式
  struct epoll_event events[5];
  int epfd = epoll_create(10);
  ...
  ...
  for (i=0;i<5;i++) 
  {
    static struct epoll_event ev;
    memset(&client, 0, sizeof (client));
    addrlen = sizeof(client);
    ev.data.fd = accept(sockfd,(struct sockaddr*)&client, &addrlen);
    ev.events = EPOLLIN;
    # 对 epfd配置,相当于poll声明的结构体,定义一种数据格式,epoll_ctl相当于构造epfd这种数据格式,大概是一个文件描述符对应的一个events事件 (fd-events)
    epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev); 
  }
  # 准备好epfd执行下面程序
  while(1){
      puts("round again");
      nfds = epoll_wait(epfd, events, 5, 10000);
    
    for(i=0;i<nfds;i++) {
            memset(buffer,0,MAXBUF);
            read(events[i].data.fd, buffer, MAXBUF);
            puts(buffer);
    }
  }
  • epoll 也是阻塞函数,无数据时阻塞;有数据时,它也是置位,不过它的置位其实是一种变相的位,会把有数据的fd重拍到最前面位置,之前的select和poll都没有返回值,而epoll是有返回值的,它的返回值是有数据的个数。这样的话我们后续的遍历读取处理也简单了,遍历复杂度由 o(n)次简化为 o(1)次了。

select/poll/epoll 源码参考文章

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 模型图
  • 单线程处理
  • select 函数源码
    • select 缺点
    • poll 函数源码
      • poll函数解决了select的哪些问题
      • epoll 函数源码
      • select/poll/epoll 源码参考文章
      相关产品与服务
      云服务器
      云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档