1.三者的特点

select特点

①使用位图(数组)实现对文件描述符集合的保存,最多允许同时监测1024个文件描述符

②需要应用层和内核层的反复数据(文件描述符集合表)拷贝

③返回的集合表需要遍历寻找到达的事件

④只能工作在水平触发模式(低速模式),不能工作在边沿触发模式(高速模式)

poll特点

①使用链表实现对文件描述符集合的存储,没有监测的文件描述符上限限制

②需要应用层和内核层的反复数据(文件描述符集合表)拷贝

③返回的集合表需要遍历寻找到达的事件

④只能工作在水平触发模式(低速模式),不能工作在边沿触发模式(高速模式)

epoll特点

①使用红黑树(二叉树)实现文件描述符集合的存储,没有文件描述符上限限制,提高查找效率

②将文件描述符集合创建在内核层,避免了应用层和内核层的反复数据拷贝

③返回到达事件,不需要遍历,只需要处理事件即可

④可工作在水平触发模式(低速模式),也可工作在边沿触发模式(高速模式)

特性 水平触发 (LT) 边沿触发 (ET)
通知频率 只要条件满足,持续通知 仅在状态变化时,通知一次
编程难度 简单,不易出错 复杂,容易因未处理完数据而导致 bug
性能 相对较低(可能多次通知同一事件) 更高(减少不必要的通知)
IO模式 阻塞和非阻塞IO均可 必须使用非阻塞IO
行为 更“宽容”,允许延迟或分批处理 更“苛刻”,要求必须一次性处理完
默认模式 epoll默认的模式 需要显式设置(EPOLLET flag)

2.epoll

(1)步骤

①创建文件描述符结合

②添加关注的文件描述符

③epoll通知内核开始进行事件监测

④epoll返回时,获取到达事件的结果

⑤根据到达事件做任务处理

(2)函数接口

int epoll_create(int size);
功能:通知内核创建文件描述符集合
参数: 
         size:监测的文件描述符个数
返回值:
      成功:文件描述符(代表内核创建的集合)
      失败:-1

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:对epoll的文件描述符集合进行操作
参数:
        epfd:创建的epoll集合
        op:对文件描述符集合进行的操作
                EPOLL_CTL_ADD  : 添加文件描述符到集合
                EPOLL_CTL_MOD : 修改集合中的文件描述符
                EPOLL_CTL_DEL   :删除集合中的文件描述符
        fd:要操作的文件描述符
        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: 读事件
                        EOPLLOUT:写事件
           data.fd : 关注的文件描述符

返回值:
         成功:0
         失败:-1

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
功能:通知内核开始监测文件描述符的事件
参数:
         epfd:监测的文件描述符集合
         events:保存返回的到达事件的结果(数组)

                    eg:struct epoll_event evs[MAX_FD_CNT];

        maxevents:最大的事件个数
        timeout:监测的超时时间
                          -1 :不设置超时(一直阻塞)返回值:
      成功:到达的事件的个数
      失败:-1
 

eg:

#include "head.h"

#define MAX_SIZE 1024
#define MAX_FD_CNT 2

int epoll_fd_add(int epfds, int fd, uint32_t events)
{
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = events;
    int ret = epoll_ctl(epfds, EPOLL_CTL_ADD, fd, &ev);
    if(ret < 0)
    {
        perror("epoll ctl error");
        return -1;
    }

    return 0;
}

int main(int argc, char const *argv[])
{
    char buff[MAX_SIZE] = {0};
    mkfifo("./myfifo", 0664);

    int fifofd = open("./myfifo", O_RDONLY);
    if(fifofd < 0)
    {
        perror("open error");
        return -1;
    }

    int epfds = epoll_create(MAX_FD_CNT);
    if(epfds < 0)
    {
        perror("epoll create error");
        return -1;
    }

    epoll_fd_add(epfds, 0, EPOLLIN);
    epoll_fd_add(epfds, fifofd, EPOLLIN);

    struct epoll_event evs[MAX_FD_CNT];

    while (1)
    {
        int cnt = epoll_wait(epfds, evs, MAX_FD_CNT, -1);
        if(cnt < 0)
        {
            perror("epoll wait error");
            return -1;
        }

        for (int i = 0; i < cnt; i++)
        {
            if(0 == evs[i].data.fd)
            {
                fgets(buff, sizeof(buff), stdin);
                printf("stdin = %s\n", buff);
            }
            else if(fifofd == evs[i].data.fd)
            {
                memset(buff, 0, sizeof(buff));
                ssize_t n = read(evs[i].data.fd, buff, sizeof(buff));
                if (0 == n) 
                {
                    epoll_ctl(epfds, EPOLL_CTL_DEL, fifofd, NULL);
                    close(fifofd);
                    printf("fifo写端已关闭,停止监听\n");
                } 
                else if (n > 0) 
                {
                    printf("fifo = %s\n", buff);
                }
            }
        }
        
    }

    close(fifofd);
    return 0;
}

(3)实现TCP的并发

#include "head.h"

#define SIN_PORT 50000
#define SIN_ADDR "192.168.0.176"
#define MAX_SIZE 1024  

int init_tcp_ser()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        return -1;
    }

    //允许绑定处于TIME_WAIT状态的地址,避免端口占用问题:
    int optval = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SIN_PORT);
    seraddr.sin_addr.s_addr = inet_addr(SIN_ADDR);

    int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if(ret < 0)
    {
        perror("bind error");
        return -1;
    }

    ret = listen(sockfd, 100);
    if(ret < 0)
    {
        perror("listen error");
        return -1;
    }

    return sockfd;
}

int epoll_fd_addr(int epfds, int fd, uint32_t events)
{
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = events;

    int ret = epoll_ctl(epfds, EPOLL_CTL_ADD, fd, &ev);
    if(ret < 0)
    {
        perror("epoll ctl error");
        return -1;
    }
    return 0;
}

int main(int argc, char const *argv[])
{
    struct sockaddr_in cliaddrs[MAX_SIZE];
    memset(cliaddrs, 0, sizeof(cliaddrs));
    socklen_t clilen = sizeof(struct sockaddr_in);

    int sockfd = init_tcp_ser();
    if(sockfd < 0)
    {
        return -1;
    }

    int epfds = epoll_create(MAX_SIZE);
    if(epfds < 0)
    {
        perror("epoll create error");
        return -1;
    }

    epoll_fd_addr(epfds, sockfd, EPOLLIN);

    struct epoll_event evs[MAX_SIZE];

    while (1)
    {
        int cnt = epoll_wait(epfds, evs, MAX_SIZE, -1);
        if(cnt < 0)
        {
            perror("epoll wait error");
            return -1;
        }

        for (int i = 0; i < cnt; i++)
        {
            int fd = evs[i].data.fd;
            
            if(sockfd == fd)  
            {
                struct sockaddr_in temp_cliaddr;
                socklen_t temp_clilen = sizeof(temp_cliaddr);
                int connfd = accept(sockfd, (struct sockaddr *)&temp_cliaddr, &temp_clilen);
                if(connfd < 0)
                {
                    perror("accept error");
                    continue;  
                }
                
                epoll_fd_addr(epfds, connfd, EPOLLIN);

                // 保存客户端地址信息
                cliaddrs[connfd] = temp_cliaddr;

                printf("[%s:%d] online\n", inet_ntoa(cliaddrs[connfd].sin_addr), ntohs(cliaddrs[connfd].sin_port));
            }
            else  
            {
                char buff[MAX_SIZE] = {0};
                ssize_t recv_cnt = recv(fd, buff, sizeof(buff), 0);
                if(recv_cnt < 0)
                {
                    perror("recv error");
                    continue; 
                }
                else if(0 == recv_cnt)
                {
                    printf("[%s:%d] offline\n", inet_ntoa(cliaddrs[fd].sin_addr), ntohs(cliaddrs[fd].sin_port));
                    epoll_ctl(epfds, EPOLL_CTL_DEL, fd, NULL);
                    close(fd);
                }
                else
                {
                    printf("[%s:%d] : %s\n", inet_ntoa(cliaddrs[fd].sin_addr), ntohs(cliaddrs[fd].sin_port), buff);
                    
                    strcat(buff, "---->ok");
                    send(fd, buff, strlen(buff), 0);
                }
            }
        }
    }

    close(sockfd);
    return 0;
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐