前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >I/O多路复用select/poll/epoll

I/O多路复用select/poll/epoll

原创
作者头像
WindSun
修改于 2019-09-09 06:45:12
修改于 2019-09-09 06:45:12
1.3K0
举报
文章被收录于专栏:编程技术专栏编程技术专栏

前言

早期操作系统通常将进程中可创建的线程数限制在一个较低的阈值,大约几百个。因此, 操作系统会提供一些高效的方法来实现多路IO,例如Unix的select和poll。现代操作系统中,线程数已经得到了极大的提升,如NPTL线程软件包可支持数十万的线程。

I/O多路复用

select

select 允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或指定时间后返回它。

select函数原型

代码语言:txt
AI代码解释
复制
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout);

返回值:监听到有事件发生的文件描述符的个数,超时为0,错误为 -1.

1.当监视的相应的文件描述符集中满足条件时,比如说读文件描述符集中有数据到来时,内核(I/O)根据状态修改文件描述符集,并返回一个大于0的数。 2.当没有满足条件的文件描述符,且设置的timeval监控时间超时时,select函数会返回一个为0的值。 3.当select返回负值时,发生错误。

参数:

maxfd:是需要监视的最大的文件描述符值+1;

rdset、wrset、exset:是传入传出参数,fd_set类型,分别对应于需要检测的可读文件描述符的集合、可写文件描述符的集合、异常文件描述符的集合。若对其中任何参数条件不感兴趣,则可将其设为NULL。

timeout:设置超时时间,指定select在返回前没有接收事件时应该等待的时间。

timeval 结构体

代码语言:txt
AI代码解释
复制
struct timeval{
    long tv_sec;    // 秒
    long tv_usec;   // 微秒
}

timeout参数的三种可能:

NULL:永远等待下去,仅在有描述符就绪时才返回 正常时间:在不超过timeout设置的时间内,在有描述符就绪时返回 0:检查描述符的每位后立即返回(轮询) 

描述符集合fd_set操作函数

系统提供了4个宏对描述符集进行操作:

代码语言:txt
AI代码解释
复制
#include <sys/select.h>
#include <sys/time.h>
void FD_SET(int fd, fd_set *fdset);   // 设置文件描述符集fdset中对应于文件描述符fd的位(设置为1)
void FD_CLR(int fd, fd_set *fdset);   // 清除文件描述符集fdset中对应于文件描述符fd的位(设置为0)
void FD_ISSET(int fd, fd_set *fdset); // 检测文件描述符集fdset中对应于文件描述符fd的位是否被设置
void FD_ZERO(fd_set *fdset);          // 清除文件描述符集fdset中的所有位(既把所有位都设置为0)

理解select模型

  理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。

  (1)执行fd_set set,FD_ZERO(&set),则set用位表示是0000,0000。

  (2)若fd=5,执行FD_SET(fd,&set),后set变为0001,0000(第5位置为1)

  (3)若再加入fd=2,fd=1,则set变为0001,0011

  (4)执行select(6,&set,0,0,0)阻塞等待

  (5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

select模型的描述符集合,内部实现是位图,这些参数指明了我们关心哪些描述符,和需要满足什么条件(可写,可读,异常)。fd_set类型变量每一位代表了一个描述符。我们也可以认为它只是一个由很多二进制位构成的数组。

图1
图1

select的第一个参数是最大的描述符+1,表示描述符大小的范围,此时仅将描述符当做一个数看待,它会遍历从0到maxfd+1个位置

将感兴趣的描述符加入对应的集合中,调用select,它会遍历maxfd+1个描述符,如果有条件满足,内核(I/O)根据状态修改文件描述符集,并返回有事件发生的描述符的个数。此时描述符集合fd_set中的描述符被修改了,集合中都是有事件发生的。

select模型特点

基于上面的讨论,可以轻松得出select模型的特点

(1)可监控的文件描述符个数取决与fd_set的值。一般为1024,每bit表示一个文件描述符,则支持的最大文件描述符是1024。可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。

(2)每次调用 select(),都需要把描述符集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大,同时每次调用 select() 都需要在内核中轮询最大描述符数+1个描述符,这个开销在 fd 很多时也很大。将文件描述符fd加入到事件集合中,还需要用一个数组,将文件描述符fd保存起来。一是,用于在select返回之后,fd_set参数中已经被修改为都是有事件发生的文件描述符位,这个数组中的文件描述符可以用FD_ISSET来轮询对发生事件后的集合中的描述符判断;二是,select返回后会把以前加入的但并无事件发生的fd的位清空,下一次开始 select 前要重新从数组中取得文件描述符逐个加入到 fd_set 中( FD_ZERO 最先),扫描数组的同时取得文件描述符的最大值 maxfd ,用于 select 的第一个参数。

(3)返回后的集合,需要轮询数组中保存的描述符的每一个与集合中进行FD_ISSET操作,排查当文件描述符个数很多时,效率很低。

select模型实例

代码语言:txt
AI代码解释
复制
#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
    int i, maxi, maxfd, listenfd, connfd, sockfd;
    int nready, client[FD_SETSIZE]; // FD_SETSIZE 默认为 1024
    ssize_t n;
    fd_set rset, allset;	//fd_set类型的rset、allset
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    socklen_t cliaddr_len;
    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(SERV_PORT);

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

    Listen(listenfd, 20);

    maxfd = listenfd;
    maxi = -1;

    for (i = 0; i < FD_SETSIZE; i++)
        client[i] = -1;

    FD_ZERO(&allset);	//将所有集合清零
    FD_SET(listenfd, &allset); // 将监听套接字加入到集合中

    for (;;)
    {
        rset = allset; // 每次循环时都从新设置select监听集合
        nready = select(maxfd + 1, &rset, NULL, NULL, NULL);	//nready是发生事件的描述符的个数

        if (nready < 0)
            perr_exit("select error");
        if (FD_ISSET(listenfd, &rset))	//如果listenfd在返回的rset中
        { 
            //新客户端请求连接
            cliaddr_len = sizeof(cliaddr);
            connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);	//conn接收客户端的套接字描述符
            printf("received from %s at PORT %d\n",
                   inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                   ntohs(cliaddr.sin_port));
            for (i = 0; i < FD_SETSIZE; i++)
            {
                if (client[i] < 0)
                {
                    client[i] = connfd; //保存接收客户端的描述符到client[]里
                    break;
                }
            }
            //达到select能监控的文件个数上限 1024
            if (i == FD_SETSIZE)
            {
                fputs("too many clients\n", stderr);
                exit(1);
            }

            FD_SET(connfd, &allset); // 添加接受的客户端的描述符到监听集合中
            if (connfd > maxfd)	//是否更新maxfd
                maxfd = connfd; 
            if (i > maxi)	//是否更新client[]的最大下标
                maxi = i;

            if (--nready == 0)
                continue; // 如果没有更多的就绪文件描述符继续回到上面select阻塞监听(仅监听到新客户端的连接)
        }
        for (i = 0; i <= maxi; i++)
        { 
            if ((sockfd = client[i]) < 0)	//取出client[]中>0的描述符
                continue;
            if (FD_ISSET(sockfd, &rset))	//检测clients[]中哪个描述符在返回的rset可读事件集合中
            {
                if ((n = Read(sockfd, buf, MAXLINE)) == 0)
                {
                    Close(sockfd);           /* 当client关闭链接时,服务器端也关闭对应链接 */
                    FD_CLR(sockfd, &allset); /* 解除select监控此文件描述符 */
                    client[i] = -1;
                }
                else
                {
                    int j;
                    for (j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]);
                    Write(sockfd, buf, n);
                }
                if (--nready == 0)
                    break;
            }
        }
    }
    close(listenfd);
    return 0;
}

poll

poll 和 select 系统调用的本质一样,poll 的机制与 select 类似,与 select 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理。

poll函数原型

代码语言:txt
AI代码解释
复制
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

返回值:

返回值 < 0,表示出错 返回值 == 0,表示poll函数等待超时 返回值 > 0,表示poll由于监听的文件描述符就绪返回,并且返回结果就是就绪的文件描述符的个数。

参数:

fds:一个结构数组,struct pollfd结构如下:

代码语言:txt
AI代码解释
复制
struct pollfd{
    int fd;          //要监听的文件描述符
    short events;    //需要监听的事件(读、写、异常)
    short revents;   //调用poll后的结果事件,内核在调用返回时设置这个事件
};

events & revents的取值如下:

常量

说明

POLLIN

普通或优先级带数据可读

POLLRDNORM

普通数据可读

POLLRDBAND

优先级带数据可读

POLLPRI

高优先级数据可读

POLLOUT

普通数据可写

POLLWRNORM

普通数据可写

POLLWRBAND

优先级带数据可写

POLLERR

发生错误

POLLHUP

发生挂起

POLLNVAL

描述字不是一个打开的文件

nfds:要监视的描述符的数目。经过测试,如果监听了两个fd,但是nfds==1的情况下,只有fdarray0.fd能被监听到。

timeout:用毫秒表示的时间,是指定poll在返回前没有接收事件时应该等待的时间。

timeout值

说明

-1

永远等待

0

立即返回,不阻塞进程

>0

等待指定数目的毫秒数

poll模型的特点

(1)poll没有最大连接数的限制,原因是它是基于链表来存储的。在select中,被监听集合和返回集合是一个集合,在poll中将监听和返回的事件都在结构体中不同的成员中,它们互补干扰,poll 中将有事件发生的文件描述符设置其结构体的revents,不需要向select一样用一个数组存储原来的文件描述符。

(2)poll函数中fds数组中元素是pollfd结构体,该结构体保存描述符的信息,每增加一个文件描述符就向数组中结构体加入一个描述符,结构体只需要拷贝一次到内核态。poll解决了select重复初始化的问题。但轮寻检查事件发生的问题仍然未解决。

(3)与select一样,poll返回后,需要轮询每个pollfd结构体的revents来获取就绪的描述符,这样会使性能下降 ,poll会遍历到数组已使用的最大下标,如果同时连接的大量客户端在一时刻可能只有很少的就绪状态,就是最大下标很大,而只有几个描述符发生事件,因此随着监视的描述符数量的增长,其效率也会线性下降。

poll模型实例

代码语言:txt
AI代码解释
复制
#define MAXLINE 80
#define SERV_PORT 8000
#define OPEN_MAX 1024

int main(int argc, char *argv[])
{
    int i, j, maxi, listenfd, connfd, sockfd;
    int nready; //接收poll返回值, 记录发生事件的fd个数
    ssize_t n;

    char buf[MAXLINE], str[INET_ADDRSTRLEN];
    socklen_t clilen;
    struct pollfd client[OPEN_MAX];	//client[]中存放pollfd类型结构体
    struct sockaddr_in cliaddr, servaddr;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    Listen(listenfd, 128);

    client[0].fd = listenfd;   // 要监听的第一个文件描述符 存入client[0]
    client[0].events = POLLIN; // listenfd监听读事件

    for (i = 1; i < OPEN_MAX; i++)
        client[i].fd = -1; // 用-1初始化client[]里剩下元素,0也是文件描述符,不能用

    maxi = 0; // client[]数组有效元素中最大元素下标

    for (;;)
    {
        nready = poll(client, maxi + 1, -1); // 阻塞监听

        if (client[0].revents & POLLIN)	//有客户端连接
        {
            clilen = sizeof(cliaddr);
            connfd = Accept(client[0].fd, (struct sockaddr *)&cliaddr, &clilen); /* 接收客户端请求 Accept 不会阻塞 */
            printf("received from %s at PORT %d\n",
                   inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                   ntohs(cliaddr.sin_port));

            for (i = 1; i < OPEN_MAX; i++)	//找到client[]中空闲的位置存放接收客户端的套接字描述符
                if (client[i].fd < 0)
                {
                    client[i].fd = connfd; 
                    break;
                }

            if (i == OPEN_MAX) // 达到了最大客户端数
                perr_exit("too many clients");

            client[i].events = POLLIN; // 将接收客户端的套接字描述符的事件设置位读
            if (i > maxi)
                maxi = i; // 更新client[]中最大元素下标
            if (--nready <= 0)	//是否仅有客户端连接一个事件
                continue; 
        }

        for (i = 1; i <= maxi; i++)//有多个事件发生,轮询检测client[] 看是那个connfd就绪
        { 
            if ((sockfd = client[i].fd) < 0)	//每个sockfd是否有效>0
                continue;

            if (client[i].revents & POLLIN)	//每个结构体的revents是否是可读事件
            {
                if ((n = Read(sockfd, buf, MAXLINE)) < 0)
                {
                    if (errno == ECONNRESET)
                    { 
                        // 收到RST标志
                        printf("client[%d] aborted connection\n", i);
                        Close(sockfd);
                        client[i].fd = -1; // poll中不监控该文件描述符,直接置为-1即可,不用像select中那样移除
                    }
                    else
                        perr_exit("read error");
                }
                else if (n == 0)
                { // 说明客户端先关闭链接
                    printf("client[%d] closed connection\n", i);
                    Close(sockfd);
                    client[i].fd = -1;
                }
                else
                {
                    for (j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]);
                    Writen(sockfd, buf, n);
                }
                if (--nready <= 0)
                    break;
            }
        }
    }
    return 0;
}

epoll

epoll 是之前的 select 和 poll 的增强版本。相对于 select 和 poll 来说,epoll更加灵活,没有描述符限制。epoll使用一个epoll句柄管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

epoll相关函数

代码语言:txt
AI代码解释
复制
int epoll_create(int size);

创建一个epoll文件描述符,相当于一个句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

返回值:返回一个文件描述符fd,可以理解为指向内核中的一颗红黑树的树根,size就是创建红黑树的大小。

代码语言:txt
AI代码解释
复制
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件注册函数,它在这里注册要监听的事件类型。参数 epfd 是epoll_create()的返回值的描述符;参数 op 表示动作,用三个宏来表示,控制某个epoll监听的文件描述符上的事件:添加、修改、删除。相当于在红黑树上操作。参数 fd 是需要监听事件的文件描述符,参数 event 是告诉内核需要监听什么事件。

返回值:成功返回0,不成功返回1

事件注册函数的第二个参数的动作,有四个宏表示:

EPOLL_CTL_ADD:注册新的fd到epfd中 EPOLL_CTL_MOD:修改已经注册的fd的监听事件 EPOLL_CTL_DEL:从epfd中删除一个fd

struct epoll_event结构如下:

代码语言:txt
AI代码解释
复制
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队列里.

代码语言:txt
AI代码解释
复制
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

参数 fd 是epoll_create返回的文件描述符;参数 events 是一个数组,传入传出参数;参数 maxevents 告之内核这个events数组有多大(数组成员的个数),这个 maxevents 的值不能大于创建epoll_create()时的size;参数 timeout 设置超时时间(-1 阻塞,0 立即返回,非阻塞,>0 指定毫秒)。

返回值: 成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1。返回的有事件发生的描述符都在 events 数组中,数组中实际存放的成员个数是函数的返回值个。

epoll模型的特点

(1)本身没有最大并发连接的限制,仅受系统中进程能打开的最大文件数目限制。

(2)基于事件就绪通知方式:一旦被监听的某个文件描述符就绪,内核会采用类似于callback的回调机制,迅速激活这个文件描述符,这样随着文件描述符数量的增加,也不会影响判定就绪的性能。不会像select/poll中轮询检测每个描述符是否就绪。

(3)当文件描述符就绪,就会被放到一个数组中,这样调用epoll_weit获取就绪文件描述符的时候,只要取数组中的返回的个数个元素即可,不需要全部做轮询检测。

(4)内存拷贝是利用mmap()文件映射内存的方式加速与内核空间的消息传递,减少复制开销。(内核与用户空间共享一块内存)

epoll模型实例

代码语言:txt
AI代码解释
复制
#define MAXLINE 8192
#define SERV_PORT 8000
#define OPEN_MAX 5000

int main(int argc, char *argv[])
{
    int i, listenfd, connfd, sockfd;
    int n, num = 0;
    ssize_t nready, efd, res;
    char buf[MAXLINE], str[INET_ADDRSTRLEN];
    socklen_t clilen;

    struct sockaddr_in cliaddr, servaddr;
    struct epoll_event tep, ep[OPEN_MAX]; //tep: epoll_ctl参数  ep[] : epoll_wait参数

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //端口复用

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

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

    Listen(listenfd, 20);

    efd = epoll_create(OPEN_MAX); //创建epoll模型, efd指向红黑树根节点
    if (efd == -1)
        perr_exit("epoll_create error");

    tep.events = EPOLLIN;
    tep.data.fd = listenfd;                              //指定lfd的监听时间为"读"
    res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep); //将lfd及对应的结构体设置到树上,efd可找到该树
    if (res == -1)
        perr_exit("epoll_ctl error");

    for (;;)
    {
        /*epoll为server阻塞监听事件, ep为struct epoll_event类型数组, OPEN_MAX为数组容量, -1表永久阻塞*/
        nready = epoll_wait(efd, ep, OPEN_MAX, -1);
        if (nready == -1)
            perr_exit("epoll_wait error");

        for (i = 0; i < nready; i++)
        {
            if (!(ep[i].events & EPOLLIN)) //如果不是"读"事件, 继续循环
                continue;

            if (ep[i].data.fd == listenfd)
            { //判断满足事件的fd是不是lfd
                clilen = sizeof(cliaddr);
                connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); //接受链接

                printf("received from %s at PORT %d\n",
                       inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                       ntohs(cliaddr.sin_port));
                printf("cfd %d---client %d\n", connfd, ++num);

                tep.events = EPOLLIN;
                tep.data.fd = connfd;
                res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
                if (res == -1)
                    perr_exit("epoll_ctl error");
            }
            else
            { //不是lfd,
                sockfd = ep[i].data.fd;
                n = Read(sockfd, buf, MAXLINE);

                if (n == 0)
                {                                                      //读到0,说明客户端关闭链接
                    res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); //将该文件描述符从红黑树摘除
                    if (res == -1)
                        perr_exit("epoll_ctl error");
                    Close(sockfd); //关闭与该客户端的链接
                    printf("client[%d] closed connection\n", sockfd);
                }
                else if (n < 0)
                { //出错
                    perror("read n < 0 error: ");
                    res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
                    Close(sockfd);
                }
                else
                { //实际读到了字节数
                    for (i = 0; i < n; i++)
                        buf[i] = toupper(buf[i]); //转大写,写回给客户端

                    Write(STDOUT_FILENO, buf, n);
                    Writen(sockfd, buf, n);
                }
            }
        }
    }
    Close(listenfd);
    Close(efd);

    return 0;
}

epoll工作模式

epoll对文件描述符的操作有两种模式:水平触发LT(level trigger)和边缘触发ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。 ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

二者的主要差异在于level-trigger模式下只要某个socket处于readable/writable状态,无论什么时候进行epoll_wait都会返回该socket;而edge-trigger模式下只有某个socket从unreadable变为readable或从unwritable变为writable时,epoll_wait才会返回该socket。

描述符就绪条件

可读条件

(1) “监听socket”:该套接字是一个监听套接字且已完成的连接数不为0。而这样的套接字处于可读状态,是因为套接字收到了对方的connect请求,执行了三次握手的第一步:对方发送SYN请求过来,使该方监听套接字处于可读状态;通常情况下,对这样的套接字执行accept操作不会阻塞; (2)“已连接socket”:该套接字的接收缓冲区中的数据字节大于等于该套接字的接收缓冲区低水位标记的当前大小。对这样的套接字执行读操作不会阻塞并返回一个大于0的值(也就是返回准备好读入的数据)。可以用SO_RCVLOWAT套接字选项设置该套接字的低水位标记。对于TCPUDP套接字而言,其缺省值为1,这意味着,默认情况下,只要缓冲区中有数据,那就是可读的。 (3)“已连接socket”:该连接的读半部关闭(也就是接收了FIN的TCP连接)。对这样的套接字的读操作将不阻塞并返回0(也就是返回EOF),此时必须且一直会返回0; (4)“已连接socket”:其上有一个套接字错误待处理。对这样的套接字的读操作将不会阻塞并返回-1(即返回一个错误),同时把errno设置成确切的错误条件。这些待处理错误(pending error)也可通过指定SO_ERROR套接字选项调用getsockopt获取并清除。

可写条件

(1)“已连接socket/UDP socket”:该套接字发送缓冲区中的可用空间字节数大于等于该套接字的发送缓冲区低水位标记的当前大小(对于TCP的已连接socket或者UDP socket均可)。对这样的套接字的写操作将不阻塞并返回一个大于0的值(也就是返回准备好写入的数据)。可以用SO_SNDLOWAT套接字选项设置该套接字的低水位标记。对于TCP和UDP套接字而言,低水位默认值为2048,发送缓冲区默认大小为8K,这意味着,默认情况下,一个套接字连接成功后,总是可写的; (2)“已连接socket”:该连接的写半部关闭(主动发送了FIN的TCP连接)。对这样的套接字的写操作将产生SIGPIPE信号,该信号的缺省行为是终止进程; (3)“已连接socket”:其上有一个套接字错误待处理。对这样的套接字的写操作将不会阻塞并且返回-1(即返回一个错误),同时把errno设置成确切的错误条件。这些待处理的错误也可以通过指定SO_ERROR套接字选项调用getsockopt函数来取得并清除; (4)使用非阻塞式connect的套接字已建立连接,或者connect已经以失败告终,即connect已经完成。

异常条件

该套接字存在带外数据或者仍处于带外标记

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
「网络IO套路」当时就靠它追到女友
今天分享的基本上一面试就会被问的网络IO,文中涉及的代码部分不太重要,重要的是对这概念的理解。在看文章之前大家也可通过下面的思维导图看看自己是否能回答出来。
五分钟学算法
2020/09/27
5220
「网络IO套路」当时就靠它追到女友
select()函数详解
http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html
bear_fish
2018/09/20
1.8K0
select()函数详解
epoll,求知者离我近点
上网一搜epoll,基本是这样的结果出来:《多路转接I/O – epoll模型》,万变不离这个标题。 但是呢,不变的事物,我们就更应该抓出其中的重点了。 多路、转接、I/O、模型。 别急,先记住这几个词,我比较喜欢你们看我文章的时候带着问题。
看、未来
2020/08/25
5190
epoll,求知者离我近点
poll()函数总结
http://www.cnblogs.com/Anker/archive/2013/08/15/3261006.html
bear_fish
2018/09/20
2.3K0
poll()函数总结
I/O复用——单进程服务器(select版)
为了可以处理多个客户的请求,我们之前一直使用多进程TCP并发服务器,socket()监听一个套接口,accept()多个用户,父进程监听listenfd,子线程们在connfd上进行应答处理。
jackieluo
2019/01/06
2K0
I/O复用——单进程服务器(select版)
【Linux】I/O多路复用-SELECT/POLL/EPOLL
I/O多路复用 前言 文本相关参考资料及部分内容来源 《Linux高性能服务器编程》 《TCP/IP网络编程》 《Linux/UNIX系统编程手册》 ---- I/O多路复用核心思想为,使用一个线程,来处理多个客户端的请求。 或者说,使用一个特殊的fd,监视多个fd。 使得程序能同时监听多个文件描述符,这对提高程序的性能至关重要。 通常,网络程序在下列情况下需要使用I/O多路复用技术。 客户端程序需要同时处理多个socket。 客户端程序要同时处理用户输入和网络连接。 TCP服务器要同
半生瓜的blog
2023/05/13
1K0
【Linux】I/O多路复用-SELECT/POLL/EPOLL
linux 网络编程 I/O复用 select,poll ,epoll
http://blog.csdn.net/zs634134578/article/details/19929449
bear_fish
2018/09/20
2.6K0
详解I/O多路转接模型:select & poll & epoll
多路转接是IO模型的一种,这种IO模型通过select、poll或者epoll进行IO等待,可以同时等待多个文件描述符,当某个文件描述符的事件就绪,便会通知上层处理对应的事件。
二肥是只大懒蓝猫
2023/10/13
6580
详解I/O多路转接模型:select & poll & epoll
linux 下经典 IO 复用模型 -- epoll 的使用
epoll 是 linux 内核为处理大批量文件描述符而对 poll 进行的改进版本,是 linux 下多路复用 IO 接口 select/poll 的增强版本,显著提高了程序在大量并发连接中只有少量活跃的情况下的CPU利用率。 在获取事件时,它无需遍历整个被侦听描述符集,只要遍历被内核 IO 事件异步唤醒而加入 ready 队列的描述符集合就行了。 epoll 除了提供 select/poll 所提供的 IO 事件的电平触发,还提供了边沿触发,,这样做可以使得用户空间程序有可能缓存 IO 状态,减少 epoll_wait 或 epoll_pwait 的调用,提高程序效率。
用户3147702
2022/06/27
7030
linux 下经典 IO 复用模型 -- epoll 的使用
linux网络编程之socket(九):使用select函数改进客户端/服务器端程序
一、当我们使用单进程单连接且使用readline修改后的客户端程序,去连接使用readline修改后的服务器端程序,会出现一个有趣的现象,先来看输出: 先运行服务器端,再运行客户端, simba@ub
s1mba
2017/12/28
3.8K0
linux网络编程之socket(九):使用select函数改进客户端/服务器端程序
第6章 I/O复用:select和poll函数
I/O复用:一种预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪,它就通知进程。 同步I/O:导致请求的进程阻塞,直到I/O操作完成。 异步I/O:不导致请求进程阻塞。 I/O
_gongluck
2018/03/09
7550
第6章 I/O复用:select和poll函数
select的限制以及poll的使用
1.先说select在多路IO中的限制: 1)linux中每个程序能够打开的最多文件描述符是有限制的。默认是1024. 可以通过ulimit -n进行查看和修改:
xcywt
2022/05/09
1K0
select的限制以及poll的使用
linux网络编程之socket(十三):epoll 系列函数简介、与select、poll 的区别
一、epoll 系列函数简介 #include <sys/epoll.h> int epoll_create(int size); int epoll_create1(int flags); i
s1mba
2017/12/28
2.1K0
linux网络编程之socket(十二):select函数的并发限制和 poll 函数应用举例
一、用select实现的并发服务器,能达到的并发数,受两方面限制 1、一个进程能打开的最大文件描述符限制。这可以通过调整内核参数。可以通过ulimit -n来调整或者使用setrlimit函数设置, 
s1mba
2017/12/28
1.9K0
linux网络编程之socket(十二):select函数的并发限制和 poll 函数应用举例
高性能网络编程 - select、 poll 、epoll 、libevent
总之,这些是用于编程的工具和库,用于高效地处理多个 I/O 操作,特别是在网络通信的背景下。Select 和 poll 是较旧、性能较低的选项,而 epoll 是一种高性能的替代方案。Libevent 是一个库,简化了使用这些机制的工作,同时提供了跨不同平台的可移植性。
小小工匠
2023/11/09
6350
205-ESP32_SDK开发-TCP服务器(select方式,支持多连接,高速高并发传输)
https://www.cnblogs.com/orlion/p/6119812.html
杨奉武
2021/12/12
1.1K0
205-ESP32_SDK开发-TCP服务器(select方式,支持多连接,高速高并发传输)
epoll的使用实例
  在网络编程中通常需要处理很多个连接,可以用select和poll来处理多个连接。但是select都受进程能打开的最大文件描述符个数的限制。并且select和poll效率会随着监听fd的数目增多而下降。
xcywt
2022/05/09
7820
socket网络编程(四)——epoll多路复用问题
问大家一个问题,如果要设计一款有着千万级别并发的系统,你的客户端和服务端的网络通信底层该怎么设计?我在上一篇文章(socket网络编程(三)——select多路复用问题)中有说到用select可以实现IO多路复用,但是select的设计有瓶颈所在,超过十万的并发效率就非常慢。那么着又该怎么办呢?
一点sir
2024/01/10
3680
Linux下select的用法--实现一个简单的回射服务器程序
2. 函数说明:可以同时监控多个文件描述符是否发生了读写或者异常。(有点像windows下的waitformultipleobjects,可以同时等待多个事件) 参数说明: 1)nfds:要监控的文件描述符的最大值加1,这个值不能错。 2)readfds:指向fd_set的指针。这是一个集合,专门用于监视读取数据的。所有需要监控读取数据的描述符都需要放进这个集合中。比如你需要监控4描述符的读取数据,就把4放进这个集合之中。 3)writefds:同上,这里是专门监视写的集合 4)exceptfds:同上,这里是专门监视异常的集合 5)timeout:超时。指向的timeval 结构体。 如果参数设为NULL,则select是阻塞的。 如果不为空,则表示超时时间(当结构体里面的成员都设为0时,表示不阻塞,立即返回)。
xcywt
2022/05/09
6650
socket网络编程(三)——select多路复用问题
在上文《socket网络编程(二)—— 实现持续发送》我们提到了多客户端的时候,多台客户端发送数据到服务端的话,只能有一台客户端可以正常发送和接受数据,另外一台完全没有反应,那这个问题怎么解决呢?很多人可能第一反应想到利用多线程技术,线程多的话用线程池来维护。的确,多线程确实可以实现这个效果,但是,可能很多看见这个但是就不怎么开心了,却不知很多科学科技的进步都是这个但是引发的。但是一个多线程编程很麻烦又容易出错,二是如果连接有几千个的话,线程间切换的开销确实是很大。如果能够在一个线程里就实现这个效果的话,那该多好啊!
一点sir
2024/01/10
8220
推荐阅读
相关推荐
「网络IO套路」当时就靠它追到女友
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文