同步、异步、堵塞和非堵塞差别

  同步:发出一个功能调用时。在没有得到结果之前,该调用就不返回

  异步:当一个异步过程调用发出后。调用者不能立马得到结果。实际处理这个调用的部件在完毕后。通过状态、通知和回调来通知调用者

  堵塞:堵塞调用是指调用结果返回之前,当前线程会被挂起。

函数仅仅有在得到结果之后才会返回

  

  非堵塞:不能立马得到结果之前,该函数不会堵塞当前线程,而会立马返回

  

  同步与堵塞的差别:

   - 同步不一定堵塞,对于同步调用来说,许多时候当前线程还是激活的,仅仅是从逻辑上当前函数没有返回而已,如调用recv函数,假设缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息

   - 当socket工作在堵塞模式的时候。 假设没有数据的情况下调用该函数,则当前线程就会被挂起,直到有数据为止

  同步堵塞:效率最低

  同步非堵塞:将fd设为O_NONBLOCK,则可马上返回

  异步堵塞:如selectIO复用。不是在处理消息时堵塞。而是在等待消息被触发时被堵塞

IO多路复用

  select、poll、epoll都是IO复用机制,IO复用是指在一个线程中同一时候监听多个描写叙述符,一旦有一个描写叙述符就绪。就能够通知程序对该描写叙述符进行读写操作

  IO复用的基本方法都是:先构建一张有关描写叙述符的列表。然后使用统一的堵塞函数,在等待消息被触发时被堵塞。当不论什么socket有事件通知时跳出堵塞状态。

  IO复用本质都是同步IO。只是通过提前堵塞地监听描写叙述符,保证有描写叙述符须要进行读写时才调用相应的同步读写函数。跳过了对每一个描写叙述符的等待过程。提高了并发时的效率。

  

select

相关接口

  • 函数原型

     #include <sys/select.h> //select; 
    #include <sys/time.h> //struct timeval;
    int select(
    int nfds,
    fd_set *readset,
    fd_set *writeset,
    fd_set* exceptset,
    struct timeval *timeout
    );
    //返回已就绪的描写叙述符个数
    //当某些调用出错时,返回-1。可通过errno查看错误代码
    //当超时仍没有事件就绪时,返回0
  • 參数
    • nfds:第一个參数是:最大的文件描写叙述符值+1;
    • readset:可读描写叙述符集合;
    • writeset:可写描写叙述符集合;
    • exceptset:异常描写叙述符;
    • timeout:select 的监听时长。timeout为NULL时,堵塞等待。timeout为0时,马上返回
FD_ZERO(fd_set *)  //清空描写叙述符集合
FD_SET(int, fd_set *) //向描写叙述符集合增加指定描写叙述符
FD_CLR(int, fd_set *) //从描写叙述符集合删除指定描写叙述符
FD_ISSET(int, fd_set *) //检測指定描写叙述符是否在描写叙述符集合中
#define FD_SETSIZE 1024 //描写叙述符最大数量

select调用的基本框架(以read操作为例):

fd_set rdset;
listNode *p=head;
int max_fd;
int ret=0;
struct timeval timeout={3,0}; //设置超时时间
while(1){
//每次调用select之前都要清空fdset。并又一次增加全部描写叙述符
FD_ZERO(&rdset);
p=head;
while(p){ //依次将每一个待监听的描写叙述符增加列表
FD_SET(p->fd,&rdset);
if(p->fd>max_fd)
max_fd=p->fd;
p=p->next;
}
ret=select(max_fd+1,&rdset,NULL,NULL,&timeout);
if(ret<0) //返回-1。则出错
error;
if(ret==0) //timeout超时时,仍没有不论什么描写叙述符就绪
continue;
for(int i=0;i<=max_fd;++i){
//依次检測每一个描写叙述符。若就绪,则调用相应的回调函数进行处理
if(FD_ISSET(i,&rdset){
callback(i);
}
}
}

select的原理

  select是基于整型数组的,。每一个整型数字有32bit,每一位可表示一个fd。比如假设最大描写叙述符数为1024,则须要1024/32 = 32,即fd_set set为int a[32]。

  1)每次在调用select之前,都须要通过FD_SET增加待监听的描写叙述符。

  FD_SET(int fd, fd_set *pSet);则是将pSet的第fd位置为1(从0開始计数);如fd = 3,则经过FD_SET后,最后一字节为 0000 1000

  2)当调用select后,就将各fd_set从用户态copy到内核。当在超时时间内相应的描写叙述符没有事件就绪,则将该位置0.

  如在调用select之前,调用FD_SET设置了2、 3、 4这三个描写叙述符,则fd_set最低字节为 0001 1100。若在超时时间内,2、 3描写叙述符就绪,4未就绪。

则在超时返回时,fd_set最低字节变为 0000 1100。将改变后的fd_set从内核copy到用户态。

  3)遍历每一个描写叙述符,通过FD_ISSET推断是否就绪,若就绪。则进行相应处理。

  尽管能够改变描写叙述符数量的最大值。可是这样会导致数组很大。效率变得很低,因此意义不大。

  

select的缺陷

  通过上述对select原理的分析可知,select主要有下面几个缺陷:

  1)描写叙述符数量的限制。对于高并发的场景1024个描写叙述符远远不够

  2)每次调用select之前都须要依次向fd_set中增加每一个待监听的描写叙述符

  3)调用select时,都须要将fd_set从用户态copy到内核,超时时。又要将返回的fd_set从内核copy到用户态

  4)调用select时,底层是通过轮询每一个描写叙述符来推断是否有描写叙述符就绪的

  5)select返回的结果仅仅是就绪的描写叙述符的个数,而不是详细事件。因此须要遍历描写叙述符,依次推断每一个描写叙述符是否就绪

epoll

epoll是对select的改进:

  1)epoll的底层是基于红黑树的,每次增加一个监听的描写叙述符。即是向红黑树中增加一个节点。

  因此没有最大描写叙述符的限制

  2)同一时候监听的描写叙述符列表没有发生变化时,底层的红黑树不会发生不论什么变化,因此不再须要每次都又一次增加描写叙述符。

当要新增加或删除某个描写叙述符时,仅仅是在红黑树中增加或删除一个节点

  3)epoll还相应一个双向链表。每当有一个描写叙述符就绪时,就将该事件增加到双向链表中。

  当调用epoll_wait等待就绪事件时,内核是通过检查双向链表是否为空来推断是否有事件就绪的,而不用轮询每一个描写叙述符   

  4)epoll採用了共享内存,因此用户空间和内核传递数据不再须要copy,使得效率大大提高

  5)epoll_wait返回时,还会返回就绪描写叙述符,因此不再须要依次遍历每一个描写叙述符进行推断

  

epoll的相关接口

  epoll头文件为  

#include <sys/epoll.h>

  epoll**仅仅有epoll_create,epoll_ctl,epoll_wait** 3个系统调用

  1. int epoll_create(int size);

  创建一个epoll的句柄。自从linux2.6.8之后,size參数是被忽略的。须要注意的是。当创建好epoll句柄后,它就是会占用一个fd值。所以在使用完epoll后,必须调用close()关闭。否则可能导致fd被耗尽。

  2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

  epoll的事件注冊函数。它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注冊要监听的事件类型。

  • 第一个參数是epoll_create()的返回值
  • 第二个參数表示动作。用三个宏来表示:

    EPOLL_CTL_ADD:注冊新的fd到epfd中;

    EPOLL_CTL_MOD:改动已经注冊的fd的监听事件;

    EPOLL_CTL_DEL:从epfd中删除一个fd。
  • 第三个參数是须要监听的fd。
  • event:告诉内核须要监听什么事件

struct epoll_event结构例如以下:

//保存触发事件的某个文件描写叙述符相关的数据(与详细使用方式有关)
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 */
};

events能够是下面几个宏的集合:

  • EPOLLIN :读事件(包含对端SOCKET正常关闭);
  • EPOLLOUT:写事件;
  • EPOLLPRI:表示相应的文件描写叙述符有紧急的数据可读(这里应该表示有带外数据到来);
  • EPOLLERR:错误;
  • EPOLLHUP:表示相应的文件描写叙述符被挂断;
  • EPOLLET: 将EPOLL设为ET模式,这是相对于LT来说的。
  • EPOLLONESHOT:仅仅监听一次事件,当监听完这次事件之后。假设还须要继续监听这个socket的话。须要再次把这个socket增加到EPOLL队列里

    3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

      获得在epoll监听的描写叙述符中已就绪的事件。

      events是分配好的epoll_event结构体数组。epoll将会把发生的事件赋值到events数组中(events不能够是空指针,内核仅仅负责把数据拷贝到这个events数组中,不会去帮助我们在用户态中分配内存)。

      maxevents告之内核这个events有多大。这个 maxevents的值不能大于创建epoll_create()时的size,

      timeout是超时时间

      假设函数调用成功。返回相应I/O上已准备好的文件描写叙述符数目。如返回0表示已超时。

epoll两种工作模式:

  • LT(水平触发):若程序这次没有处理,下次还会再次通知  
  • ET(边沿触发):仅仅通知一次,若程序没有处理。后面不会再通知

    系统默觉得LT模式

epoll的基本使用框架  

int epfd;
struct epoll_event event;
struct epoll_event *events;
epfd = epoll_create1 (0);
event.data.fd = listenfd; //listenfd为server端监听fd
event.events = EPOLLIN | EPOLLET;//读入,边缘触发方式
s = epoll_ctl (efd, EPOLL_CTL_ADD, listenfd, &event);
events = calloc (MAXEVENTS, sizeof event);
while(1) {
nfds = epoll_wait(epfd,events,20,500);
for(i=0;i<nfds;++i) {
if(events[i].data.fd==listenfd){ //有新的连接
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接
ev.data.fd=connfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd增加到epoll的监听队列中
} else if( events[i].events&EPOLLIN ) { //接收到数据,读socket
n = read(sockfd, line, MAXLINE)) < 0 //读
ev.data.ptr = md; //md为自己定义类型,增加数据
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//改动标识符,等待下一个循环时发送数据,异步处理的精髓
} else if(events[i].events&EPOLLOUT){ //有数据待发送,写socket
struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //取数据
sockfd = md->fd;
send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); //发送数据
ev.data.fd=sockfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //改动标识符,等待下一个循环时接收数据
} else {
//其它的处理
}
}
}

select &amp; epoll的更多相关文章

  1. Python网络编程(4)——异步编程select & epoll

    在SocketServer模块的学习中,我们了解了多线程和多进程简单Server的实现,使用多线程.多进程技术的服务端为每一个新的client连接创建一个新的进/线程,当client数量较多时,这种技 ...

  2. python select epoll poll的解析

    select.poll.epoll三者的区别 select select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组(在linux中一切事物皆文件 ...

  3. python实现并发服务器实现方式(多线程/多进程/select/epoll)

    python实现并发服务器实现方式(多线程/多进程/select/epoll)   并发服务器开发 并发服务器开发,使得一个服务器可以近乎同一时刻为多个客户端提供服务.实现并发的方式有多种,下面以多进 ...

  4. 五种网络IO模型以及多路复用IO中select/epoll对比

    下面都是以网络读数据为例 [2阶段网络IO] 第一阶段:等待数据 wait for data 第二阶段:从内核复制数据到用户 copy data from kernel to user 下面是5种网络 ...

  5. linux网络编程 IO多路复用 select epoll

    本文以我的小型聊天室为例,对于服务器端的代码,做了三次改进,我将分别介绍阻塞式IO,select,epoll . 一:阻塞式IO 对于聊天室这种程序,我们最容易想到的是在服务器端accept之后,然后 ...

  6. linux 设置connect 超时代码[select/epoll]

    转载请注明来源:https://www.cnblogs.com/hookjc/ linux下socket编程有常见的几个系统调用: 对于服务器来说, 有socket(), bind(),listen( ...

  7. select,epoll,poll比较

    介绍和比较 http://www.cnblogs.com/maociping/p/5132583.html 比较 http://www.dataguru.cn/thread-336032-1-1.ht ...

  8. select&epoll

    内核空间和用户空间 现在操作系统都是采用虚拟存储器,那么对 32 位操作系统而言,它的寻址空间(虚拟地址空间,或叫线性地址空间)为 4G(2的32次方).也就是说一个进程的最大地址空间为 4G.操作系 ...

  9. linux基础编程:IO模型:阻塞/非阻塞/IO复用 同步/异步 Select/Epoll/AIO(转载)

      IO概念 Linux的内核将所有外部设备都可以看做一个文件来操作.那么我们对与外部设备的操作都可以看做对文件进行操作.我们对一个文件的读写,都通过调用内核提供的系统调用:内核给我们返回一个file ...

  10. 多线程 or I/O复用select/epoll

    1:多线程模型适用于处理短连接,且连接的打开关闭非常频繁的情形,但不适合处理长连接.线程模型默认情况下,在Linux下每个线程会开8M的栈空间,在TCP长连接的情况下,以2000/分钟的请求为例,几乎 ...

随机推荐

  1. chrome 获取移动端页面元素信息

    一:背景在使用appium进行app端自动化测试的时候,一般使用的是uiautomatorviewer来给页面元素做定位.但如果遇到页面元素类型是webview的时候,则只能定位整个页面,而不能更进一 ...

  2. [arc067f]yakiniku restaurants

    题意: n家饭店,m张餐票,第i家和第i+1家饭店之间的距离是$A_i$,在第i家饭店用掉第j张餐票会获得$B_{i,j}$的好感度,但是从饭店i走到饭店j会有$dis_{i,j}$的代价,可以从任意 ...

  3. useradd: cannot open /etc/passwd

    [root@ftp ~]# useradd -g ftp -s/sbin/nologin liwmuseradd: cannot open /etc/passwd [root@ftp ~]# user ...

  4. Python学习————集合的增删查

    可变的数据类型,他里面的元素必须是不可变的数据类型.无序,内容不能重复.应用于去重 增加:set1.add('元素')--->将元素无序的插入集合set1中set1.update("元 ...

  5. C#调用带结构体指针的C Dll的方法

    在C#中调用C(C++)类的DLL的时候,有时候C的接口函数包含很多参数,而且有的时候这些参数有可能是个结构体,而且有可能是结构体指针,那么在C#到底该如何安全的调用这样的DLL接口函数呢?本文将详细 ...

  6. 【codeforces 196B】Infinite Maze

    [题目链接]:http://codeforces.com/problemset/problem/196/B [题意] 给你一个n*m的棋盘; 然后你能够无限复制这个棋盘; 在这个棋盘上你有一个起点s; ...

  7. ie11 .pac代理脚本无法使用的问题

    参考: http://blogs.msdn.com/b/ieinternals/archive/2013/10/11/web-proxy-configuration-and-ie11-changes. ...

  8. 洛谷 P1990 覆盖墙壁

    P1990 覆盖墙壁 题目描述 你有一个长为N宽为2的墙壁,给你两种砖头:一个长2宽1,另一个是L型覆盖3个单元的砖头.如下图: 0 0 0 00 砖头可以旋转,两种砖头可以无限制提供.你的任务是计算 ...

  9. Qt之OpenSSL

    简述 OpenSSL是一个强大的安全套接字层密码库,囊括主要的密码算法.常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用. 简述 下载安装 使用 更多参考 下载安装 ...

  10. hadoop hdfs空间满后重新启动不了

    server检查的时候,发现存在HDFS上的文件无法同步.再发现hadoop停掉了. 进行重新启动,重新启动不成功. 查看hadoop日志: 2014-07-30 14:15:42,025 INFO ...