三组 l/O 复用 函数

I/O复用


I/O 复用使得程序能同时监听多个文件描述符,这对提高程序的性能至关重要。通常,网络程序在下列情况下需要使用 I/O 复用技术

  • 客户端程序要同时处理多个 socket。比如本章将要讨论的非阻塞 connect 技术
  • 客户端程序要同时处理用户输入和网络连接。比如本章将要讨论的聊天室程序口TCP 服务器要同时处理监听 socket 和连接 socket。这是I/0 复用使用最多的场合。后
    续章节将展示很多这方面的例子。
  • 服务器要同时处理 TCP 请求和 UDP 请求。比如回射服务器。
  • 服务器要同时监听多个端口,或者处理多种服务。比如 xinetd 服务器。

需要指出的是,IO 复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依次处理其中的每一个文件描述符,这使得服务器程序看起来像是申行工作的。如果要实现并发,只能使用多进程或多线程等编程手段。
Linux 下实现 I/O 复用的系统调用主要有 select、poll 和epoll

1.1、select系统调用

select 系统调用的用途是:在一段指定时间内,监听用户感兴趣的文件描述符上的可读可写和异常等事件

1.1.1 select API

select 系统调用的原型如下:

#include <sys/select.h>
int select( int nfds, fd_set* readfds, fd set* writefds, fd_set* exceptfds,
           struct timeval* timeout );

1)nfds 参数指定被监听的文件描述符的总数。它通常被设置为 slect 监听的所有文件描述符中的最大值加 1,因为文件描述符是从0开始计数的
2)readfds、writefds 和 exceptfds 参数分别指向可读、可写和异常等事件对应的文件描述符集合。应用程序调用 select 函数时,通过这3 个参数传自已感兴趣的文件描述符。select 调用返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪。这3个参数是fd_set 结构指针类型。fd set 结构体的定义如下

#include <typesizes.h>
#define  FD SETSIZE 1024
#include <sys/select .h>
#define FD_SETSIZE _FD_SETSIZE
typedef long int _fd_mask;
#undef _NFDBITS 
#define _NFDBITS ( 8 * (int) sizeof ( _fd_mask ) )
typedef struct
{
#ifdef _USE_XOPEN
	_fd_mask fds bits[ _FD_SETSIZE /  _NFDBITS ];
#define _FDS_BITS(set) ((set)->fds_bits)
#else
	_fd_mask _fds_bits[ _FD_SETSIZE / _NFDBITS ];
#define  _FDS_BITS(set) ((set)-> _fds_bits)
#endif
} fd_set;

​ 由以上定义可见,fd set 结构体仅包含一个整型数组,该数组的每个元素的每一位 (bit)标记一个文件描述符。fd set 能容纳的文件描述符数由 FD SETSIZE 指定,这就限制了select 能同时处理的文件描述符的总量。
由于位操作过于烦琐,我们应该使用下面的一系列宏来访问 fd set 结体中的位:

#include <sys/select.h>
FD_ZERO( fd set *fdset );             /*清除 fdset 的所有位*/
FD_SET( int fd, fd set *fdset );      /* 设置 fdset 的位 fd */
FD_CLR( int fd, fd set *fdset );      /*清除 fdset 的位 fd */
int FD_ISSET( int fd, fd set *fdset );/* 测试 fdset 的位 fd 是否被设置 */
  1. timeout 参数用来设置 select 函数的超时时间。它是一个timeval结构类型的指针采用指针参数是因为内核将修改它以告诉应用程序 select 等待了多久。不过我们不能完全信任sclect 调用返回后的 timeout 值,比如调用失败时 timeout 值是不确定的。timeval 结构体的定义如下:
struct timeval
{
	long tv_sec; /*秒数*/
	long tv_usec;/* 微秒数 */
}

由以上定义可见,select 给我们提供了一个微秒级的定时方式。如果给 timeout 变量的tv_sec 成员和 tv_usec 成员都传递0,则select将立即返回。如果给timeout传递 NULL,则select 将一直阻寨,直到某个文件描述符就绪。
select 成功时返回就绪(可读、可写和异常)文件描述符的总数。如果在超时时间内没有任何文件描述符就绪,select 将返回 0。select 失败时返回 -1并设置 errno。如果在 select等待期间,程序接收到信号,则 select 立即返回-1,并设置errno 为 EINTR。

1.1.2 文件描述符就绪条件

哪些情况下文件描述符可以被认为是可读、可写或者出现异常,对于 select 的使用非常关键。在网络编程中,下列情况下 socket 可读:

  • socket 内核接收缓存区中的字节数大于或等于其低水位标记SO_RCVLOWAT。此时我们可以无阻塞地读该 socket,并且读操作返回的字节数大于 0,

  • socket 通信的对方关闭连接。此时对该 socket 的读操作将返回0 监听 socket 上有新的连接请求。

  • socket 上有未处理的错误。此时我们可以使用 getsockopt 来读取和清除该错误。

  • 下列情况下 socket 可写:

    • socket 内核发送缓存区中的可用字节数大于或等于其低水位标记S OSNDLOWAT此时我们可以无阻寨地写该 socket,并且写操作返回的字节数大于0。
    • socket 的写操作被关闭。对写操作被关闭的 sockct 执行写操作将触发一个SIGPIPE信号
    • socket 使用非阻塞 connect 连接成功或者失败《超时)之后。
    • socket 上有未处理的错误。此时我们可以使用 getsockopt 来读取和清除该错误。

    网络程序中,select 能处理的异常情况只有一种:socket 上接收到带外数据

带外数据: 有些传输层协议具有带外(Out Of Band,OOB)数据的概念,用于迅速通告对方本端发生的重要事件。 因此,带外数据比普通数据(也称为带内数据)有更高的优先级,它应该总是立即被发送,而不论发送缓冲区中是否有排队等待发送的普通数据

1.1.3 处理带外数据

同时处理普通数据和带外数据

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

int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
		printf( "usage: s ip address port_number\n"basename( argv[0] ) )
        return 1;
	}
}


1.1.4 示例代码

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/select.h>
#include<time.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<list>
using namespace std;
int socket_init();

int main()
{
    int sockfd = socket_init();
    if(sockfd == -1)
    {
        cout<<"socket init err\n"<<endl;
        exit(1);
    }
    list<int> fds;
    fds.push_back(sockfd);

    fd_set fdset;//读事件
	fd_set fdexpcetion;//异常事件
    while(true)
    {
        FD_ZERO(&fdset);
        int maxfd = -1;
        for(auto iter = fds.begin();iter !=fds.end();++iter)
        {
            if(*iter<0)
                continue;
            
            FD_SET(*iter,&fdset);

            if(*iter <maxfd)
            {
                maxfd = *iter;
            }
        }

         struct timeval tv = {5,0};

        int n = select(maxfd+1,&fdset,NULL,NULL,&tv);
        if(n < 0)
        {
            cout<<"select err"<<endl;
        }
        else if( n == 0)//超时
        {
            cout<<"time over"<<endl;
        }
        else
        {
            for(auto iter = fds.begin();iter != fds.end();++iter)
            {
                if(*iter <0)
                {
                    continue;
                }
                if(FD_ISSET(*iter,&fdset))//有读事件就绪
                {
                    if(*iter == sockfd)//accept 监听套接字
                    {
                        struct sockaddr_in caddr;
                        socklen_t len = sizeof(caddr);
                        int c = accept(*iter,(struct sockaddr*)&caddr,&len);
                        if(c<0)
                        {
                            continue;
                        }
                        fds.push_back(c);
                        cout<<"accept:c = "<<c<<endl;
                        
                    }  
                    else if
                    {
                        char buff[128];
                        int num = recv(*iter,buff,num,0);
                        if(num <= 0)
                        {
                            close(*iter);
                            cout<<"client close"<<endl;
                            fds.erase(iter);
                            continue;
                        }
                        cout<<"c = "<<*iter<<"buff = "<<buff<<endl;
                        send(*iter,"ok",2,0);
                    }
                }
                /* 对于异常事件,采用带 MSG_OOB 标志的 recv 图数读取带外数据“ */
                if(FD_ISSET(*iter,&fdexception))
                {
                    char buff[128];
                    int num = recv(*iter,buff,num,MSG_OOB);
                    if(num <= 0)
                    {
                        close(*iter);
                        cout<<"client close"<<endl;
                        fds.erase(iter);
                        continue;
                    }
                    cout<<"c = "<<*iter<<"buff = "<<buff<<endl;
                    send(*iter,"no",2,0);
                }
        }
    }

}

int socket_init()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        printf("socket err\n");
        exit(0);
    }
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //绑定
    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(res == -1)
    {
        printf("bind err\n");
        return(-1);
    }
    //创建监听队列
    res = listen(sockfd,0);
    if(res == -1)
    {
        return(-1);
    }
    return(sockfd);
}

1.2 poll 系统调用

poll 系统调用和 select 类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。poll 的原型如下:

#include <pol1.h>
int poll( struct pollfd* fds, nfds_t nfds, int timeout );

fds 参数是一个 pollfd 结类型的组,它指定所有我们感兴趣的文件描述符上发生的可读、可写和异常等事件。pollfd 结构体的定义如下:

struct pollfd
{
	int fd;       /*文件描述符 */
	short events; /*注册的事件 */
	short revents;/* 实际发生的事件,由内核填充 */
}

其中,fd 成员指定文件描述符:

events 成员告诉 poll 监听 fd 上的哪些事件,它是一系列事件的按位或;

revcnts 成员则由内核修改,以通知应用程序fd 上实际发生了哪些事件。

poll支持的事件类型如表9-1 所示。

在这里插入图片描述
在这里插入图片描述

表9-1中,POLLRDNORM、POLLRDBAND、POLLWRNORM、POLLWRBAND由XOPEN 规范定义。它们实际上是将 POLLIN 事件和 POLLOUT 事件分得更细致,以区别对待普通数据和优先数据。但 Linux 并不完全支持它们。

通常,应用程序需要根据 recv 调用的返回值来区分 socket 上接收到的是有效数据还是对方关闭连接的请求,并做相应的处理。不过,自 Linux 内核 2.6.17 开始,GNU 为 pol 系统调用增加了一个POLLRDHUP 事件,它在 socket 上接收到对方关闭连接的请求之后触发。这为我们区分上述两种情况提供了一种更简单的方式。但使用 POLLRDHUP 事件时,我们需要在代码最开始处定义 GNU SOURCE。

  • nfds 参数指定被监听事件集合 fds 的小。其类nfds t的定如下typedef unsigned long int nfds t;
  • timcout 参数指定 poll 的超时值,单位是毫秒。当timeout 为-1 时,poll 调用将永远阻塞,直到某个事件发生;当 timcout 为0时,poll 调用将立即返回。poll 系统调用的返回值的含义与 select 相同。

1.2.1示例代码

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

#define  MAXFD   10

int socket_init();

void fds_init(struct pollfd fds[])
{
    for(int i = 0; i < MAXFD; i++ )
    {
        fds[i].fd = -1;
        fds[i].events = 0;//注册
        fds[i].revents = 0;//内核返回的事件
    }
}

void fds_add(struct pollfd fds[], int fd)
{
    for( int i = 0; i < MAXFD; i++ )
    {
        if( fds[i].fd == -1 )
        {
            fds[i].fd = fd;
            fds[i].events = POLLIN;
            fds[i].revents = 0;
            break;
        }
    }
}

void fds_del(struct pollfd fds[], int fd)
{
    for( int i = 0; i < MAXFD; i++)
    {
        if ( fds[i].fd == fd)
        {
            fds[i].fd = -1;
            fds[i].events = 0;
            fds[i].revents = 0;
            break;
        }
    }
}

void accept_client(struct pollfd fds[], int sockfd)
{
    int c = accept(sockfd,NULL,NULL);
    if ( c < 0 )
    {
        return ;
    }

    printf("accept c=%d\n",c);
    fds_add(fds,c);
}

void recv_data(struct pollfd fds[], int c)
{
    char buff[128] = {0};
    int n = recv(c,buff,1,0);
    if ( n <= 0 )
    {
        close(c);
        fds_del(fds,c);
        printf("client close\n");
        return;
    }

    printf("recv:%s\n",buff);
    send(c,"ok",2,0);
}
int main()
{
    int sockfd = socket_init();
    if ( sockfd == -1 )
    {
        exit(1);
    }

    struct pollfd fds[MAXFD];
    fds_init(fds);
    fds_add(fds,sockfd);//

    while( 1 )
    {
        int n = poll(fds,MAXFD,5000);//阻塞
        if ( n == -1 )
        {
            printf("poll err\n");
        }
        else if ( n == 0 )
        {
            printf("time out\n");
        }
        else
        {
            for( int i = 0; i < MAXFD; i++)
            {
                if ( fds[i].fd == -1)
                {
                    continue;
                }

                if ( fds[i].revents & POLLIN)
                {
                    if ( fds[i].fd == sockfd )
                    {
                        accept_client(fds,fds[i].fd);
                    }
                    else
                    {
                        recv_data(fds,fds[i].fd);
                    }
                }
                //if ( fds[i].revents & POLLOUT)
                //{
                //}
            }
        }
    }


}

int socket_init()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if ( sockfd == -1)
    {
        return -1;
    }
    
    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if( res == -1)
    {
        printf("bind err\n");
        return -1;
    }

    res = listen(sockfd,5);
    if ( res == -1 )
    {
        return -1;
    }   

    return sockfd;
}


1.3 epoll 系统调用

1.3.1 内核事件表

epoll 是 Linux 特有的I/0 复用函数。它在实现和使用上与 select、poll 有很大差异。首先,epoll 使用一组函数来完成任务,而不是单个函数。其次,epoll 把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须 slet 和 pol 那每次用都要重复传人文件描述符集或事件集。但 epoll需要使用一个额外的文件描述符,来唯一识内核中的这个事件表。这个文件描述符使用如下 epoll create 函数来创建:

#include <sys/epo11.h>
int epoll create( int size )

size 参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。该函数返回的文件描述符将用作其他所有 poll 系统调用的第一个参数,以指定要访问的内核事件表。

//下面的函数用来操作 cpol1 的内核事件表:
#include <sys/epoll.h>
int epoll_ctl( int epfd, int op, int fd, struct_epoll event *event )

fd 参数是要操作的文件描述符,

op 参数则指定操作类型。操作类型有如下3种

  • EPOLL CTL ADD,往事件表中注册 fd 上的事件。
  • EPOLL_CTT_MOD,修改 fd 上的注册事件。
  • EPOLL CTL DEL,删除 fd 上的注册事件。

event 参数指定事件,它是epoll_event结构指针类型epoll_event 的定义如下

struct epoll_event
{
	_uint32_t events;/*epoll 事件 */
	epoll_data_t data;/*用户数据*/
}

其中events 成员描述事件类型。epoll 支持的事件类型和 poll 基相同表示pll事件类型的宏是在 poll 对应的宏前加上“E”,比如epoll 的数据可读事件是 EPOLLIN。 epoll有两个额外的事件类型一-EPOLLET 和EPOLLONESHOT。它们对于epoll的高效运作非常关键。

data 成员用于存储用户数据,其类型 epoll data t的定义如下

typedef union epoll_data
{
	void* ptr;
	int fd;
	uint32_t u32;
	uint64_t u64;
}epoll_data_t;

epoll_data_t是一个联合体,其 4 个成员中使用最多的是 fd,

  • fd指定事件所从属的目标文件描述符。
  • ptr 成员可用来指定与 fd 相关的用户数据。于 poll_data_t 是一个联合体,我们不能同时使用其 ptr 成员和 f 成员,因此,如果要将文件描述符和用户数据关联起来(将句柄和事件处器绑定一样),以实现快速的数据访问,只能使用其他手段,比如放弃使用 epoll_data_t 的 fd 成员,而在 ptr 指的用户数据中包含 fd
  • epoll ctl 成功时返回0,失败则返-1并设值errno

1.3.2 epoll_wait 函数

epoll系列系统调用的主要接口是 epoll_wait 函。它在一超时时间内等待一组文件描述符上的事件,其原型如下:

#include <sys/epoll.h>
int epoll_wait( int epfd, struct epoll event* events, int maxevents,int timeout );

该函数成功时返回就绪的文件描述符的个数,失败时返回-1 并设置errno。

关于该函数的参数,。timeout 参数的含义与 poll 接口的 timeout 参数相同。maxevents 参数指定最多监听多少个事件,它必须大于0。
epoll_wait 函数如果检测到事件,就将所有就绪的事件从内核事件表(由epfd 参数指定)中复制到它的第二个参数 events 指向的数组中。这个数组只用于输出 epoll_wait 检测到的就绪事件,而不像 select 和 poll 的数组参数那样,即用于传用户注册的事件,又用于输出内核检测到的就绪事件。这就极大地提高了应用程序索引就绪文件描述符的效率

在这里插入图片描述

1.3.3 LT 模式 与ET 模式

epoll对文件描述符的操作有两种模式:LT(Level Trigger,电平触发)模式和 ET(EdgeTrigger,边沿触发)模式。LT 模式是默认的工作模式,这种模式下 epoll相当于一个效率较高的 epoll。当往 epoll内核事件表中注册一个文件描述符上的 EPOLLET 事件时,epoll 将以ET模式来操作该文件描述符。ET 式是 epoll 的高效工作模式
对于采用 LT 工作模式的文件描述符,当pol1_wait 检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait 时,cpol1_wait 还会再次向应用程序通告此事件,直到该事件被处理。而对于采用 ET 工作模式的文件描述符,当 epoll wait 检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的 epoll wait 调用将不再向应用程序通知这一事件。可见,ET 模式在很大程度上降低了同一个 epoll 事件被重复触发的次数因此效率要比 LT 模式高

1.3.4 示例代码

注意:每个使用 ET 模式的文件描述符都应该是非阻塞的。如果文件描述符是阻塞的,那么读或写操作将会因为没有后续的事件而一直处于阻塞状态 (饥渴状态)。

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

#define MAXFD 10

int socket_init();

void setnonblock(int fd)
{
    int oldfl = fcntl(fd, F_GETFL);
    int newfl = oldfl | O_NONBLOCK;
    if (fcntl(fd, F_SETFL, newfl) == -1)
    {
        printf("set nonblock err\n");
    }
}
void epoll_add(int epfd, int fd)
{
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = EPOLLIN | EPOLLET; // 开启ET模式

    setnonblock(fd); // 设置非阻塞

    if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1)
    {
        printf("epoll ctl err\n");
    }
}

void epoll_del(int epfd, int fd)
{
    if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1)
    {
        printf("epoll del err\n");
    }
}
void accept_client(int epfd, int sockfd)
{
    int c = accept(sockfd, NULL, NULL);
    if (c < 0)
    {
        return;
    }

    printf("accept c=%d\n", c);

    epoll_add(epfd, c);
}

void recv_data(int epfd, int c)
{
    while (1)
    {
        char buff[128] = {0};
        int n = recv(c, buff, 1, 0);
        if ( n == -1 )
        {
            if ( errno != EAGAIN && errno != EWOULDBLOCK)
            {
                printf("recv err\n");
            }
            else
            {
                send(c,"ok",2,0);
            }
            break;
        }
        else if ( n == 0 )//对方关闭,-》移除描述符,并关闭
        {
            epoll_del(epfd,c);
            close(c);
            printf("client close\n");
            break;
        }
        else
        {
            printf("read(%d):%s\n",c,buff);
        }
    }
    /*
    char buff[128] = {0};
    int n = recv(c,buff,1,0);
    if ( n <= 0 )
    {
        epoll_del(epfd,c);
        close(c);
        printf("client close\n");
        return;
    }

    printf("recv(%d)=%s\n",c,buff);
    send(c,"ok",2,0);
    */
}
int main()
{
    int sockfd = socket_init();
    if (sockfd == -1)
    {
        exit(1);
    }

    // 创建内核事件表(红黑树)
    int epfd = epoll_create(MAXFD); // 参数大于0
    if (epfd == -1)
    {
        exit(1);
    }

    // 向内核事件表添加sockfd
    epoll_add(epfd, sockfd);

    struct epoll_event evs[MAXFD]; // 收集就绪描述符

    while (1)
    {
        int n = epoll_wait(epfd, evs, MAXFD, 5000);
        if (n == -1)
        {
            printf("epoll wait err\n");
        }
        else if (n == 0)
        {
            printf("time out\n");
        }
        else
        {
            for (int i = 0; i < n; i++)
            {
                int fd = evs[i].data.fd;
                if (evs[i].events & EPOLLIN)
                {
                    if (fd == sockfd)
                    {
                        accept_client(epfd, fd);
                    }
                    else
                    {
                        recv_data(epfd, fd);
                    }
                }
                // if ( evs[i].events & EPOLLOUT)
                //{

                //}
            }
        }
    }
}
int socket_init()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        return -1;
    }

    struct sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if (res == -1)
    {
        printf("bind err\n");
        return -1;
    }

    res = listen(sockfd, 5);
    if (res == -1)
    {
        return -1;
    }

    return sockfd;
}

1.3.5 EPOLLONESHOT 事件

即使我们使用 ET 模式,一个 socket 上的某个事件还是可能被触发多次。这在并发程序中就会引起一个问题。比如一个线程(或进程,下同)在读取完某个 socket 上的数据后开始处理这些数据,而在数据的处理过程中该 socket 上又有新数据可读(EPOLLIN再次被触发,此时另外一个线程被唤醒来读取这些新的数据。于是就出现了两个线同时操作一个sockct 的局面。这当然不是我们期望的。我们期望的是一个 socket 连接在任一时刻都只被一个线程处理。这一点可以使用epoll 的 EPOLLONESHOT 事件实现。对于注册了 EPOLLONESHOT 事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非我们使用 po1 ctl 函重置该文件描述符上注册的 EPOLLONESHOT 事件。这样,当一个线程在处理某个 socket 时,其他线是不可能有机会操作该 socket 的。但反过来思考,注册了 EPOLLONESHOT事件的 socket一且被某个线程处理完毕,该线程就应该立即重置这个 socket 上的 EPOLLONESHOT事件,以确保这个socket 下一次可读时,其 EPOLLIN 事件能被触发,进而让其他工作线程有机会继续处理这个 socket。

具体使用可见 《Linux 高性能服务器编程》 9.3.4

1.4 三组I/O 复用 函数的 比较

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奥特神龟3.1.4

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值