Linux I/O复用:select ,poll,epoll

I/O复用:一种进程预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪,它就通知进程。

一、5种I/O模型

Linux下有5种I/O模型,分别为:

  • 阻塞式I/O;
  • 非阻塞式I/O;
  • I/O复用;
  • 信号驱动式I/O;
  • 异步I/O;

      阻塞式I/O,即应用程序调用IO函数,导致程序阻塞(当前进程被挂起,暂停运行直到函数返回),等待数据准备好,如果数据没有准备好,进程就一直处于等待状态,只有数据准备好了且返回到进程缓冲区或发生错误才返回。在网络通信中,socket函数创建套接字时,所有的套接字默认都是阻塞的。
      
      非阻塞式I/O,进程反复调用一个IO函数,若没有数据准备好,返回一个错误,即不进入阻塞状态,而是不断的进行函数调用。 把一个SOCKET接口设置为非阻塞就是告诉内核,当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误。这样我们的I/O操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。在这个不断测试的过程中,会大量的占用CPU的时间。
      
      I/O复用,主要通过select和poll等函数来实现,阻塞在这两个系统调用中的某一个之上,而不是阻塞在真正的IO系统调用上,比阻塞IO并没有什么优越性;关键是能实现同时对多个IO端口进行监听(select和poll等函数可以看作是一种代理,由它们来代替IO函数进行监听等待)。
      
      信号驱动式I/O,通过使用信号,让内核在描述符就绪时发送SIGIO信号给进程。当程序运行到IO时,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。
      
      异步I/O,提前告知内核执行某个操作,且在内核完成整个操作之后通过我们,在等待IO执行期间,进程不会阻塞。
      
      总结:同步IO引起进程阻塞,直至IO操作完成。
         异步IO不会引起进程阻塞。
         IO复用是先通过select调用阻塞。

二、select 函数

  select函数允许进程指示内核等待多个事件中的一个或多个发生,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒它。我们调用select告知内核对哪些描述字(就读、写或异常条件)感兴趣以及等待多长时间。我们感兴趣的描述字不局限于套接口,任何描述字都可以使用select来测试。

/* 函数
 * 返回值:若有就绪描述符则为其数目,超时则为0,出错为-1;
 * 函数原型:
 */
 #include <sys/select.h>
 #include <sys/time.h>
 int select (int maxfdpl , fd_set *readset ,fd_set *writeset, fd_set *exceptset , const struct timeval *timeout);
/* 说明:
 * maxfdpl表示待测试的描述符个数,其值为待测试的最大描述符+1;
 * 中间的readset,writeset,exceptset指定我们要让内核测试读、写、异常条件的描述符;
 * timeout告知内核等待所指定描述字中的任何一个就绪可花多长时间;
 */

/* timeval 结构
 * 结构定义:
 */
 struct timeval {
     long tv_sec; //seconds
     long tv_usec ; //microseconds
 }
/* 说明:
 * 这个结构参数有3种可能值:
 * 1、NULL:代表永远等待下去,仅在有一个描述符准备好时才返回;
 * 2、一个固定的值:代表等待一段固定的时间,期间有描述符准备好则返回;
 * 3、0:表示根本不等待,检查描述字之后立即返回,这称为轮询;
 */

/* fd_set
 * fd_set 结构表示一个描述符集,通常是一个整数数组,每个整数中每一位对应一个描述符。
 * 关于fd_set所有实现细节隐藏在数据类型和以下四个宏当中:
 */
void FD_ZERO(fd_set *fdset); /* clear all bits in fdset */
void FD_SET(int fd, fd_set *fdset); /* turn on the bit for fd in fdset */
void FD_CLR(int fd, fd_set *fdset); /* turn off the bit for fd in fdset */
int  FD_ISSET(int fd, fd_set *fdset); /* is the bit for fd on in fdset ? */
/* 说明:
 * select函数修改由指针参数所指向的描述字集,因而这三个参数都是值-结果参数;
 * 在select函数执行过程中,会修改指针参数(readset...)其中的值;
 * 函数返回时,结果指示哪些描述符已就绪,该函数返回后,
 * 我们使用FD_ISSET来测试fd_set数据类型中的描述符,
 * 描述符集中任何与未就绪的描述符对应的位返回时均清为0,
 * 为此,每次重新调用select函数中,我们都得再次把所有描述字集合中的所关心的位置为1;
 */

部分参考:Linux五种IO模型性能分析

三、poll 函数

  poll函数提供的功能与select类似,不过在处理流设备时,它能够额外的信息。

/* 返回值:若有就绪描述符则为其数目,超时则为0,出错为-1;
 * 函数定义:
 */
#include <poll.h>
int poll(struct polldf *fdarray, unsigned long nfds, int timeout);
/*
 * 说明:
 * 第一个参数为指向结构数组的指针,每一个pollfd结构用于指定测试某个给定描述符fg的条件;
 * nfds表示结构数组中元素的个数;
 * timeout指定poll函数返回前等待多长时间(单位毫秒);可能取值:
 * 1、-1:永远等待(较新的系统中貌似没有定义INFTIM);
 * 2、0:立即返回,不阻塞进程(较新的系统中貌似没有0的说明);
 * 3、>0:等待指定数值的毫秒数;
 */
struct pollfd{
    int fd;         /*descriptor to check*/
    short events;   /*events of interest on fd*/
    short revents;  /*events that occurred on fd*/
}
/*
 * fd 成员表示感兴趣的,且打开了的文件描述符;
 * events  成员是位掩码,用于指定针对这个文件描述符感兴趣的事件;
 * revents  成员是位掩码,用于指定当 poll 返回时,在该文件描述符上已经发生了哪些事情。
 */ 

  pollfd结构作用与select函数中的fd_set是相似的,pollfd每个描述符有两个变量,一个为调用值,另一个为返回结果,fd_set则使用的是值-结果参数,但都是通过参数变化来返回结果。通过一些定义的宏来指定一些事件:
  POLLIN : 普通或优先级带数据可读;
  POLLOUT : 普通数据可写;
  POLLPRI : 高优先级数据可读;
  POLLERR :发生错误;
  POLLUP :发生挂起;
  …
  详细宏说明可参考:poll()函数详解

四、epoll

epoll是linux中独有的函数,功能也是用于IO复用,但是是select和poll的改进。epoll包括3个函数。

int epoll_create(int size);
/*创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。
当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,
是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
*/
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,第四个参数是告诉内核需要监听什么事,
struct epoll_event结构如下:
struct epoll_event {
  __uint32_t events;  // Epoll events 
  epoll_data_t data;  // User data variable 
};
typedef union epoll_data
{
  void        *ptr;
  int          fd;
  __uint32_t   u32;
  __uint64_t   u64;
} epoll_data_t;

events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,
需要再次把这个socket加入到EPOLL队列里

关于epoll工作模式ET,LT
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket,
在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。
如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。
传统的select/poll都是这种模型的代表。
ET (edge-triggered)是高速工作方式,只支持no-block socket。
在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。
然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,
直到你做了某些操作导致那个文件描述符不再为就绪状态了,但是请注意,
如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)

EPOLLLT——水平触发(电平一直在1上就一直提醒)
EPOLLET——边缘触发(电平变化时提醒一次)
epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。
LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作;
而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论fd中是否还有数据可读。
所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者遇到EAGAIN错误。
*/
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
/*等待事件的产生,类似于select()调用。
参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,
这个maxevents的值不能大于创建epoll_create()时的size,
参数timeout是超时时间(毫秒,0会立即返回,-1永久阻塞)。
该函数返回需要处理的事件数目,如返回0表示已超时。*/

epoll和select区别

五、select,poll,epoll的区别

select的几大缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大;
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大;
(3)select支持的文件描述符数量太小了,默认是1024;

poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。

epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。

epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。

对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048。

总而言之:
select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。

epoll的最大好处是不会随着FD的数目增长而降低效率,在selec中采用轮询处理,其中的数据结构类似一个数组的数据结构,而epoll是维护一个队列,直接看队列是不是空就可以了。epoll只会对”活跃”的socket进行操作—这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有”活跃”的socket才会主动的去调用 callback函数(把这个句柄加入队列),其他idle状态句柄则不会,在这点上,epoll实现了一个”伪”AIO。但是如果绝大部分的I/O都是“活跃的”,每个I/O端口使用率很高的话,epoll效率不一定比select高(可能是要维护队列复杂)。

select、poll、epoll之间的区别总结[整理]

六、select 函数应用于客户端实例

  在博客:Linux 套接字编程基础中我们介绍了一个简单的客户/服务器程序,但是没有解决当服务器崩溃或关机时客户端马上获取信号并退出的问题,这里使用select函数来实现。主要思想:原程序是直接阻塞在IO函数中,不能及时获取服务器发送的FIN信号。这里利用select函数对socket端口进行监听,每当IO条件满足时,执行相应的读取操作。修改只限于客户端,代码如下:

/*
 * client.c
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>

void str_cli(FILE* fp, int sockfd);  //处理函数声明

int main(int argc, char** argv)
{
    int sockfd;
    struct sockaddr_in servaddr;
    char* ips = "192.168.110.128";
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(6666);
    if(inet_aton(ips, &servaddr.sin_addr) <= 0)
    {
        printf("inet_aton error!\n");
        exit(0);
    }
    if(connect(sockfd, (struct sockaddr*)& servaddr, sizeof(servaddr)) < 0)
    {
        printf("connect error!\n");
        exit(0);
    }
    str_cli(stdin, sockfd);
    exit(0);
}

void str_cli(FILE* fp, int sockfd)
{
    char sendline[256], recvline[256] = "";
    int maxfdpl, stdineof;
    int n;     //记录读取字节数
    fd_set rset;//select para

    stdineof = 0;
    FD_ZERO(&rset);
    printf("Input message to be send:\n");
    for(;;)
    {
        if(stdineof == 0)
        {
            FD_SET(fileno(fp), &rset);
        }
        FD_SET(sockfd, &rset);   //参数设置
        maxfdpl = (fileno(fp) > maxfdpl ? fileno(fp) : maxfdpl) + 1;
        select(maxfdpl, &rset, NULL, NULL, NULL);//调用select
        if(FD_ISSET(sockfd, &rset))  //socket is readable
        {//处理socket口
            if((n = read(sockfd, recvline, 256)) == 0)
            {
                if(stdineof == 1)
                {
                    printf("terminate normally\n");
                    return;
                }
                else
                {//服务器异常退出
                    printf("read error, server collapse!\n");//server collapse
                    exit(1);
                }
            }
            write(fileno(stdout), recvline, n);
            printf("Input message to be send:\n");
        }
        if(FD_ISSET(fileno(fp), &rset)) //input is readable
        {//处理标准输入口
            if((n = read(fileno(fp), sendline, 256)) == 0)
            { //客户端发起终止连接
                stdineof = 1;
                shutdown(sockfd, SHUT_WR);
                printf("client-shutdown\n");
                FD_CLR(fileno(fp), &rset);
                continue;
            }
             write(sockfd, sendline, n);
        }
    }
}

说明:套接字连接这里不再解释,主要说明str_cli 函数。
函数str_cli 中调用select 对IO口进行监听,这里有两个描述符:sockfd,fileno(fp),一个是套接口,一个是标准输入口(fileno()用于获取fp的描述符),分别用于监听套件字是否有数据要读和标准输入是否有数据输入。
maxfdpl : select函数中描述符个数参数
stdineof : 用于标识标准输入是否输入了一个EOF,若输入了EOF则不再设置rset参数,也就不在对这个IO口进行监听。
程序正常退出过程: 如果客户端输入了EOF(Ctrl+D终止连接),read函数会返回0,那么就执行shutdown函数关闭写这一半,当服务器受到因shutdown发送的FIN,会返回一个FIN,客户端的socket马上能够收到这个FIN,read函数返回0,执行return,程序正常退出。
服务器崩溃退出过程:如果服务器突然崩溃或关机,会发送一个FIN给客户端,客户端会马上受到FIN,read函数返回0,这是不是客户端发起的连接终止,stdineof不为1,因此输出错误信息,退出程序。

/* 测试
 * 客户端:
 */
[centos@localhost Documents]$ ./a.out    //连接服务器
Input message to be send:
hello  
hello
Input message to be send:
are you ok
are you ok
Input message to be send:      //这里输入Ctrl+D,终止连接
client-shutdown
terminate normally
[centos@localhost Documents]$ ./a.out 
Input message to be send:
heiehi
heiehi
Input message to be send:     //这里将服务器关闭,获取输出信息
read error, server collapse!

/* 服务器
 * 与上面对应
 */
receive message: hello
child 5130 terminated!
receive message: heihei
//这里关闭服务器

代码主要参考自:UNIX网络编程卷1:套接字联网API-chapter-6.7;

七、利用poll实现的服务器程序

  之前的服务器程序实现代码是通过fork函数创建子进程来实现并发的,这里利用poll函数来实现同样的功能,客户端为上面的程序;

/* 服务器
 * poll实现并发
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <errno.h>
#include <poll.h>
#include <sys/wait.h>
#include <limits.h>
#define OPEN_MAX 10

int main(int argc, char** argv)
{
    int i, maxi, listenfd, connfd, sockfd;
    int nready;
    ssize_t n;
    socklen_t clilen;
    char buf[256];
    struct pollfd client[OPEN_MAX];
    struct sockaddr_in cliaddr, servaddr;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(6666); //what's is serv_port?

    bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr));

    listen(listenfd, 5); //5 is the number that can deal at the same time;
    client[0].fd = listenfd;
    client[0].events = POLLRDNORM;
    for(i = 1; i < OPEN_MAX; i++)
        client[i].fd = -1;
    maxi = 0;
    for(;;)
    {
        nready = poll(client, maxi+1, -1);
        if(client[0].revents & POLLRDNORM)
        {
            clilen = sizeof(cliaddr);
            connfd = accept(listenfd, (struct sockaddr*) &cliaddr, &clilen);
            for(i = 1; i < OPEN_MAX; i++)
            {
                if(client[i].fd < 0)
                {
                    client[i].fd = connfd;
                    break;
                }
            }
            if(i == OPEN_MAX)
            {
                printf("too many clients");
                exit(1);
            }
            client[i].events = POLLRDNORM;
            if(i>maxi)
                maxi =i;
            if(--nready <= 0)
                continue;
        }
        for(i = 1; i <= maxi; i++)
        {
            if((sockfd = client[i].fd) < 0)
                continue;
            if(client[i].revents & (POLLRDNORM | POLLERR))
            {
                if((n = read(sockfd, buf, 256)) < 0)
                {
                    printf("error");
                    close(sockfd);
                    client[i].fd = -1;
                }
                else if(n == 0)
                {
                    char* sss = "connect closed!";
                    write(fileno(stdout), sss, strlen(sss));
                    close(sockfd);
                    client[i].fd = -1;
                }
                else
                {
                    write(fileno(stdout), buf, n);
                    write(sockfd, buf, n);
                }
                if(nready <= 0)
                    break;
            }
        }
    }
}

说明: maxi为记录当前pollfd参数结构中的元素数量-1;nready为每次poll函数返回的就绪的描述符个数。
参数结构pollfd的第一个元素用于监听套接字,每次监听套接字在描述符就绪后,利用accept函数来获取新的连接描述符并添加到参数结构pollfd中,若是其它元素描述符就绪则依次进行读写操作。
测试输出结果与四中类似。

八、epoll 服务端实现

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

const int SERVER_PORT=1024;
//const char* SERVER_IP="127.0.0.1";
const int LISTEN_MAX = 36;
const size_t MAXEVENTS = 20;

size_t deal_fun(int rfd)
{
  char* buf = (char*)malloc(1024*1024);  //max file size: 1M
  int readnum;
  readnum = read(rfd,buf, 1024*1024);
  if(readnum <=0)
  {
      close(rfd);
      //events[i].data.fd = -1;
      return -1;
  }
  std::cout<<"read: " << buf ;//<< std::endl;
  write(rfd,buf, readnum);
  free(buf);

  return 0;
}
int main()
{
  int listenfd, epollfd;
  struct sockaddr_in cliaddr, servaddr;
  struct epoll_event event;
  struct epoll_event *events;

  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  // this machine
  servaddr.sin_port = htons(SERVER_PORT); //

  listenfd = socket(AF_INET, SOCK_STREAM, 0); //ipve4 tcp
  //no block socket
  //int flags = fcntl (listenfd, F_GETFL, 0);
  //flags |= O_NONBLOCK;
  //fcntl (listenfd, F_SETFL, flags);
  //is needed ??
  bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
  listen(listenfd, LISTEN_MAX);

  events = (epoll_event *)calloc(MAXEVENTS, sizeof(event));
  event.data.fd = listenfd;
  event.events = EPOLLIN;
  epollfd = epoll_create(3);   //3 is random no use
  epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&event);  //add fd to epool

  while(1)
  {
    int readyfds;
    readyfds = epoll_wait(epollfd, events, MAXEVENTS, -1);

    for(int i=0; i<readyfds; i++)
    {
      if(events[i].data.fd == listenfd)    //new connect
      {
        int connectfd;
        socklen_t addlen;
        connectfd = accept(listenfd, (sockaddr *)&cliaddr, &addlen);
        if(connectfd == -1)
        {
          LOG("connect erro\n");
          break;
        }
        char *str = inet_ntoa(cliaddr.sin_addr);
        std::cout << "accapt a connection from " << str << std::endl;

        event.data.fd = connectfd;
        event.events = EPOLLIN;
        epoll_ctl(epollfd, EPOLL_CTL_ADD, connectfd, &event);
      }
      else    //data to read
      {
        int rfd = events[i].data.fd;
        if(rfd <0)
          continue;

        if(deal_fun(rfd) == -1)
        {
          events[i].data.fd = -1;
        }
      }
    }
  }

  free (events);
  close (epollfd);
}

epoll使用详解(精髓)
通过完整示例来理解如何使用 epoll

九、水平或边缘触发与阻塞与非阻塞的关系

套接字可以设置为阻塞或非阻塞的,使用epoll监听套接字可以使用水平触发模式或边缘触发模式。
1. 监听套接字水平模式:套接字阻塞:正常连接;套接字非阻塞:正常连接
2. 监听套接字边缘模式:套件字阻塞:正常连接;套接字非阻塞:正常连接可能会出现连接失败问题(while循环accept解决)
对于监听套接字,当使用边缘模式时,如果是非阻塞,当accept时,可能并不会读取完所有连接。
3. 连接套接字水平模式(与客户端的连接):套接字阻塞:正常读写;套接字非阻塞:正常读写
4. 连接套接字边缘模式: 套件字阻塞:读写会有问题,可能会阻塞程序,套接字非阻塞:正常读写

套接字的阻塞表示在对套接字进行处理(读写)时,当没有数据可以操作时,阻塞直到有数据返回。
使用边缘模式读取数据时需要进行while循环读取,不然会导致数据读取不完全的问题,这里就只能使用非阻塞的套接字,因为如果使用阻塞套接字,当while读完所有数据时,会阻塞在读操作上无法返回。

一般来说监听套接字使用水平触发,阻塞就可以了。
对于连接客户端的读写套接字在水平触发模式下,阻塞和非阻塞效果都一样,建议设置非阻塞。边缘触发模式下,必须使用非阻塞 IO,并要求一次性地完整读写全部数据。

实例浅析epoll的水平触发和边缘触发,以及边缘触发为什么要使用非阻塞IO
边缘触发到底有什么优势

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值