Linux C/C++之IO多路复用(poll,epoll)

简介: 这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。

1. poll

1.1 poll与select

  • poll与select非常类似
  • poll没有最大描述符号限制
  • select在操作描述符号时使用描述符号集合fd_set, poll在操作描述符号时使用pollfd结构体链表或者数组

1.2 poll的编程模型

        //1. 创建fd结构体数组
        struct  pollfd  fds[300];
        int    fdNum = 0;   //当前描述符号数量
        //2. 把要监视的描述符号设置好
        fds[0].fd = 0;
        fds[0].events = POLLIN;
        fdNum++;
        //3. 监视
        int r = poll(fds,fdNum,0);
        if(-1 == r){
            //错误
        }else if(0 == r){
            //没有动静
            continue;
        }else{
            //有动静,检查对应事件
            if(fds[0].revents & POLLIN){

            }
        }

1.3 poll监视标准输入设备0

#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>

int main(){
    //1. 创建fd结构体数组
    struct pollfd fds[8];
    int fdNum = 0;  //当前描述符号数量
    //2. 把要监视的描述符号设置好
    fds[0].fd = 0;
    fds[0].events = POLLIN;
    fdNum++;
    //3. 监视
    int r;
    char buff[1024];

    while(1){
        r = poll(fds,fdNum,0);
        if(-1 == r){
            //错误
            printf("监视出错!\n");
        }else if(0 == r){
            //没有动静
            continue;
        }else{
            printf("有动静!\n");
            //有动静,检查对应事件
            if(fds[0].revents & POLLIN){
                memset(buff,0,1024);
                scanf("%s",buff);
                printf(">> %s\n",buff);
            }
        }
    }

    return 0;
}

1.4 poll函数原型

//需要的头文件
#include <poll.h>

struct pollfd{
    int fd;            //文件描述符号
    short events;      //请求的事件
    short revents;     //返回的事件
}

int poll(struct pollfd *fds, //链表头指针
         nfds_t nfds,        //链表节点数   
         int timeout);       //延时
//事件宏
POLLIN            //有数据可读
POLLRDNORM        //有普通数据可读
POLLRDBAND        //有优先数据可读

POLLPRI           //有紧急数据可读

POLLOUT           //写数据不会阻塞,可写数据
POLLWRNORM        //可以写普通数据
POLLWRBAND        //可以写优先数据

POLLMSGSIGPOLL    //消息可以使用
POLLER            //指定的fd出现错误
POLLHUP           //指定的fd被挂起
POLLNVAL          //指定的fd是非法的

1.5 poll实现多个(客户)client端连接(服务器)server端

服务器(server端)

//服务器端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <poll.h>

//最多允许的客户端数量
#define NUM 100

int serverSocket;

void hand(int val){
    close(serverSocket);
    printf("bye bye!\n");
    exit(0);
}
int main(int argc,char* argv[]){
    if(argc != 3) printf("请输入ip地址和端口号!\n"),exit(0);
    printf("ip: %s     port:%d\n",argv[1],atoi(argv[2]));

    signal(SIGINT,hand);

    //1. 创建socket 参数一: 协议类型(版本) 参数二: 通信媒介 参数三: 保护方式
    serverSocket = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == serverSocket) printf("创建socket失败:%m\n"),exit(-1);
    printf("创建socket成功!\n");

    //2. 创建服务器协议地址簇
    struct sockaddr_in sAddr = { 0 };
    sAddr.sin_family = AF_INET;        //协议类型 和socket函数第一个参数一致
    sAddr.sin_addr.s_addr = inet_addr(argv[1]);  //将字符串转整数
    sAddr.sin_port = htons(atoi(argv[2]));    //将字符串转整数,再将小端转换成大端

    //3. 绑定服务器协议地址簇
    int r = bind(serverSocket,(struct sockaddr*)&sAddr,sizeof sAddr);
    if(-1 == r) printf("绑定失败:%m\n"),close(serverSocket),exit(-2);
    printf("绑定成功!\n");

    //4. 监听  
    r = listen(serverSocket,10);   //数量
    if(-1 == r) printf("监听失败:%m\n"),close(serverSocket),exit(-3);
    printf("监听成功!\n");

    //监视serverSocket和ClientSocket
    struct sockaddr_in cAddr = {0};
    int len = sizeof(cAddr);
    int cfd;        //临时保存一个客户端fd
    char buff[1024];
    int fdNum = 0;

    //准备一个pollfd数组
    struct pollfd fds[NUM];
    //初始化数组
    for(int i = 0;i < NUM; i++){
        fds[i].fd = -1;
        fds[i].events = POLLIN;
    }

    //将serverSocket放进去
    fds[0].fd = serverSocket;
    fds[0].events = POLLIN;    //监视是否有数据可读
    fdNum++;

    while(1){
        r = poll(fds,fdNum,10);   //延时10ms
        if(-1 == r){
            printf("监视失败:%m\n"),close(serverSocket),exit(-1);
        }else if(0 == r){
            continue;
        }else{
            //有动静
            if(fds[0].revents & POLLIN){
                //有客户端连接服务器的动作
                cfd = accept(serverSocket,(struct sockaddr*)&cAddr,&len);
                if(-1 == cfd){
                    printf("server Error!\n");
                    continue;
                }else{
                    printf("客户端%d -- %s连接上服务器了!\n",cfd,inet_ntoa(cAddr.sin_addr));
                }
                //将客户端的描述符号添加到监视数组中
                for(int i = 1; i < NUM; i++){
                    if(-1 == fds[fdNum].fd){
                        fds[fdNum].fd = cfd;
                        fds[fdNum].events = POLLIN;
                        fdNum++;
                        break;
                    }
                }
            }
        }
        //检查客户端动静,看是否有数据发送过来
        for(int i = 1; i < NUM; i++){
            if(-1 != fds[i].fd && (fds[i].revents & POLLIN)){
                r = recv(fds[i].fd,buff,1023,0);
                if(r > 0){
                    buff[r] = 0;
                    printf("%d >> %s\n",fds[i].fd,buff);
                }else{
                    //客户端掉线了
                    fds[i].fd = -1;
                    fdNum--;
                }
            }
        }
    }

    return 0;
}

客户(client)端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>

int clientSocket;
void hand(int val){
    //5. 关闭连接
    close(clientSocket);
    printf("bye bye!\n");
    exit(0);
}
int main(int argc,char* argv[]){
    if(argc != 3) printf("请输入ip地址和端口号!\n"),exit(0);
    printf("ip: %s     port:%d\n",argv[1],atoi(argv[2]));

    signal(SIGINT,hand);

    //1. 创建socket 参数一: 协议类型(版本) 参数二: 通信媒介 参数三: 保护方式
    clientSocket = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == clientSocket) printf("创建socket失败:%m\n"),exit(-1);
    printf("创建socket成功!\n");

    //2. 创建服务器协议地址簇
    struct sockaddr_in cAddr = { 0 };
    cAddr.sin_family = AF_INET;
    cAddr.sin_addr.s_addr = inet_addr(argv[1]);  //将字符串转整数
    cAddr.sin_port = htons(atoi(argv[2]));    //将字符串转整数,再将小端转换成大端

    //3.连接服务器
    int r = connect(clientSocket,(struct sockaddr*)&cAddr,sizeof cAddr);
    if(-1 == r) printf("连接服务器失败:%m\n"),close(clientSocket),exit(-2);
    printf("连接服务器成功!\n");

    //4. 通信
    char buff[256] = {0};
    while(1){
        printf("你想要发送:");
        scanf("%s",buff);
        send(clientSocket,buff,strlen(buff),0);
    }

    return 0;
}

2. epoll

2.1 epoll相对于poll的优势

  • select与poll没有太大的区别, 均是轮循fd监视实现, 因此效率受描述符号数量影响(监视的fd越多,速率越慢) --- 就相当于一直盯着手机看是否有人打电话
  • epoll是优化后的poll,通过注册事件实现, 不需要再轮循监视,因此效率不受监视的描述符号数量影响 --- 就相当于设置了来电提示看是否有人打电话

2.2 epoll编程模型

  1. 创建epoll epoll_create

  2. 注册描述符号事件 epoll_ctl

  3. 等待,挨个处理事件 epoll_wait 检查 使用&EPOLLIN(与poll相同)

2.3 epoll函数原型

//需要的头文件
#include <sys/epoll.h>

int epoll_create(int size);    //数量

int epoll_ctl(int epfd,        //epoll_create的返回值,处理过的文件描述符
              int op,          //操作方式
              int fd,          //需要监视的文件描述符
struct epoll_event *event);    //监视的描述符号链表表头地址(或者数组首地址)

//参数二op
EPOLL_CTL_ADD                  //添加
EPOLL_CTL_MOD                  //修改
EPOLL_CTL_DEL                  //删除

int epoll_wait(int epfd,       //epoll_create的返回值,处理过的文件描述符
  struct epoll_event *events,  //监视的描述符号链表表头地址(或者数组首地址)
               int maxevents,  //事件结构体数量(第二个参数的数量)
               int timeout);   //延时
//使用的数据类型
typedef union epoll_data{
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

struct epoll_event{
    uint32_t events;
    epoll_data_t data;
};

2.4 epoll实现多个(客户)client端连接(服务器)server端

服务器(server)端

//服务器端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <sys/epoll.h>

//最多允许的客户端数量
#define NUM 100

int serverSocket;

void hand(int val){
    close(serverSocket);
    printf("bye bye!\n");
    exit(0);
}
int main(int argc,char* argv[]){
    if(argc != 3) printf("请输入ip地址和端口号!\n"),exit(0);
    printf("ip: %s     port:%d\n",argv[1],atoi(argv[2]));

    signal(SIGINT,hand);

    //1. 创建socket 参数一: 协议类型(版本) 参数二: 通信媒介 参数三: 保护方式
    serverSocket = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == serverSocket) printf("创建socket失败:%m\n"),exit(-1);
    printf("创建socket成功!\n");

    //2. 创建服务器协议地址簇
    struct sockaddr_in sAddr = { 0 };
    sAddr.sin_family = AF_INET;        //协议类型 和socket函数第一个参数一致
    sAddr.sin_addr.s_addr = inet_addr(argv[1]);  //将字符串转整数
    sAddr.sin_port = htons(atoi(argv[2]));    //将字符串转整数,再将小端转换成大端

    //3. 绑定服务器协议地址簇
    int r = bind(serverSocket,(struct sockaddr*)&sAddr,sizeof sAddr);
    if(-1 == r) printf("绑定失败:%m\n"),close(serverSocket),exit(-2);
    printf("绑定成功!\n");

    //4. 监听  
    r = listen(serverSocket,10);   //数量
    if(-1 == r) printf("监听失败:%m\n"),close(serverSocket),exit(-3);
    printf("监听成功!\n");

    //监视serverSocket和ClientSocket
    struct sockaddr_in cAddr = {0};
    int len = sizeof(cAddr);
    int cfd;        //临时保存一个客户端fd
    char buff[1024];
    int fdNum = 0;

    //创建epoll 参数可缺省(只需设置最大描述符号即可)
    int epfd = epoll_create(NUM);
    if(-1 == epfd){
        printf("创建epoll失败:%m\n");
        close(serverSocket);
        exit(-1);
    }
    printf("创建epoll成功!\n");

    //注册描述符号事件
    struct epoll_event ev;  //注册时使用
    struct epoll_event events[NUM]; //等待处理事件时使用

    ev.events = EPOLLIN;
    ev.data.fd = serverSocket;

    r = epoll_ctl(epfd,EPOLL_CTL_ADD,serverSocket,&ev);
    if(-1 == r){
        printf("注册serverSocket事件失败:%m\n");
        close(epfd);
        close(serverSocket);
        exit(-2);
    }
    printf("注册serverSocket事件成功!\n");
    //等待,挨个处理事件
    //epoll_wait 成功返回有动静的描述符号数量
    int curCfd;  //用于保存当前发数据的客户端epoll_wait的返回值
    int nfds;    //用于保存epoll_wait的返回值
    while(1){
        nfds = epoll_wait(epfd,events,NUM,1000);  //延时1000ms
        if(nfds < 0){
            printf("epoll_wait失败:%m\n");
            close(epfd);
            close(serverSocket);
            exit(-3);
        }else if(0 == nfds){
            //没有动静
            continue;
        }else{
            //有动静
            printf("有动静发生----\n");
            for(int i = 0;i < nfds; i++){
                if(serverSocket == events[i].data.fd){
                    cfd = accept(serverSocket,(struct sockaddr*)&cAddr,&len);
                    if(-1 == cfd){
                        printf("服务器崩溃:%m\n");
                        close(epfd);
                        close(serverSocket);
                        exit(-4);
                    }
                    printf("有客户端%d连接上服务器了:%s\n",cfd,
                        inet_ntoa(cAddr.sin_addr));

                    //注册客户端描述符号事件
                    ev.events = EPOLLIN;
                    ev.data.fd = cfd;

                    r = epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
                    if(-1 == r){
                        printf("注册clientSocket失败:%m\n");
                        close(epfd);
                        close(serverSocket);
                        exit(-5);
                    }
                    printf("注册clientSocket事件成功!\n");
                }else if(events[i].events && EPOLLIN){
                    //有客户端发数据过来
                    curCfd = events[i].data.fd;
                    r = recv(curCfd,buff,1023,0);
                    if(r > 0){
                        buff[r] = 0;
                        printf("%d >> %s\n",curCfd,buff);
                    }
                }
            }
        }
    }

    return 0;
}

客户(client)端 (与上方poll代码相同)

相关文章
|
4月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
154 1
Linux C/C++之IO多路复用(aio)
|
3月前
|
存储 JSON Java
细谈 Linux 中的多路复用epoll
大家好,我是 V 哥。`epoll` 是 Linux 中的一种高效多路复用机制,用于处理大量文件描述符(FD)事件。相比 `select` 和 `poll`,`epoll` 具有更高的性能和可扩展性,特别适用于高并发服务器。`epoll` 通过红黑树管理和就绪队列分离事件,实现高效的事件处理。本文介绍了 `epoll` 的核心数据结构、操作接口、触发模式以及优缺点,并通过 Java NIO 的 `Selector` 类展示了如何在高并发场景中使用多路复用。希望对大家有所帮助,欢迎关注威哥爱编程,一起学习进步。
|
4月前
|
Ubuntu Linux 编译器
Linux/Ubuntu下使用VS Code配置C/C++项目环境调用OpenCV
通过以上步骤,您已经成功在Ubuntu系统下的VS Code中配置了C/C++项目环境,并能够调用OpenCV库进行开发。请确保每一步都按照您的系统实际情况进行适当调整。
938 3
|
4月前
|
资源调度 Linux 调度
Linux C/C++之线程基础
这篇文章详细介绍了Linux下C/C++线程的基本概念、创建和管理线程的方法,以及线程同步的各种机制,并通过实例代码展示了线程同步技术的应用。
54 0
Linux C/C++之线程基础
|
24天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
64 19
|
24天前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
45 13
|
24天前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
47 5
|
24天前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
36 5
|
24天前
|
Serverless 编译器 C++
【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】
本任务要求设计一个矩形类、圆形类和图形基类,计算并输出相应图形面积。相关知识点包括纯虚函数和抽象类的使用。 **目录:** - 任务描述 - 相关知识 - 纯虚函数 - 特点 - 使用场景 - 作用 - 注意事项 - 相关概念对比 - 抽象类的使用 - 定义与概念 - 使用场景 - 编程要求 - 测试说明 - 通关代码 - 测试结果 **任务概述:** 1. **图形基类(Shape)**:包含纯虚函数 `void PrintArea()`。 2. **矩形类(Rectangle)**:继承 Shape 类,重写 `Print
44 4
|
24天前
|
设计模式 IDE 编译器
【C++面向对象——类的多态性与虚函数】编写教学游戏:认识动物(头歌实践教学平台习题)【合集】
本项目旨在通过C++编程实现一个教学游戏,帮助小朋友认识动物。程序设计了一个动物园场景,包含Dog、Bird和Frog三种动物。每个动物都有move和shout行为,用于展示其特征。游戏随机挑选10个动物,前5个供学习,后5个用于测试。使用虚函数和多态实现不同动物的行为,确保代码灵活扩展。此外,通过typeid获取对象类型,并利用strstr辅助判断类型。相关头文件如&lt;string&gt;、&lt;cstdlib&gt;等确保程序正常运行。最终,根据小朋友的回答计算得分,提供互动学习体验。 - **任务描述**:编写教学游戏,随机挑选10个动物进行展示与测试。 - **类设计**:基类
31 3