我们先从著名的C10K问题开始探讨,由于早期在网络还不普及的时候,互联网的用户并不是很多,一台服务器同时在线100个用户估计在当时已经算是大型应用了。但是随着互联网的发展,用户群体迅速的扩大,每一个用户都必须与服务器保持TCP连接才能进行实时的数据交互。Facebook这样的网站同一时间的并发TCP连接可能会过亿。这时候问题就来了。

解决这种问题的思路主要有两个:一个是对于每一个连接处理分配一个独立的进程或线程;另一种就是用同一进程或线程来同时处理若干个连接。

第一种解决方案,即来一个TCP链接,就需要分配一个进程/线程。而进程又是操作系统最昂贵的资源,一台机器无法创建很多进程,比如C10K问题就要创建1万个进程,操作系统是无法承受的。

第二中解决方案,即每个进程/线程同时处理多个连接(IO多路复用),我们讨论的select,poll,epoll都是基于这种思路的。

  • 这种思路最简单的方法是循环挨个处理各个请求,每个连接对应一个socket,当所有的socket的有数据的时候,这种方法是可行的,但当应用读取某个socket的文件数据不ready的时候,整个应用就会阻塞在这里等待该文件句柄,即使别的文件句柄ready,也无法往下处理。
  • select要解决的就是上面的阻塞问题,思路很简单,如果我在读取文件句柄之前,先查看它的状态,ready了就进行处理,不ready就不进行处理,这样就解决了这个问题了
    1. select 使用fd_set结构体来告诉内核同时监控多个文件句柄,当其中有文件句柄的状态发生制定变化或超时,则调用返回。之后使用FD_ISSET来捉个查看是哪个文件句柄发生变化。
    2. fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。
    3. fd_set集合可以通过一些宏由人为的来操作,FD_ZERO(fd_set*):清空集合;FD_SET(int, fd_set*):将指定的文件描述符加入到集合;FD_CLR(int, fd_set*):将一个给定的文件描述符从集合中删除;FD_ISSET(int, fd_set*):判断指定的文件描述符是否可以读写。
    4. select函数原型:int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
      1. maxfdp:集合中所有文件描述符的范围,即所有文件描述符的最大值+1;
      2. readfds:指定我们要监视的读变化的文件描述符集合,即我们是否可以从这些文件中读取数据;
      3. writefds:指定我们要监视的写变化的文件描述符集合,即我们是否可以从这些文件中写数据;
      4. errorfds:用来监视文件错误异常;
      5. timeout:select 的超时时间,若将此参数设为NULL,表示阻塞,直到有文件描述符状态发生变化;若将此参数设为0秒0毫秒,表示非阻塞,直接返回,返回值为0表示没有文 件描述符发生变化,返回值为非0表示有文件描述符发生变化;若将此参数设为大于0,表示等待的时间,期间如果有事件发生则返回非0值,如果一直没有事件, 则等待此参数设置的时间,然后返回0。
    5. select可以实现小规模的连接,但当连接数很多的时候,逐个检查状态就很慢了,因此,select往往存在管理的句柄上限(FD_SETSIZE),同时,在使用上,因为只有一个字段记录关注和发生事件,每次调用之前要重新初始化fd_set结构体。
    6. select例子:
      1. server.c:

        #include <sys/time.h>
        #include <netinet/in.h>
        #include <sys/select.h>
        #include <sys/types.h>
        #include <sys/socket.h>
        #include <unistd.h>
        #include <stdio.h>
        #include <stdlib.h> int main(){
        int socket1, socket2;
        struct sockaddr_in socket1addr, socket2addr;
        fd_set fds;
        struct timeval timeout = {, };
        char buffer[] = {}; socket1 = socket(PF_INET, SOCK_DGRAM, );
        //bzero(&socket1addr, sizeof(socket1addr));
        socket1addr.sin_family = AF_INET;
        socket1addr.sin_addr.s_addr = htonl(INADDR_ANY);
        socket1addr.sin_port = htons();
        bind(socket1, (struct sockaddr*)&socket1addr, sizeof(socket1addr)); socket2 = socket(PF_INET, SOCK_DGRAM, );
        //bzero(&socket2addr, sizeof(socket2addr));
        socket2addr.sin_family = AF_INET;
        socket2addr.sin_addr.s_addr = htonl(INADDR_ANY);
        socket2addr.sin_port = htons();
        bind(socket2, (struct sockaddr*)&socket2addr, sizeof(socket2addr)); while(){
        FD_ZERO(&fds);
        FD_SET(socket1, &fds);
        FD_SET(socket2, &fds);
        int maxfdp = (socket1 > socket2) ? (socket1+) : (socket2 + );
        int retval = select(maxfdp, &fds, NULL, NULL, &timeout);
        if(retval == -){
        printf("error\n");
        return -;
        }else if(retval == ){
        continue;
        }else{
        struct sockaddr_in client;
        int len = sizeof(client);
        if(FD_ISSET(socket1, &fds)){
        recvfrom(socket1, buffer, , , (struct sockaddr*)&client, &len);
        printf("%u says:%s\n", ntohs(client.sin_port), buffer);
        }
        if(FD_ISSET(socket2, &fds)){
        recvfrom(socket2, buffer, , , (struct sockaddr*)&client, &len);
        printf("%u says:%s\n", ntohs(client.sin_port), buffer);
        }
        } }
        }
      2. client.c
        #include <stdio.h>
        #include <string.h>
        #include <sys/socket.h>
        #include <netinet/in.h> int main(int argc, char **argv)
        {
        if(argc < ){
        printf("usage: %s port\n", argv[]);
        return -;
        }
        int sockfd;
        struct sockaddr_in servaddr; sockfd = socket(PF_INET, SOCK_DGRAM, ); bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(atoi(argv[]));
        servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); char sendline[];
        sprintf(sendline, "Hello, world!"); sendto(sockfd, sendline, strlen(sendline), , (struct sockaddr *)&servaddr, sizeof(servaddr)); close(sockfd); return ;
        }
  • poll解决了select的两个问题,通过一个pollfd数组向内核传递需要关注的事件消除文件句柄上限,同时使用不同字段分别标注关注事件和发生事件,来避免重复初始化。

    poll函数原型:int poll(struct pollfd[], nfds_t nfds, int timeout);

        1)struct pollfd的结构如下:

          struct pollfd{

            int fd;    //文件描述符

            short events;  //请求的事件

            short revents; //返回的事件

          };

         events和revents是通过对代表各种事件的标志进行逻辑或运算构建而成的。其中events是传入参数,revents是传出参数,即revents的填充是由内核来完成的。

         fd代表一个文件描述符,当fd设置为负值时,则忽略events的设置并将revents设置为0,

        2)nfds:要监视的描述符的个数,即pollfd[]的大小。

        3)timeout:指定poll在返回前没有接受事件应该等待的事件。INFTIM表示永远等待,0表示立即返回不阻塞进程,>0表示等待指定时间。

    poll函数事件的标识符值:

      

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

    poll问题:虽然poll解决了select中的两个问题,但是它仍然需要逐个排查所有文件句柄状态,效率不高。

    poll函数例子:

    1. server.c:

       #include <stdio.h>
      #include <netinet/in.h>
      #include <stdlib.h>
      #include <sys/socket.h>
      #include <sys/types.h>
      #include <sys/poll.h> int main(){
      struct pollfd pollfd[];
      int socket1, socket2;
      struct sockaddr_in socket1addr, socket2addr; socket1 = socket(PF_INET, SOCK_DGRAM, );
      socket1addr.sin_family = AF_INET;
      socket1addr.sin_addr.s_addr = htonl(INADDR_ANY);
      socket1addr.sin_port = htons();
      bind(socket1, (struct sockaddr*)&socket1addr, sizeof(socket1addr)); socket2 = socket(PF_INET, SOCK_DGRAM, );
      socket2addr.sin_family = AF_INET;
      socket2addr.sin_addr.s_addr = htonl(INADDR_ANY);
      socket2addr.sin_port = htons();
      bind(socket2, (struct sockaddr*)&socket2addr, sizeof(socket2addr)); pollfd[].fd = socket1;
      pollfd[].events = POLLIN;
      pollfd[].fd = socket2;
      pollfd[].events = POLLIN; while(){
      int numready = poll(pollfd, , -);
      if(numready == -){
      break;
      }else if(numready == ){
      continue;
      }else{
      char buffer[]={};
      struct sockaddr_in client;
      int i, len = sizeof(client);
      for(i=; i< ; i++){
      if(pollfd[i].revents & POLLIN){
      recvfrom(pollfd[i].fd, buffer, , , (struct sockaddr*)&client, &len);
      printf("%d says %s\n", ntohs(client.sin_port), buffer);
      numready--;
      }
      }
      }
      }
      int i;
      for(i=; i<; i++){
      close(pollfd[i].fd);
      } return ;
      }
    2. client.c:
      #include <stdio.h>
      #include <string.h>
      #include <sys/socket.h>
      #include <netinet/in.h> int main(int argc, char **argv)
      {
      if(argc < ){
      printf("usage: %s port\n", argv[]);
      return -;
      }
      int sockfd;
      struct sockaddr_in servaddr; sockfd = socket(PF_INET, SOCK_DGRAM, ); bzero(&servaddr, sizeof(servaddr));
      servaddr.sin_family = AF_INET;
      servaddr.sin_port = htons(atoi(argv[]));
      servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); char sendline[];
      sprintf(sendline, "Hello, world!"); sendto(sockfd, sendline, strlen(sendline), , (struct sockaddr *)&servaddr, sizeof(servaddr)); close(sockfd); return ;
      }
  • epoll

    既然逐个排查所有文件句柄状态效率不高,很自然的,如果调用返回的时候只给应用提供发生了状态变化的文件句柄,这样效率就会高很多,epoll就是采用了这种设计,使用与大规模   的应用场景。

    epoll的使用:epoll用到三个函数:epoll_create、epoll_ctl、epoll_wait。

    函数原型:

      int epoll_create(int size)

        该函数生成一个epoll专用的文件描述符(即返回值),size指定生成描述符的最大范围。

      int epoll_ctl(int epfd, int op, int fd, struct epoll_event event)

        该函数用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。调用成功返回0,失败返回-1。

        epfd:由epoll_create生成的专用文件描述符

        op:要进行的操作,可能取值:EPOLL_CTL_ADD(注册),EPOLL_CTL_MOD(修改),EPOLL_CTL_DEL(删除)。

        fd:关联的文件描述符

        event:指向epoll_event的指针

      int epoll_wait(int epfd,struct epoll_event   events,int maxevents,int timeout)

        该函数用于轮询事件的发生。返回发生的事件数,-1表示有错误。

        epfd:由epoll_create生成的专用文件描述符

        epoll_event:用于回传待处理事件的数组

        maxevents:每次能处理的事件数

        timeout:等待事件发生的超时值,-1永远等待直到有事件发生。

    数据结构:

    

    typedef union epoll_data {
void ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t; struct epoll_event {
__uint32_t events; / Epoll events /
epoll_data_t data; / User data variable /
};

      结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件,其中epoll_data 联合体用来保存触发事件的某个文件描述符相关的数据,例如一个client连接到服务器,      服务器通过调用accept函数可以得到于这个client对应 的socket文件描述符,可以把这文件描述符赋给epoll_data的fd字段以便后面的读写操作在这个文件描述符上进行。          epoll_event 结构体的events字段是表示感兴趣的事件和被触发的事件可能的取值为:

      EPOLLIN :表示对应的文件描述符可以读;
      EPOLLOUT:表示对应的文件描述符可以写;
      EPOLLPRI:表示对应的文件描述符有紧急的数据可读
      EPOLLERR:表示对应的文件描述符发生错误;
      EPOLLHUP:表示对应的文件描述符被挂断;
      EPOLLET:表示对应的文件描述符设定为edge模式;

    使用例子:

      server.c

      

#include <stdio.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h> int main(){
struct epoll_event ev1, ev2, events[];
int epfd = epoll_create();
int socket1, socket2;
struct sockaddr_in socket1addr, socket2addr; socket1 = socket(PF_INET, SOCK_DGRAM, );
socket1addr.sin_family = AF_INET;
socket1addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket1addr.sin_port = htons();
bind(socket1, (struct sockaddr*)&socket1addr, sizeof(socket1addr)); socket2 = socket(PF_INET, SOCK_DGRAM, );
socket2addr.sin_family = AF_INET;
socket2addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket2addr.sin_port = htons();
bind(socket2, (struct sockaddr*)&socket2addr, sizeof(socket2addr)); ev1.data.fd = socket1;
ev1.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, socket1, &ev1); ev2.data.fd = socket2;
ev2.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, socket2, &ev2); while(){
int nfds = epoll_wait(epfd, events, , );
int i;
for(i=; i<nfds; i++){
char buffer[];
struct sockaddr_in client;
int len = sizeof(client);
if((events[i].data.fd == socket1) && (events[i].events&&EPOLLIN)){
recvfrom(socket1, buffer, , , (struct sockaddr*)&client, &len);
printf("%u says:%s\n", ntohs(client.sin_port), buffer);
}else if((events[i].data.fd == socket2) && (events[i].events&&EPOLLIN)){
recvfrom(socket2, buffer, , , (struct sockaddr*)&client, &len);
printf("%u says:%s\n", ntohs(client.sin_port), buffer);
}
}
}
}

      client.c

      

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h> int main(int argc, char **argv)
{
if(argc < ){
printf("usage: %s port\n", argv[]);
return -;
}
int sockfd;
struct sockaddr_in servaddr; sockfd = socket(PF_INET, SOCK_DGRAM, ); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[]));
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); char sendline[];
sprintf(sendline, "Hello, world!"); sendto(sockfd, sendline, strlen(sendline), , (struct sockaddr *)&servaddr, sizeof(servaddr)); close(sockfd); return ;
}

    Epoll的ET模式与LT模式
      ET(Edge Triggered)与LT(Level Triggered)的主要区别可以从下面的例子看出
      eg:
        1. 标示管道读者的文件句柄注册到epoll中;
        2. 管道写者向管道中写入2KB的数据;
        
3. 调用epoll_wait可以获得管道读者为已就绪的文件句柄;
        
4. 管道读者读取1KB的数据
        
5. 一次epoll_wait调用完成
      
如果是ET模式,管道中剩余的1KB被挂起,再次调用epoll_wait,得不到管道读者的文件句柄,除非有新的数据写入管道。如果是LT模式,只要管道中有数据可读,每次调用epoll_wait都会触发。

      另一点区别就是设为ET模式的文件句柄必须是非阻塞的

select、poll、epoll用法的更多相关文章

  1. select,poll,epoll用法

    http://blog.csdn.net/sunboy_2050/article/details/6126712 select用法 #include <sys/time.h>       ...

  2. select/poll/epoll on serial port

    In this article, I will use three asynchronous conferencing--select, poll and epoll on serial port t ...

  3. Linux下select&poll&epoll的实现原理(一)

    最近简单看了一把 linux-3.10.25 kernel中select/poll/epoll这个几个IO事件检测API的实现.此处做一些记录.其基本的原理是相同的,流程如下 先依次调用fd对应的st ...

  4. Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)

    一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 简单的启动线程语法 def run(name): ...

  5. 多进程、协程、事件驱动及select poll epoll

    目录 -多线程使用场景 -多进程 --简单的一个多进程例子 --进程间数据的交互实现方法 ---通过Queues和Pipe可以实现进程间数据的传递,但是不能实现数据的共享 ---Queues ---P ...

  6. Python自动化 【第十篇】:Python进阶-多进程/协程/事件驱动与Select\Poll\Epoll异步IO

    本节内容: 多进程 协程 事件驱动与Select\Poll\Epoll异步IO   1.  多进程 启动多个进程 进程中启进程 父进程与子进程 进程间通信 不同进程间内存是不共享的,要想实现两个进程间 ...

  7. select,poll,epoll的归纳总结区分

    Select.Poll与Epoll比较 以下资料都是来自网上搜集整理.引用源详见文章末尾. 1 Select.Poll与Epoll简介 Select select本质上是通过设置或者检查存放fd标志位 ...

  8. 转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】

    下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架, ...

  9. select.poll,epoll的区别与应用

    先讲讲同步I/O的五大模型 阻塞式I/O, 非阻塞式I/O, I/O复用,信号驱动I/O(SIGIO),异步I/O模型 而select/poll/epoll属于I/O复用模型 select函数 该函数 ...

  10. select poll epoll三者之间的比较

    一.概述 说到Linux下的IO复用,系统提供了三个系统调用,分别是select poll epoll.那么这三者之间有什么不同呢,什么时候使用三个之间的其中一个呢? 下面,我将从系统调用原型来分析其 ...

随机推荐

  1. Difference Between Primes

    Problem Description All you know Goldbach conjecture.That is to say, Every even integer greater than ...

  2. vijosP1046 观光旅游(最小环)

    vijosP1046 观光旅游 链接:https://vijos.org/p/1046 [思路] Floyd求解最小环. [代码] #include<iostream> using nam ...

  3. java 23 种设计模式

    一.设计模式的分类 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接 ...

  4. ecshop格式化商品价格

    <?php /** * 格式化商品价格 * * @access public * @param float $price 商品价格 * @return string */ function pr ...

  5. 推荐一个网站——聚合了微软的文件的Knowledge Base下载地址

    Microsoft Files是一个微软的文件数据库,从这里可以很方便的找到各个文件版本对应的下载链接. 比如今天debug需要找一个特定版本的sos.dll,从这个网站就很方便的给出了这个sos.d ...

  6. Android源代码之DeskClock (一)

    一.概述 一直有read the fucking source code的计划,可是实行起来都是断断续续的.到如今也没有真正得读过多少Android的源代码(主要是懒的).如今回忆起来实在是非常羞愧, ...

  7. 超人学院Hadoop大数据资源分享

    超人学院Hadoop大数据资源分享 http://bbs.superwu.cn/forum.php?mod=viewthread&tid=770&extra=page%3D1 很多其它 ...

  8. Qt on Android: Qt Quick 之 Hello World 图文具体解释

    在上一篇文章,<Qt on Android:QML 语言基础>中,我们介绍了 QML 语言的语法,在最后我们遗留了一些问题没有展开,这篇呢,我们就正式開始撰写 Qt Quick 程序,而那 ...

  9. delphi TColorDialog

    TColorDialog 预览          实现过程 动态创建和使用颜色对话框 function ShowColorDlg:TColor;begin  with TColorDialog.Cre ...

  10. [RxJS] Combination operator: withLatestFrom

    Operator combineLatest is not the only AND-style combinator. In this lesson we will explore withLate ...