• I/O多路转接之select


    前言

            今天我们要介绍的是实现高效IO采用的方案IO多路转接中的select模型,关于select模型是如何实现高效IO,下面我们就一起来具体看看吧。

    1.初始select

    系统提供select函数来实现多路复用输入/输出模型.
            select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
            程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变;

    2.select函数原型

    1. #include
    2. int select(int nfds, fd_set *readfds, fd_set *writefds,
    3. fd_set *exceptfds, struct timeval *timeout);

    参数介绍:

    参数nfds是需要监视的最大的文件描述符值+1;
    参数timeout为结构timeval,用来设置select()的等待时间

    NULL:则表示select ()没有timeout, select将一 直被阻塞, 直到某个文件描述符上发生了事件;

    timeout包含字段:

    struct timeval timeout = {0, 0} ->表示非阻塞
    struct timeval timeout = {5, 0} >5秒以内阻塞式,超过5秒,非阻塞返回一次 

    rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合;
    fd_ set:位图结构,表示文件描述符的集合
    rdset,wrset,exset都是输入输出型参数:
    输入:表示用户告诉内核,哪些fd上的事件需要内核关心
    比特位的位置,表示fd的数值,比特位的内容,表示是否需要关系
    输出:内核告诉用户,关心的多个fd中,有哪些E经就绪了
    比特位的位置,表示fd的数值,比特位的内容,表示哪些fd上面对应的事件已经就绪了

    让用户和内核进行沟通
    提供了一组操作fd_ set的接口,较方便的操作位图:

    1. void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
    2. int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
    3. void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
    4. void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位

    函数返回值:
    执行成功则返回文件描述词状态已改变的个数
    如果返回0代表在描述词状态改变前已超过timeout时间,没有返回
    当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout的
    值变成不可预测。

    3.select服务器编写

    说明:测试使用select,只监听读事件是否就绪,不关心写事件就绪和异常事件就绪,对收到的数据不进行判段是否读取完整,默认一次读取一个完整的报文。

    逻辑流程图:

    代码实现:

    selectSever.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include"sock.hpp"
    5. namespace ns
    6. {
    7. class selectServer
    8. {
    9. static const int defaultport = 8080;
    10. static const int fdnum = sizeof(fd_set)*8;
    11. static const int defaultfd = -1;
    12. using func_t = std::functionstring(const std::string&)>;
    13. public:
    14. selectServer(func_t f,int port = defaultport)
    15. :fun(f),_port(port),_listensock(-1),fdarray(nullptr)
    16. {}
    17. void initServer() {
    18. _listensock = Sock::Socket();
    19. Sock::Bind(_listensock,_port);
    20. Sock::Listen(_listensock);
    21. fdarray = new int[fdnum];
    22. for(int i = 0; i < fdnum; ++i) fdarray[i] = defaultfd;
    23. fdarray[0] = _listensock;
    24. }
    25. void Accepter(int listensock) {
    26. //走到这里,说明已经准备就绪,accept不会阻塞
    27. std::string clientIp;
    28. uint16_t clientport;
    29. int sock = Sock::Accept(_listensock,&clientIp,&clientport);
    30. if (sock < 0)
    31. return;
    32. logMessage(NORMAL, "accept success [%s:%d]", clientIp.c_str(), clientport);
    33. // sock我们能直接recv/read 吗?不能,整个代码,只有select有资格检测事件是否就绪
    34. // 将新的sock 托管给select!
    35. // 将新的sock托管给select的本质,其实就是将sock,添加到fdarray数组中即可!
    36. int i = 0;
    37. for(;i < fdnum; ++i) {
    38. if(fdarray[i] != defaultfd) continue;
    39. else break;
    40. }
    41. if(i == fdnum) {
    42. logMessage(WARNING, "server if full, please wait");
    43. close(sock);
    44. }
    45. else fdarray[i] = sock;
    46. logMessage(DEBUG, "Accepter out");
    47. }
    48. void Recver(int sock,int pos) {
    49. logMessage(DEBUG, "in Recver");
    50. //1.读取request
    51. char buffer[1024];
    52. ssize_t s = recv(sock,buffer,sizeof(buffer)-1,0);
    53. if(s > 0) {
    54. buffer[s] = 0;
    55. logMessage(NORMAL,"client say# %s",buffer);
    56. } else if (s == 0) {
    57. close(sock);
    58. fdarray[pos] = defaultfd;
    59. logMessage(NORMAL,"client quit");
    60. } else {
    61. close(sock);
    62. fdarray[pos] = defaultfd;
    63. logMessage(ERROR,"client quit: %s",strerror(errno));
    64. }
    65. //处理request:
    66. std::string response = fun(buffer);
    67. write(sock,response.c_str(),response.size());
    68. }
    69. void HandlerEvent(fd_set& rfds) {
    70. //HandlerEvent 不仅仅是只有一个fd是就绪的,可能有多个
    71. for(int i = 0; i < fdnum; ++i) {
    72. if(fdarray[i] == defaultfd) continue;
    73. //正常的fd,但是不一定就绪了
    74. if(FD_ISSET(fdarray[i],&rfds) && fdarray[i] == _listensock) Accepter(_listensock);
    75. else if(FD_ISSET(fdarray[i],&rfds)) Recver(fdarray[i],i);
    76. else {} //没有就绪
    77. }
    78. }
    79. void start() {
    80. for(;;) {
    81. fd_set rfds;
    82. FD_ZERO(&rfds);
    83. int maxfd = fdarray[0];
    84. for(int i = 0; i < fdnum; ++i) {
    85. if(fdarray[i] == defaultfd) continue;
    86. //合法fd全部添加到读文件描述符集中:
    87. FD_SET(fdarray[i],&rfds);
    88. if(maxfd < fdarray[i]) maxfd = fdarray[i];
    89. }
    90. // struct timeval timeout = {0,0};
    91. // 一般而言,要是用select,需要程序员自己维护一个保存所有合法fd的数组!
    92. int n = select(maxfd+1,&rfds,nullptr,nullptr,nullptr);
    93. switch (n)
    94. {
    95. case 0:
    96. logMessage(NORMAL, "timeout...");
    97. break;
    98. case -1:
    99. logMessage(WARNING, "select error, code: %d, err string: %s", errno, strerror(errno));
    100. break;
    101. default:
    102. // 说明有事件就绪了,目前只有一个监听事件就绪了
    103. logMessage(NORMAL, "get a new link...");
    104. HandlerEvent(rfds);
    105. break;
    106. }
    107. }
    108. }
    109. ~selectServer() {}
    110. private:
    111. int _port;
    112. int _listensock;
    113. int* fdarray;
    114. func_t fun;
    115. };
    116. }

    注:

    a.不知道一次有多少文件描述符就绪,所以需要借助一个数组管理文件描述符,每次遍历寻找有哪些文件描述符处于就绪状态

    b.因为输入输出型参数,第一次添加到读文件描述符集合中的事件没有就绪,内核就会清空对该文件描述符的关心,所以每次都要重新设置文件描述符

    c.监控文件描述符大小受限fd_set,该数据类型大小为128byte,即1024个比特位,所以select最多可以监控1024文件描述符

    4.select的缺点

    1.select能同时等待的文件fd是有上限的
    2.必须借助第三方数组维护fd
    3.select的大部分参数是输入输出型的,调用select前,要重新设置所有的fd,调用之后,还要检查更新所有的fd,带来了遍历的成本
    4.select为什么第一个参数是 最大fd+ 1呢?确定遍历范围
    5.select采用位图,用户->内核 内核- >用户 进行数据拷贝,拷贝成本问题

    总结

            select可以一次对多个文件描述符设置关心状态,当有事件就绪之后,通知上层处理事件,可以减少等的时间,进而提高IO效率,但是select同时存在许多的缺点,关于等待文件描述符有上限的问题和每次调用都要重新设置关心的fd的问题,会在下一篇poll模型的介绍中进行解决,欢迎大家继续来阅读,今天的内容就介绍这么多了,感谢大家的阅读,我们下次再见!

  • 相关阅读:
    hyperf 十六 session
    molecular-graph-bert(三)
    VRRP协议以及关联Track详解
    Mysql 的高可用详解
    90后汕头返种水稻 国稻种芯·中国水稻会:广东新农人田保姆
    南大通用GBase8s 常用SQL语句(289)
    Integer和int 的区别
    私域流量的变现方式,你知道多少?
    git命令行分支(增删改查)
    Druid未授权访问--->ip:端口号/druid/index.html
  • 原文地址:https://blog.csdn.net/qq_65307907/article/details/132324814