• 【Linux学习】IO复用技术 select、poll、epoll函数使用 服务器/客户端举例


    目录

    前言

    一、IO复用基本概念

    💻阻塞I/O

    💻非阻塞I/O

    💻I/O复用

    📌select  函数 

    📌poll  函数

    📌epoll  函数

    二、利用I/O复用搭建服务器/客户端

    💻服务器完整代码

    💻客户端完整代码

    💻测试效果 


    前言

    本文主要学习Linux内核编程,结合Visual Studio 2019进行跨平台编程,内容包括IO复用技术相关知识介绍以及用服务器客户端实例说明(详细举例epoll的使用 )

    一、IO复用基本概念

    💻阻塞I/O

    最流行的I/O模型是阻塞I/O模型,缺省时,所有的套接口都是阻塞的

    💻非阻塞I/O

    当我们把一个套接口设置为非阻塞方式时,即通知内核,当请求的I/O操作非得让进程睡眠不能完成时,不要让进程睡眠,而应返回一个错误

    应用程序连续不断地查询内核,看看某操作是否准备好,这对CPU时间是极大的浪费,一般只在专门提供某种功能的系统中才会用到

    💻I/O复用

    有了I/O复用,我们就可以调用select或poll,在这两个系统调用的某一个上阻塞,而不是真正阻塞于真正的I/O系统调用

    如果一个或多个I/O条件满足(例如:输入已准备好被读,或者描述字可以承接更多输出的时候)

    我们就能够被通知到,这样的能力被称为I/O复用,是由函数select和poll支持的

    I/O复用网络应用场合:

    • 当客户处理多个描述字
    • 一个客户同时处理多个套接口
    • 如果一个TCP服务器既要处理监听套接口,又要处理连接套接口
    • 如果一个服务器既要处理TCP,又要处理UDP 

    📌select  函数 

    select函数允许进程指示内核等待多个事件中的任一个发生,并仅在一个或多个事件发生或经过某指定的时间后才唤醒进程

    📍头文件:<sys/select.h>  <sys/socket.h>

    📍作用:  提供了即时响应多个套接的读写事件

    📍原型:

    int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *except,

    const struct timeval *timeout);

    📍参数:

    maxfdp1: 等待最大套接字值加1(等待套接字的数量)

    readset:要检查读事件的容器

    writeset:要检查写事件的容器

    timeout:  超时时间

    📍返回值: 返回触发套件接字的个数

    📌poll  函数

    poll函数和select类似,但它是用文件描述符而不是条件的类型来组织信息

    也就是说,一个文件描述符的可能事件都存储在struct pollfd中

    与之相反,select用事件的类型来组织信息,而且读,写和错误情况都有独立的描述符掩码

    poll函数是POSIX:XSI扩展的一部分,它起源于UNIX System V 

    📍头文件:  <poll.h>

    📍作用:  提供了即时响应多个套接的读写事件

    📍原型:

    int poll(struct pollfd *fdarray,unsigned long nfds,int timeout);

    📍参数:

    fdarray:是一个pollfd的机构体数组用来表示表示文件描述符的监视信息

    nfds:给出了要监视的描述符数目

    timeout:是一个用毫秒表示的时间,是poll在返回前没有接收事件是应等待的时间,如果timeout的值为-1,poll就永远不会超时,如果整数值为32个比特,最大超时周期约为30分钟

    📍返回值:准备好描述字的个数,0-超时,1-出错

    📌epoll  函数

    epoll是基于事件驱动的I/O方式,相对于select来说 ,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符, 将用户关心的文件描述符的事件存放到内核的一个事件表中, 这样在用户空间和内核空间的copy只需一次

    Linux中提供的epoll相关函数如下:

    🎮epoll_create ()

    📍作用:创建一个epoll句柄

    📍原型:

    int epoll_create(int size);

    📍参数:

    size:表明内核要监听的描述符数量

    📍返回值:成功时返回一个epoll句柄描述,失败时返回-1

    🎮epoll_ctl ()

    📍作用:注册要监听的事件类型

    📍原型:

    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    📍参数:

    epfd:表示epoll句柄

    op:表示fd操作类型,有如下3种

            EPOLL_CTL_ADD 注册新的fd到epfd中

            EPOLL_CTL_MOD 修改已注册的fd的监听事件

            EPOLL_CTL_DEL 从epfd中删除一个fd

    fd:是要监听的描述符

    event:表示要监听的事件

    📍返回值:成功时返回 0,失败时返回-1

    🎮epoll_wait()

    📍作用:等待事件的就绪,成功时返回就绪的事件数目

    📍原型:

    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

    📍参数:

    epfd:是epoll句柄

    events:表示从内核得到的就绪事件集合

    maxevents:告诉内核events的大小

    timeout:表示等待的超时事件

    📍返回值:调用失败时返回 -1,等待超时返回 0

    二、利用I/O复用搭建服务器/客户端

    代码参考自博主:似末 

    链接:【Linux】一文看懂多路复用IO模型(select、poll、epoll)_似末的博客-CSDN博客


    💻服务器完整代码

    1. #include <iostream>
    2. #include <sys/types.h>
    3. #include <sys/socket.h>
    4. #include <netinet/in.h>
    5. #include <stdio.h>
    6. #include <unistd.h>
    7. #include <string.h>
    8. #include <pthread.h>
    9. #include <sys/epoll.h>
    10. #include <arpa/inet.h>
    11. using namespace std;
    12. int main()
    13. {
    14. int socketfd = 0;
    15. int acceptfd = 0;
    16. struct sockaddr_in s_addr;
    17. int len = 0;
    18. char buf[255] = { 0 };//存放客户端发过来的信息
    19. int opt_val = 1;
    20. //初始化网络 参数一:使用ipv4 参数二:流式传输
    21. socketfd = socket(AF_INET, SOCK_STREAM, 0);
    22. if (socketfd == -1)
    23. {
    24. perror("socket error");
    25. }
    26. else
    27. {
    28. //原本使用struct sockaddr,通常使用sockaddr_in更为方便,两个数据类型是等效的,可以相互转换
    29. struct sockaddr_in s_addr;
    30. //确定使用哪个协议族 ipv4
    31. s_addr.sin_family = AF_INET;
    32. //系统自动获取本机ip地址 也可以是本地回环地址:127.0.0.1
    33. s_addr.sin_addr.s_addr = inet_addr("192.168.48.128");
    34. //端口一个计算机有65535个 10000以下是操作系统自己使用的, 自己定义的端口号为10000以后
    35. s_addr.sin_port = htons(10086); //自定义端口号为10086
    36. len = sizeof(s_addr);
    37. //端口复用 解决 address already user 问题
    38. setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, (const void*)opt_val, sizeof(opt_val));
    39. //绑定ip地址和端口号
    40. int res = bind(socketfd, (struct sockaddr*)&s_addr, len);
    41. if (res == -1)
    42. {
    43. perror("bind error");
    44. }
    45. else
    46. {
    47. //监听这个地址和端口有没有客户端来连接 第二个参数现在没有用 只要大于0就行
    48. if (listen(socketfd, 2) == -1)
    49. {
    50. perror("listen error");
    51. }
    52. }
    53. int epollfd = 0;
    54. int epollwaitefd = 0;
    55. struct epoll_event epollEvent;
    56. struct epoll_event epollEventArray[5];
    57. //事件结构体初始化
    58. bzero(&epollEvent, sizeof(epollEvent));
    59. //绑定当前准备好的sockedfd(可用网络对象)
    60. epollEvent.data.fd = socketfd;
    61. //绑定事件为客户端接入事件
    62. epollEvent.events = EPOLLIN;
    63. //创建epoll
    64. epollfd = epoll_create(5);
    65. //将已经准备好的网络描述符添加到epoll事件队列中
    66. epoll_ctl(epollfd, EPOLL_CTL_ADD, socketfd, &epollEvent);
    67. while (1)
    68. {
    69. cout << "等待客户端epoll wait client......" << endl;
    70. epollwaitefd = epoll_wait(epollfd, epollEventArray, 5, -1);
    71. if (epollwaitefd < 0)
    72. {
    73. perror("epoll wait error");
    74. }
    75. for (int i = 0; i < epollwaitefd; i++)
    76. {
    77. if (epollEventArray[i].data.fd == socketfd)
    78. {
    79. cout << "网络开始工作........等待客户端上线" << endl;
    80. acceptfd = accept(socketfd, NULL, NULL);
    81. cout << "acceptfd = " << acceptfd << endl;
    82. epollEvent.data.fd = acceptfd;
    83. epollEvent.events = EPOLLIN; //EPOLLIN表示对应的文件描述符可以读
    84. epoll_ctl(epollfd, EPOLL_CTL_ADD, acceptfd, &epollEvent);
    85. }
    86. else if (epollEventArray[i].events & EPOLLIN)
    87. {
    88. bzero(buf, sizeof(buf));
    89. int res = read(epollEventArray[i].data.fd, buf, sizeof(buf));
    90. if (res > 0)
    91. {
    92. cout << "服务器收到 fd = " << epollEventArray[i].data.fd << " 发来的数据:buf = " << buf << endl;
    93. }
    94. else if (res <= 0)
    95. {
    96. cout << "........客户端掉线........" << endl;
    97. close(epollEventArray[i].data.fd);
    98. //从epoll中删除客户端描述符
    99. epollEvent.data.fd = epollEvent.data.fd;
    100. epollEvent.events = EPOLLIN;
    101. epoll_ctl(epollfd, EPOLL_CTL_DEL, epollEventArray[i].data.fd, &epollEvent);
    102. }
    103. }
    104. }
    105. }
    106. }
    107. }

    💻客户端完整代码

    1. #include <iostream>
    2. #include <sys/types.h>
    3. #include <sys/socket.h>
    4. #include <netinet/in.h>
    5. #include <stdio.h>
    6. #include <unistd.h>
    7. #include <string.h>
    8. #include <pthread.h>
    9. #include <sys/epoll.h>
    10. #include <arpa/inet.h>
    11. using namespace std;
    12. int main()
    13. {
    14. int socketfd = 0;
    15. int acceptfd = 0;
    16. struct sockaddr_in s_addr;
    17. int len = 0;
    18. char buf[255] = { 0 };//存放客户端发过来的信息
    19. int opt_val = 1;
    20. //初始化网络 参数一:使用ipv4 参数二:流式传输
    21. socketfd = socket(AF_INET, SOCK_STREAM, 0);
    22. if (socketfd == -1)
    23. {
    24. perror("socket error");
    25. }
    26. else
    27. {
    28. //原本使用struct sockaddr,通常使用sockaddr_in更为方便,两个数据类型是等效的,可以相互转换
    29. struct sockaddr_in s_addr;
    30. //确定使用哪个协议族 ipv4
    31. s_addr.sin_family = AF_INET;
    32. //系统自动获取本机ip地址 也可以是本地回环地址:127.0.0.1
    33. s_addr.sin_addr.s_addr = inet_addr("192.168.48.128");
    34. //端口一个计算机有65535个 10000以下是操作系统自己使用的, 自己定义的端口号为10000以后
    35. s_addr.sin_port = htons(10086); //自定义端口号为10086
    36. len = sizeof(s_addr);
    37. //端口复用 解决 address already user 问题
    38. setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, (const void*)opt_val, sizeof(opt_val));
    39. //绑定ip地址和端口号
    40. int res = bind(socketfd, (struct sockaddr*)&s_addr, len);
    41. if (res == -1)
    42. {
    43. perror("bind error");
    44. }
    45. else
    46. {
    47. //监听这个地址和端口有没有客户端来连接 第二个参数现在没有用 只要大于0就行
    48. if (listen(socketfd, 2) == -1)
    49. {
    50. perror("listen error");
    51. }
    52. }
    53. int epollfd = 0;
    54. int epollwaitefd = 0;
    55. struct epoll_event epollEvent;
    56. struct epoll_event epollEventArray[5];
    57. //事件结构体初始化
    58. bzero(&epollEvent, sizeof(epollEvent));
    59. //绑定当前准备好的sockedfd(可用网络对象)
    60. epollEvent.data.fd = socketfd;
    61. //绑定事件为客户端接入事件
    62. epollEvent.events = EPOLLIN;
    63. //创建epoll
    64. epollfd = epoll_create(5);
    65. //将已经准备好的网络描述符添加到epoll事件队列中
    66. epoll_ctl(epollfd, EPOLL_CTL_ADD, socketfd, &epollEvent);
    67. while (1)
    68. {
    69. cout << "等待客户端epoll wait client......" << endl;
    70. epollwaitefd = epoll_wait(epollfd, epollEventArray, 5, -1);
    71. if (epollwaitefd < 0)
    72. {
    73. perror("epoll wait error");
    74. }
    75. for (int i = 0; i < epollwaitefd; i++)
    76. {
    77. if (epollEventArray[i].data.fd == socketfd)
    78. {
    79. cout << "网络开始工作........等待客户端上线" << endl;
    80. acceptfd = accept(socketfd, NULL, NULL);
    81. cout << "acceptfd = " << acceptfd << endl;
    82. epollEvent.data.fd = acceptfd;
    83. epollEvent.events = EPOLLIN; //EPOLLIN表示对应的文件描述符可以读
    84. epoll_ctl(epollfd, EPOLL_CTL_ADD, acceptfd, &epollEvent);
    85. }
    86. else if (epollEventArray[i].events & EPOLLIN)
    87. {
    88. bzero(buf, sizeof(buf));
    89. int res = read(epollEventArray[i].data.fd, buf, sizeof(buf));
    90. if (res > 0)
    91. {
    92. cout << "服务器收到 fd = " << epollEventArray[i].data.fd << " 发来的数据:buf = " << buf << endl;
    93. }
    94. else if (res <= 0)
    95. {
    96. cout << "........客户端掉线........" << endl;
    97. close(epollEventArray[i].data.fd);
    98. //从epoll中删除客户端描述符
    99. epollEvent.data.fd = epollEvent.data.fd;
    100. epollEvent.events = EPOLLIN;
    101. epoll_ctl(epollfd, EPOLL_CTL_DEL, epollEventArray[i].data.fd, &epollEvent);
    102. }
    103. }
    104. }
    105. }
    106. }
    107. }

    💻测试效果  

    通过跨平台连接Linux进行测试,先打开服务器再打开两个客户端

    PS:上面的为服务器,下面两个为客户端1和客户端2 ,如下图所示:

    参考:

    【Linux】一文看懂多路复用IO模型(select、poll、epoll)_似末的博客-CSDN博客

    以上就是本文的全部内容啦!如果对您有帮助,麻烦点赞啦!收藏啦!欢迎各位评论区留言!!!

     

  • 相关阅读:
    基于java校园疫情防控小程序设计与实现
    PyTorch实战:卷积神经网络详解+Python实现卷积神经网络Cifar10彩色图片分类
    java 基础语法
    狮群算法优化长短期神经网络LSTM的煤炭销量预测资源,LSTM详细原理,狮群算法原理
    虚拟机安装-ubuntu
    看门狗芯片工作原理
    Java网络编程
    WebSocket实现聊天功能
    Springboot毕设项目公司资产23sh6(java+VUE+Mybatis+Maven+Mysql)
    pt25django教程
  • 原文地址:https://blog.csdn.net/m0_61745661/article/details/125415324