首页 > 其他分享 >C IO复用select, epoll 简单总结

C IO复用select, epoll 简单总结

时间:2024-03-22 11:55:06浏览次数:25  
标签:struct epoll int fd IO include data select

1. 文件描述符类型

REG :文件
DIR:目录
CHR :字符
BLK:块设备
UNIX:unix域套接字
FIFO :先进先出队列
IPv4:网际协议 (IP) 套接字

其中, 标准输入STDIN(0)和STDOUT输出(1), STDERR错误(2)为指定的值

2.  IO复用模型

(1). select (在指定的一段时间内,轮询监听用户需要的文件描述符(用户添加到fd_set中的),当监听到的文件描述符传来可读、可写或异常事件发生时就会返回)

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
nfds: 指定被监听文件描述符的总数,因为文件描述符通常从0开始计数,因此nfds通常为readfd,writefd,exceptfd这三个描述符集中的最大描述符编号加1;
readfd,writefd,exceptfd: 分别指向可读、可写、异常事件对应的文件描述符集合。应用程序调用select时,通过这三个参数传入需要监听的文件描述符,轮询等待有事件产生
timeout: 超时间设置; NULL表示一直阻塞直到某个文件描述符就绪; 非NULL表示超时时间后立即返回;

返回值: 大于0, 描述符有数据返回; 等于0超时; 小于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被清空。

(2). epoll
#include <sys/epoll.h>

int epoll_create(int size);
函数功能: 创建epoll专用文件描述符的缓冲区。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数功能: 添加、删除、修改 监听文件描述符。
函数参数:
int epfd epoll专用的文件描述符
int op 操作命令。EPOLL_CTL_ADD EPOLL_CTL_MOD EPOLL_CTL_DEL
int fd 要操作文件描述符
struct epoll_event *event 存放监听文件描述符信息的结构体

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
函数功能: 等待事件发生。
函数参数:
int epfd epoll专用的文件描述符
struct epoll_event *events : 存放产生事件的文件描述结构体。
int maxevents :最大监听的数量.
int timeout :等待事件ms单位. <0 >0 ==0

返回值: 产生事件的数量。

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 EPOLLIN 输入事件 */
epoll_data_t data; /* User data variable */
};


(3). select和epoll区别:
select epoll
性能: 随着连接数增加, 急剧下降。处理成千上万并发连接数时, 连接数增加时, 性能基本上没有下降, 大量并发连接时,
性能很差 性能很好

连接数: 连接数量有限制,最大连接数不超过1024 连接数无限制

内在处理机制: 线性轮询 回调callback

开发复杂性 低 中

简单理解:
住校时,你的朋友来找你:
select版宿管阿姨,带着你的朋友挨个房间找,直到找到你
epoll版阿姨,会先记下每位同学的房间号, 你的朋友来时,只需告诉你的朋友你住在哪个房间,无需亲自带着你朋友满大楼逐个房间找人
如果来了10000个人,都要找自己住这栋楼的同学时,select版和epoll版宿管大妈,谁效率高?同理,高并发服务器中,轮询I/O是最耗时操作之一,epoll性能更高也是很明显。

select的调用复杂度O(n)。如一个保姆照看一群孩子,如果把孩子是否需要尿尿比作网络I/O事件,select就像保姆挨个询问每个孩子:你要尿尿吗?若孩子回答是,保姆则把孩子拎出来放到另外一个地方。当所有孩子询问完之后,保姆领着这些要尿尿的孩子去上厕所(处理网络I/O事件)

epoll机制下,保姆无需挨个询问孩子是否要尿尿,而是每个孩子若自己需要尿尿,主动站到事先约定好的地方,而保姆职责就是查看事先约定好的地方是否有孩子。若有小孩,则领着孩子去上厕所(网络事件处理)。因此,epoll的这种机制,能够高效的处理成千上万的并发连接,而且性能不会随着连接数增加而下降。

 

Epoll 使用

EpollServer.cpp

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/epoll.h>

#include <iostream>
#include <string>
#include <list>

using namespace std;

typedef struct _ClientFd
{
    int m_fd = -1;                   // 连接句柄
    string name = string();        // 名称

} ClientFd;


// socket句柄
int sockfd;

//消息结构体, 目前协定的消息接受发送的格式
struct MSG_DATA
{
    char type; //消息类型.  0表示有聊天的消息数据  1表示好友上线  2表示好友下线
//    char name[50]; //好友名称
//    unsigned char buff[100];  //发送的聊天数据消息
    string name;   //好友名称
    string buff;  //发送的聊天数据消息
    int number;   //在线人数的数量
};

#define MAX_EPOLL_FD 100
struct epoll_event events[MAX_EPOLL_FD];
struct epoll_event event;
int epfd;
int nfd;
struct MSG_DATA msg_data;

// 存放客户端句柄列表
std::list<ClientFd*> clientFdList;

// 添加描述符
void List_addFd(int fd)
{
    ClientFd *pFd = new ClientFd();
    pFd->m_fd = fd;

    clientFdList.emplace_back(pFd);
}


// 获取成员名称
void List_getName(struct MSG_DATA *msg_data,int client_fd)
{
    auto iter = clientFdList.begin();
    while( iter != clientFdList.end() )
    {
        if ( (*iter)->m_fd == client_fd )
        {
            msg_data->name = (*iter)->name;
            break;
        }
        iter++;
    }

}


// 删除描述符
void List_DelFd(int clientFd)
{
    auto iter = clientFdList.begin();
    while ( iter != clientFdList.end() )
    {
        if ( (*iter)->m_fd == clientFd )
        {
            iter = clientFdList.erase(iter);
            break;
        }
        ++iter;
    }

}


int List_GetCnt()
{
    return clientFdList.size();
}


// 保存文件
void List_SaveName(struct MSG_DATA *msg_data, int clientFd)
{
    auto iter = clientFdList.begin();
    while ( iter != clientFdList.end() )
    {
        if ( (*iter)->m_fd == clientFd )
        {
            msg_data->name = (*iter)->name;
            break;
        }
        ++iter;
    }
}


// 服务器转发消息
void Server_SendMsgData(struct MSG_DATA *msg_data,int clientFd)
{

    printf("%d | %s\n", __LINE__, __FUNCTION__);
    auto iter = clientFdList.begin();
    while ( iter != clientFdList.end() )
    {
        if ( (*iter)->m_fd == clientFd )
        {
            printf("");
            write((*iter)->m_fd, msg_data, sizeof(struct MSG_DATA));
            break;
        }
        ++iter;
    }
}



/*信号工作函数*/
void signal_work_func(int sig)
{
    (void)sig;
    close(sockfd);
    exit(0); //结束进程
}



// 函数入口
int main(int argc,char **argv)
{

    if(argc!=2)
    {
        printf("./app <端口号>\n");
        return 0;
    }

    signal(SIGPIPE,SIG_IGN); //忽略 SIGPIPE 信号--防止服务器异常退出
    signal(SIGINT,signal_work_func);


    /*1. 创建socket套接字*/
    sockfd=socket(AF_INET,SOCK_STREAM,0);


    /*2. 绑定端口号与IP地址*/
   struct sockaddr_in addr;
   addr.sin_family=AF_INET;
   addr.sin_port=htons(atoi(argv[1])); // 端口号0~65535
   addr.sin_addr.s_addr=INADDR_ANY;    //inet_addr("0.0.0.0"); //IP地址
   if(bind(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr))!=0)
   {
       perror("服务器:端口号绑定失败.\n");
       return -1;
   }


    /*3. 设置监听的数量*/
    listen(sockfd,20);


    /*4. 等待客户端连接*/
    int client_fd;
    struct sockaddr_in client_addr;
    socklen_t addrlen;
    int i;
    int cnt;


    /*5. 创建epoll相关的接口*/
    epfd=epoll_create(MAX_EPOLL_FD);
    event.events=EPOLLIN;  //监听的事件
    event.data.fd=sockfd; //监听的套接字
    epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event);


    while(1)
    {
        //等待事件发生, 有数据
        nfd=epoll_wait(epfd,events,MAX_EPOLL_FD,-1);

        for(i=0;i<nfd;i++)
        {
           if(events[i].data.fd==sockfd)  //表示有新的客户端连接上服务器
           {
               client_fd=accept(sockfd,(struct sockaddr *)&client_addr,&addrlen);
               printf("连接的客户端IP地址:%s\n",inet_ntoa(client_addr.sin_addr));
               printf("连接的客户端端口号:%d\n",ntohs(client_addr.sin_port));
               //保存已经连接上来的客户端
//               List_AddNode(list_head,client_fd);

               // 添加新连接客户端的句柄
               List_addFd(client_fd);

               //将新连接的客户端套接字添加到epoll函数监听队列里
               event.data.fd=client_fd; //监听的套接字
               epoll_ctl(epfd,EPOLL_CTL_ADD,client_fd,&event);
           }
           else  //表示客户端给服务器发送了消息-----实现消息的转发
           {
                //读取客户端发送的消息, 保存在msg_data中
               cnt = read(events[i].data.fd,&msg_data,sizeof(struct MSG_DATA));
               if(cnt<=0)  //表示当前客户端断开了连接
               {
                   //获取名称
//                   List_GetName(list_head,&msg_data,events[i].data.fd);
                   //删除节点
//                   List_DelNode(list_head,events[i].data.fd);

                   //获取名称
                   List_getName(&msg_data, events[i].data.fd);
                   //删除节点
                   List_DelFd(events[i].data.fd);

                   msg_data.type=2;             //

                   //将断开连接的客户端套接字从epoll函数监听队列里删除调用
                   event.data.fd = events[i].data.fd; //监听的套接字
                   epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&event);
                   close(event.data.fd);
               }

               if(msg_data.type==1) //好友上线的时候保存一次名称
               {
                   //保存名称
                   List_SaveName(&msg_data,events[i].data.fd);
               }

               //转发消息给其他好友
               msg_data.number = List_GetCnt(); //当前在线好友人数
               Server_SendMsgData(&msg_data,events[i].data.fd);

           }   
        }
    }

    //退出进程
    signal_work_func(0);

    return 0;
}

 

EpollClient.cpp

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h>
#include <sys/epoll.h>

//消息结构体
struct MSG_DATA
{
    char type; //消息类型.  0表示有聊天的消息数据  1表示好友上线  2表示好友下线
    char name[50]; //好友名称
    int number;   //在线人数的数量
    unsigned char buff[100];  //发送的聊天数据消息
};
struct MSG_DATA msg_data;

#define MAX_EVENTS 2
struct epoll_event ev, events[MAX_EVENTS];
int epollfd;
int nfds;

//文件接收端
int main(int argc,char **argv)
{
    if(argc!=4)
    {
        printf("./app  <IP地址> <端口号> <名称>\n");
        return 0;
    }
    int sockfd;
    //忽略 SIGPIPE 信号--方式服务器向无效的套接字写数据导致进程退出
    signal(SIGPIPE,SIG_IGN);

    /*1. 创建socket套接字*/
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    /*2. 连接服务器*/
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(atoi(argv[2])); // 端口号0~65535
    addr.sin_addr.s_addr=inet_addr(argv[1]); //IP地址
    if(connect(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr_in))!=0)
    {
        printf("客户端:服务器连接失败.\n");
        return 0;
    }

    /*3. 发送消息表示上线*/
    msg_data.type=1;
    strcpy(msg_data.name,argv[3]);
    write(sockfd,&msg_data,sizeof(struct MSG_DATA));

    int cnt;
    int i;
    //创建专用文件描述符
    epollfd = epoll_create(10);
    //添加要监听的文件描述符
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev);

    ev.events = EPOLLIN;
    ev.data.fd = 0; //标准输入文件描述符
    epoll_ctl(epollfd, EPOLL_CTL_ADD, 0, &ev);

    while(1)
    {
        //监听事件
        nfds=epoll_wait(epollfd,events,MAX_EVENTS,-1);
        if(nfds)
        {
            for(i=0;i<nfds;i++)
            {
                if(events[i].data.fd==sockfd) //判断收到服务器的消息
                {
                    cnt=read(sockfd,&msg_data,sizeof(struct MSG_DATA));
                    if(cnt<=0) //判断服务器是否断开了连接
                    {
                        printf("服务器已经退出.\n");
                        goto SERVER_ERROR;
                    }
                    else if(cnt>0)
                    {
                        if(msg_data.type==0)
                        {
                            printf("%s:%s  在线人数:%d\n",msg_data.name,msg_data.buff,msg_data.number);
                        }
                        else if(msg_data.type==1)
                        {
                            printf("%s 好友上线. 在线人数:%d\n",msg_data.name,msg_data.number);
                        }
                        else if(msg_data.type==2)
                        {
                            printf("%s 好友下线. 在线人数:%d\n",msg_data.name,msg_data.number);
                        }
                    }
                }
                else if(events[i].data.fd==0) //表示键盘上有数据输入
                {
                    gets(msg_data.buff); //读取键盘上的消息
                    msg_data.type=0; //表示正常消息
                    strcpy(msg_data.name,argv[3]); //名称
                    write(sockfd,&msg_data,sizeof(struct MSG_DATA));
                }
            }
        }
    }
SERVER_ERROR:
    close(sockfd);
    return 0;
}

 

标签:struct,epoll,int,fd,IO,include,data,select
From: https://www.cnblogs.com/weijian168/p/18088748

相关文章

  • 语义分割(semantic-segmentation)
    一、语义分割1、什么是语义分割       语义分割将图片中的每个像素分配到对应的类别。在目标检测问题中,我们一直使用方形边界框来标注和预测图像中的目标。语义分割(semanticsegmentation)问题重点关注于如何将图像分割成属于不同语义类别的区域。与目标检测不同,语义分......
  • 论文精读系列文章——Point-LIO: Robust High-Bandwidth Light Detection and Ranging
    论文精读系列文章下面是SLAM算法与工程实践系列文章的总链接,本人发表这个系列的文章链接均收录于此论文精读系列文章链接下面是专栏地址:论文精读系列文章专栏文章目录论文精读系列文章论文精读系列文章链接论文精读系列文章专栏前言论文精读系列文章——......
  • visual studio如何测试http接口?(常用的接口测试工具)
    1.情景展示用了这么多年,一直在用notepad++来记录临时文件内容。现在改用visualstudio后,发现这个前端开发工具是可以调http接口的。为什么要在visualstudio中测试http接口?作为一个后端工程师,与接口打交道可谓是家常便饭,最开始自己使用的是:在chrome上的postman插件,后来chro......
  • Multi-View Graph Convolutional Network for Multimedia Recommendation
    目录概符号说明MGCNMotivationBehavior-GuidedPurifierMulti-ViewInformationEncoderBehavior-AwareFuserPredicitonOptimation代码YuP.,TanZ.,LuG.andBaoB.Multi-viewgraphconvolutionalnetworkformultimediarecommendation.MM,2023.概本文主要处理模......
  • AI-Station使用教程
    一、创建用户用户管理-创建用户:1. 在ai-station的web页面上登录管理员的账号:admin,密码:123456Aaa?2. 点击系统管理->用户管理->创建->再次输入密码:123456Aaa?3. 创建普通用户:账户和姓名必须一致(自定义设置)->选取iei用户组->点击确定4. 设置配额:为了后续长久使......
  • CH57x,CH58x,CH59x芯片_SPI借助GPIO中断完成中断传输
    受其他项目的启发,如果IO充裕,且在SPI主机与SPI从机均可以自行编程的情况下,可以尝试在4线SPI的基础上增加两根GPIO线,通过IO中断的形式通知对方进行收数据;非SPI中断形式,以两颗CH582通讯为例,1、主机程序:voidmain(){SetSysClock(CLK_SOURCE_PLL_60MHz);GPIOA_ModeCfg......
  • 备考ICA----Istio实验3---Istio DestinationRule 实验
    备考ICA----Istio实验3—IstioDestinationRule实验1.hello服务说明这部分服务沿用Istio实验2的deployment和svc同时在上一个实验的deployment中分别加入了2个标签:app:helloworld两个deployment共有version:v1和version:v2两个deploymen不同详见:https://b......
  • k8s系列之十四安装Istio
    Istio是一个开源的服务网格(ServiceMesh),用于连接、管理和保护微服务。它提供了一组功能强大的工具,包括流量管理、安全性、监控和跟踪等,以帮助在微服务架构中更好地管理服务之间的通信。一些主要的Istio功能包括:流量管理:Istio可以对流量进行智能路由、负载均衡和故障......
  • dremio 官方对于软件版ha 以及扩展部署的参考方案
    关于dremio实际大规模部署的记录,内容来自官方文档dremio组件架构参考图此图包含了dremio的ha以及扩展,包含了主备Coordinator(故障转移的)提高查询性能的Coordinator,以及进行实际查询的执行器此部署中依赖lb,共享存储(nfs类的),zk(协调选举的),分布式存储(当然也可以使用共享存储,但......
  • soda-data dremio 集成使用
    以前简单介绍过soda数据质量工具,以下是关于dremio集成的一个说明环境准备dremiodremio基于docker部署,具体可以参考https://github.com/rongfengliang/dremio_cluster_docker-composesodasoda包含了library以及core,我使用了core,不依赖cloud,基于venvpython......