• 【Linux】——select详解


    • 💂 个人主页:努力学习的少年
    • 🤟 版权: 本文由【努力学习的少年】原创、在CSDN首发、需要转载请联系博主
    • 💬 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦

    目录

    1. select函数介绍

    2. select函数参数的介绍

    3.select的工作流程

    4.Select服务器

    5.Select的缺陷


    1. select函数介绍

    select函数是IO多路复用的函数,它主要的功能是用来等文件描述符中的事件是否就绪,select可以使我们在同时等待多个文件缓冲区 ,减少IO等待的时间,能够提高进程的IO效率。

    select()函数允许程序监视多个文件描述符,等待所监视的一个或者多个文件描述符变为“准备好”的状态。所谓的”准备好“状态是指:文件描述符不再是阻塞状态,可以用于某类IO操作了,包括可读,可写,发生异常三种

    2. select函数参数的介绍

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

    ndfs

    等待的文件描述符的最大值+1,例如:应用进程想要去等待文件描述符3,5,8的事件,则

    nfds=max(3,5,8)+1;

    fd_set类型

    readfds和writefds,exceptfds的类型都是fd_set,那么fd_set类型是什么呢?

    • fd_set类型本质是一个位图位图的位置 表示 相对应的文件描述符,内容表示该文件描述符是否有效,1代表该位置的文件描述符有效,0则表示该位置的文件描述符无效。
    • 如果将文件描述符2,3设置位图当中,则位图表示的是为1100。
    • fd_set的上限是1024个文件描述符。

    readfds

    • readfds是 等待读事件的文件描述符集合,.如果不关心读事件(缓冲区有数据),则可以传NULL值。
    • 应用进程和内核都可以设置readfds,应用进程设置readfds是为了通知内核去等待readfds中的文件描述符的读事件.而 内核设置readfds是为了告诉应用进程哪些读事件生效

    writefds

    与readfds类似,writefds是等待写事件(缓冲区中是否有空间)的集合,如果不关心写事件,则可以传值NULL。

    exceptfds

    如果内核等待相应的文件描述符发生异常,则将失败的文件描述符设置进exceptfds中,如果不关心错误事件,可以传值NULL。

    timeout

    设置select在内核中阻塞的时间,如果想要设置为非阻塞,则设置为NULL。如果想让select阻塞5秒,则将创建一个struct timeval time={5,0};

    其中struct timeval的结构体类型是:

    1. struct timeval {
    2. long tv_sec; /* seconds */
    3. long tv_usec; /* microseconds */
    4. };

    返回值

    • 如果没有文件描述符就绪就返回0;
    • 如果调用失败返回-1;
    • 如果timeout中中readfds中有事件发生,则返回timeout剩下的时间。

    3.select的工作流程

    应用进程内核都需要从readfds和writefds获取信息,其中,内核需要从readfds和writefds知道哪些文件描述符需要等待,应用进程需要从readfds和writefds中知道哪些文件描述符的事件就绪.

    如果我们要不断轮询等待文件描述符,则应用进程需要不断的重新设置readfds和writefds,因为每一次调用select,内核会修改readfds和writefds,所以我们需要在 应用程序设置一个数组 来保存程序需要等待的文件描述符,保证调用 select 的时候readfds 和 writefds中的将如下:

    4.Select服务器

     如果是一个select服务器进程,则服务器进程会不断的接收有新链接每个链接对应一个文件描述符,如果想要我们的服务器能够同时等待多个链接的数据的到来,我们监听套接字listen_sock读取新链接的时候,我们需要将新链接的文件描述符保存到read_arrys数组中,下次轮询检测的就会将新链接的文件描述符设置进readfds中,如果有链接关闭,则将相对应的文件描述符从read_arrys数组中拿走

    一张图看懂select服务器:

    简易版的select服务器:

    server.hpp文件:

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. using std::cout;
    8. using std::endl;
    9. #define BACKLOG 5
    10. namespace sjp{
    11. class server{
    12. public:
    13. static int Socket(){
    14. int sock=socket(AF_INET,SOCK_STREAM,0);
    15. if(sock>0)
    16. return sock;
    17. if(sock<0)
    18. exit(-1);
    19. W> }
    20. static bool Bind(int sockfd,short int port){
    21. struct sockaddr_in lock;
    22. memset(&lock,'\0',sizeof(lock));
    23. lock.sin_family=AF_INET;
    24. lock.sin_port=htons(port);
    25. lock.sin_addr.s_addr=INADDR_ANY;
    26. if(bind(sockfd,(struct sockaddr*)&lock,(socklen_t)sizeof(lock))<0){
    27. exit(-2);
    28. }
    29. return true;
    30. }
    31. static bool Listen(int sockfd){
    32. if(listen(sockfd,BACKLOG)<0){
    33. exit(-3);
    34. }
    35. return true;
    36. }
    37. };
    38. }

     select_server.hpp文件

    1. #pragma once
    2. #include
    3. #include"server.hpp"
    4. #include
    5. #include
    6. namespace Select{
    7. class select_server{
    8. private:
    9. int listen_sock;//监听套接字
    10. int port;
    11. public:
    12. select_server(int _port):port(_port){}
    13. //初始化select_server服务器
    14. void InitServer(){
    15. listen_sock=sjp::server::Socket();
    16. sjp::server::Bind(listen_sock,port);
    17. sjp::server::Listen(listen_sock);
    18. }
    19. void Run(){
    20. std::vector<int> readfds_arry(1024,-1);//readfds_arry保存读事件的文件描述符
    21. readfds_arry[0]=listen_sock;//将监听套接字保存进readfds_arry数组中
    22. fd_set readfds;
    23. while(1){
    24. FD_ZERO(&readfds);
    25. int nfds=0;
    26. //将read_arry数组中的文件描述符设置进程readfds_arry位图中
    27. for(int i=0;i<1024;i++)
    28. {
    29. if(readfds_arry[i]!=-1){
    30. FD_SET(readfds_arry[i],&readfds);
    31. if(nfds
    32. nfds=readfds_arry[i];
    33. }
    34. }
    35. }
    36. //调用select对readfds中的文件描述符进行等待数据
    37. switch(select(nfds+1,&readfds,NULL,NULL,NULL)){
    38. case 0:
    39. //没有一个文件描述符的读事件就绪
    40. cout<<"select timeout"<
    41. break;
    42. case -1:
    43. //select失败
    44. cout<<"select error"<
    45. default:
    46. {
    47. //有读事件发生
    48. Soluation(readfds_arry,readfds);
    49. break;
    50. }
    51. }
    52. }
    53. }
    54. void Soluation(std::vector<int>& readfds_arry,fd_set readfds){
    55. W> for(int i=0;isize();i++){
    56. if(FD_ISSET(readfds_arry[i],&readfds))
    57. {
    58. if(readfds_arry[i]==listen_sock){
    59. //有新链接到来
    60. struct sockaddr peer;
    61. socklen_t len;
    62. int newfd=accept(listen_sock,&peer,&len);
    63. cout<
    64. //将新链接设置进readfds_arry数组中
    65. AddfdsArry(readfds_arry,newfd);
    66. }
    67. else{
    68. //其他事件就绪
    69. char str[1024];
    70. int sz=recv(readfds_arry[i],&str,sizeof(str),MSG_DONTWAIT);
    71. switch(sz){
    72. case -1:
    73. //读取失败
    74. cout<": recv error"<
    75. break;
    76. case 0:
    77. //对端关闭
    78. readfds_arry[i]=-1;
    79. cout<<"peer close"<
    80. break;
    81. default:
    82. str[sz]='\0';
    83. cout<
    84. break;
    85. }
    86. }
    87. }
    88. }
    89. }
    90. void AddfdsArry(std::vector<int>& fds_arry,int fd){
    91. W> for(int i=0;isize();i++){
    92. if(fds_arry[i]==-1){
    93. fds_arry[i]=fd;
    94. break;
    95. }
    96. }
    97. }
    98. };
    99. }

     select_server.cc文件

    1. #include"select_server.hpp"
    2. int main(int argv,char* argc[]){
    3. if(argv!=2){
    4. cout<<"./selectserver port"<
    5. exit(-4);
    6. }
    7. int port=atoi(argc[1]);//端口号
    8. Select::select_server* sl=new Select::select_server(port);
    9. sl->InitServer();
    10. sl->Run();
    11. }

    测试:

     

    5.Select的缺陷

    • 由于fd_set的上限是1024,所以select能等待的读事件的文件描述符和写事件的文件描述是有上限的,如果作为一个大型服务器,能够同时链接的客户端是远远不够的。
    • 每次应用进程调用一次select之前,都需要重新设定writefds和readfds,如果进行轮询调用select,这对影响cpu效率。
    • 内核每一次等待文件描述符 都会重新扫描所有readfds或者writefds中的所有文件描述符,如果有较多的文件描述符,则会影响效率。

  • 相关阅读:
    C# 窗口事件
    linux服务器基本操作
    【ARM AMBA5 CHI 入门 12.1 -- CHI 链路层详细介绍 】
    LearnOpenGL 及 ShaderToy 的 CMake 构建框架
    基于LSTM+FCN处理多变量时间序列问题记录(二)
    透视投影函数的图像
    猿创征文|信息抽取(1)——pytorch实现BiLSTM-CRF模型进行实体抽取
    如何使用记事本制作一个简陋的小网页(2)——表格的建立
    供应链 | 在线平台的研究与思考(一):销售渠道与模式选择
    深入理解Windows句柄
  • 原文地址:https://blog.csdn.net/sjp11/article/details/126312199