• I/O多路复用三种实现


     

    一.select 实现

    (1)select流程

    基本流程是:

    1. 先构造一张有关文件描述符的表;                     fd_set readfds

    2. 清空表                                                              FD_ZERO()

    3. 将你关心的文件描述符加入到这个表中;           FD_SET()

    4. 调用select函数。                                              selset()

    5. 判断是哪一个或哪些文件描述符产生了事件(IO操作);   FD_ISSET()

    6. 做对应的逻辑处理;       

    (2)selset函数

    头文件: #include   #include   

                 #include   #include

    声明: int select(int nfds, fd_set *readfds, fd_set *writefds,\

                                        fd_set *exceptfds, struct timeval *timeout);

    功能:监测是哪些文件描述符产生事件,阻塞等待产生.

    参数:nfds:    监测的最大文件描述个数(文件描述符从0开始,这里是个数,记得+1)

              readfds:  读事件集合; // 键盘鼠标的输入,客户端连接都是读事件

              writefds: 写事件集合;  //NULL表示不关心

              exceptfds:异常事件集合;  //NULL 表示不关心

              timeout:   设为NULL,等待直到某个文件描述符发生变化;

                                  设为大于0的值,有描述符变化或超时时间到才返回。

            超时时间检测:如果规定时间内未完成函数功能,返回一个超时的信息,我们可以根据该信息设定相应需求;

    返回值:  <0 出错            >0 表示有事件产生;

                    如果设置了超时检测时间:&tv

                    <0 出错            >0 表示有事件产生;      ==0 表示超时时间已到;        

    结构体如下:                     

                struct timeval {

                   long    tv_sec;         以秒为单位,指定等待时间

                   long    tv_usec;        以毫秒为单位,指定等待时间

               };

    void FD_CLR(int fd, fd_set *set);  //将set集合中的fd清除掉 

    int  FD_ISSET(int fd, fd_set *set); //判断fd是否在set集合中产生了事件

    void FD_SET(int fd, fd_set *set);  //将fd加入到集合中

    void FD_ZERO(fd_set *set);          //清空集合

    (3)Select特点:

    Select特点:

    1. 一个进程最多只能监听1024个文件描述符 (32位)   [64位为 2048]

    2. select被唤醒之后要重新轮询(0-1023)一遍驱动,效率低(消耗CPU资源)

    3. select每次会清空未响应的文件描述符,每次都需要拷贝用户空间的表到内核空间,效率低,开销较大

       (0~3G是用户态,3G~4G是内核态,两个状态来回切换  拷贝是非常耗时,耗资源的)

     (4)select机制: 

    1. 头文件检测1024个文件描述符  0-1023

    2. 在select中0~2存储标准输入、标准输出、标准出错    

    3. 监测的最大文件描述个数为fd+1(如果fd = 3,则最大为 4) :  //因为从0开始的    

    4. select只对置1的文件描述符感兴趣 ,假如事件产生,select检测时 , 产生的文件描述符会保持1,未产生事件的会置0; 

    5. select每次轮询都会清空表(置零的清空)   //需要在select前备份临时表

    练习1:

    如何通过select实现 响应鼠标事件同时响应键盘事件?

    代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. int main(int argc, char const *argv[])
    16. {
    17. int fd = open("/dev/input/mouse0", O_RDONLY);
    18. if (fd < 0)
    19. {
    20. perror("open is err:");
    21. return -1;
    22. }
    23. //1.创建表
    24. fd_set readfds;
    25. //2/清空表
    26. FD_ZERO(&readfds);
    27. //3.设置表
    28. FD_SET(0, &readfds);
    29. FD_SET(fd, &readfds);
    30. fd_set readfdcp = readfds;
    31. int maxfd = fd;
    32. char buf[128] = {0};
    33. while (1)
    34. {
    35. //4.检测是否有相应
    36. select(maxfd + 1, &readfds, NULL, NULL, NULL);
    37. //5.检测哪一个文件描述符
    38. if (FD_ISSET(0, &readfds))
    39. {
    40. fgets(buf, sizeof(buf), stdin);
    41. if (buf[strlen(buf) - 1] == '\n')
    42. buf[strlen(buf) - 1] = '\0';
    43. printf("key: %s\n", buf);
    44. }
    45. if (FD_ISSET(fd, &readfds))
    46. {
    47. int ret = read(fd, buf, sizeof(buf));
    48. buf[ret] = '\0';
    49. printf("mouse: %s\n", buf);
    50. }
    51. readfds = readfdcp;
    52. }
    53. return 0;
    54. }

     练习2:

    select是文件描述符和下标一一对应,0只能对应0号文件描述符。因此只有最大的文件描述符关闭时,才--len。注意增加删除时是针对实际表,不是临时表。

    使用select实现server的全双工

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. int acceptfp;
    16. int main(int argc, char const *argv[])
    17. {
    18. char buf[128] = {0};
    19. //1.创建套接字,返回建立链接的文件描述符
    20. int sockfp = socket(AF_INET, SOCK_STREAM, 0);
    21. if (sockfp == -1)
    22. {
    23. perror("socket is err");
    24. exit(0);
    25. }
    26. printf("%d\n", sockfp);
    27. //2.绑定ip和端口号
    28. struct sockaddr_in saddr, caddr;
    29. saddr.sin_family = AF_INET;
    30. saddr.sin_port = htons(atoi(argv[1]));
    31. saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    32. socklen_t len = sizeof(struct sockaddr_in);
    33. if (bind(sockfp, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    34. {
    35. perror("bind is err");
    36. exit(0);
    37. }
    38. //3.listen监听
    39. if (listen(sockfp, 5))
    40. {
    41. perror("liste err");
    42. exit(0);
    43. }
    44. printf("listen ok\n");
    45. //1.创建表
    46. fd_set readfds;
    47. //2/清空表
    48. FD_ZERO(&readfds);
    49. //3.设置表
    50. FD_SET(0, &readfds);
    51. FD_SET(sockfp, &readfds);
    52. fd_set readfdcp = readfds;
    53. int maxfd = sockfp;
    54. struct timeval st;
    55. while (1)
    56. {
    57. readfds = readfdcp;
    58. //4.检测是否有响应
    59. st.tv_sec = 5;
    60. st.tv_usec = 0;
    61. int ret = select(maxfd + 1, &readfds, NULL, NULL, &st);
    62. if (ret < 0)
    63. {
    64. perror("select err");
    65. return -1;
    66. }
    67. else if (ret == 0)
    68. {
    69. printf("无响应\n");
    70. }
    71. //0响应,证明服务器要发送消息
    72. if (FD_ISSET(0, &readfds))
    73. {
    74. fgets(buf, sizeof(buf), stdin);
    75. if (buf[strlen(buf) - 1] == '\n')
    76. buf[strlen(buf) - 1] = '\0';
    77. for (int i = 4; i <= maxfd; ++i)
    78. {
    79. send(i, buf, sizeof(buf), 0);
    80. }
    81. }
    82. //sockfp,监听套接字响应证明,有客户端要链接
    83. if (FD_ISSET(sockfp, &readfds))
    84. {
    85. acceptfp = accept(sockfp, (struct sockaddr *)&caddr, &len);
    86. if (acceptfp < 0)
    87. {
    88. perror("acceptfp");
    89. exit(0);
    90. }
    91. printf("port:%d ip: %s\n", ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr));
    92. FD_SET(acceptfp, &readfdcp);
    93. if (acceptfp > maxfd)
    94. maxfd = acceptfp;
    95. }
    96. //检测客户端,检查是哪一个客户端发送的消息
    97. for (int i = 4; i <= maxfd; ++i)
    98. {
    99. if (FD_ISSET(i, &readfds))
    100. {
    101. int recvbyte = recv(i, buf, sizeof(buf), 0);
    102. if (recvbyte < 0)
    103. {
    104. perror("recv err");
    105. return -1;
    106. }
    107. else if (recvbyte == 0)
    108. {
    109. printf("%d client is exit\n", i);
    110. close(i);
    111. FD_CLR(i, &readfdcp);
    112. if (i == maxfd)
    113. --maxfd;
    114. }
    115. else
    116. {
    117. printf("%d : %s\n", i, buf);
    118. }
    119. }
    120. }
    121. }
    122. return 0;
    123. }

    练习3:

    使用select实现client的全双工

    1. #include
    2. #include /* See NOTES */
    3. #include
    4. #include
    5. #include /* superset of previous */
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include
    16. #include
    17. int main(int argc, const char *argv[])
    18. {
    19. int sockfd = socket(AF_INET,SOCK_STREAM,0);
    20. if(sockfd < 0)
    21. {
    22. perror("socker is err:");
    23. return -1;
    24. }
    25. struct sockaddr_in saddr;
    26. saddr.sin_family = AF_INET;
    27. saddr.sin_port = htons(atoi(argv[1]));
    28. saddr.sin_addr.s_addr = inet_addr(argv[2]);
    29. if(connect(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)) < 0)
    30. {
    31. perror("connect is err:");
    32. return -1;
    33. }
    34. //1.创建表
    35. fd_set readfds,tempfds;
    36. //2.清空表
    37. FD_ZERO(&readfds);
    38. FD_ZERO(&tempfds);
    39. //3.添加文件描述符
    40. FD_SET(0,&readfds);
    41. FD_SET(sockfd,&readfds);
    42. int maxfd = sockfd;
    43. int ret;
    44. char buf[128];
    45. while(1)
    46. {
    47. tempfds = readfds;
    48. //4.调select检测
    49. ret = select(maxfd+1,&tempfds,NULL,NULL,NULL);
    50. if(ret < 0)
    51. {
    52. perror("select is err:");
    53. return -1;
    54. }
    55. if(FD_ISSET(0,&tempfds))
    56. {
    57. fgets(buf,sizeof(buf),stdin);
    58. if(buf[strlen(buf)-1] == '\n')
    59. buf[strlen(buf)-1] = '\0';
    60. send(sockfd,buf,sizeof(buf),0);
    61. }
    62. if(FD_ISSET(sockfd,&tempfds))
    63. {
    64. int recvbyte = recv(sockfd,buf,sizeof(buf),0);
    65. if(recvbyte < 0)
    66. {
    67. perror("recv is err:");
    68. return -1;
    69. }
    70. printf("%s\n",buf);
    71. }
    72. }
    73. close(sockfd);
    74. return 0;
    75. }

    (5)select的超时时间检测:

    超时检测的必要性:

    1. 避免进程在没有数据时无限制的阻塞;

    2. 规定时间未完成语句应有的功能,则会执行相关功能;

    结构体如下:                     

                struct timeval {

                   long    tv_sec;         以秒为单位,指定等待时间

                   long    tv_usec;        以毫秒为单位,指定等待时间

               };

    二.poll实现

     (1)poll流程

    使用:  1.先创建结构体数组                                           struct pollfd fds[100];

              2.添加结构体成员的文件描述符以及触发方式   fds[0].fd = ?;fds[0].events = POLLIN 

              3.保存数组内最后一个有效元素的下标       

              4. 调用函数poll                                                  ret = poll(fds,nfds+1,-1);

              5.判断结构体内文件描述符是否触发事件          fds[i].revents == POLLIN

              6.根据不同的文件描述符触发不同事件 

    (2)poll函数

    声明:int poll(struct pollfd *fds, nfds_t nfds, int timeout);

    头文件: #include

    功能: 监视并等待多个文件描述符的属性变化

    参数:

      1.struct pollfd *fds:   关心的文件描述符数组,大小自己定义

       若想检测的文件描述符较多,则建 立结构体数组struct pollfd fds[N]; 

               struct pollfd

               {

                      int fd;        //文件描述符

                 short events;  //等待的事件触发条件----POLLIN读时间触发(大多数)

                 short revents; //实际发生的事件(未产生事件: 0 ))

                }

        2.   nfds:        最大文件描述符个数

        3.  timeout: 超时检测 (毫秒级):1000 == 1s      

                              如果-1,阻塞          如果0,不阻塞

    返回值:  <0 出错              >0 表示有事件产生;

                  如果设置了超时检测时间:&tv

                    <0 出错                >0 表示有事件产生;            ==0 表示超时时间已到;

    (3)poll特点

    1. 优化文件描述符个数的限制;

    (根据poll函数第一个函数的参数来定,如果监听的事件为1个,则结构体数组容量为1,如果想监听100个,那么这个结构体数组的容量就为100,多少文件描述符由程序员自己来决定)

    2. poll被唤醒之后需要重新轮询一遍驱动,效率比较低(消耗CPU)

    3. poll不需重新构造文件描述符表(也不需清空表),只需要从用户空间向内核空间拷贝一次数据(效率相对比较高)

    练习: 

    使用poll实现server的全双工

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include
    16. int acceptfp;
    17. int main(int argc, char const *argv[])
    18. {
    19. char buf[128] = {0};
    20. //1.创建套接字,返回建立链接的文件描述符
    21. int sockfp = socket(AF_INET, SOCK_STREAM, 0);
    22. if (sockfp == -1)
    23. {
    24. perror("socket is err");
    25. exit(0);
    26. }
    27. printf("%d\n", sockfp);
    28. //2.绑定ip和端口号
    29. struct sockaddr_in saddr, caddr;
    30. saddr.sin_family = AF_INET;
    31. saddr.sin_port = htons(atoi(argv[1]));
    32. saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    33. socklen_t len = sizeof(struct sockaddr_in);
    34. if (bind(sockfp, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    35. {
    36. perror("bind is err");
    37. exit(0);
    38. }
    39. //3.listen监听
    40. if (listen(sockfp, 5))
    41. {
    42. perror("liste err");
    43. exit(0);
    44. }
    45. printf("listen ok\n");
    46. //1.创建结构体数组
    47. struct pollfd fds[100];
    48. //2.添加文件描述符和触发方式
    49. fds[0].fd = 0;
    50. fds[0].events = POLLIN;
    51. fds[1].fd = sockfp;
    52. fds[1].events = POLLIN;
    53. int nfds = 1;
    54. int ret;
    55. while (1)
    56. {
    57. //3.poll轮循检测
    58. ret = poll(fds, nfds + 1, 2000);
    59. if (ret < 0)
    60. {
    61. perror("poll is err");
    62. return -1;
    63. }
    64. else if (ret == 0)
    65. {
    66. printf("qeqweqe\n");
    67. continue;
    68. }
    69. //4. 判断哪一个文件描述符产生响应,并发布任务
    70. for (int i = 0; i <= nfds; ++i)
    71. {
    72. if (fds[i].revents == POLLIN)
    73. {
    74. if (fds[i].fd == 0)
    75. {
    76. fgets(buf, sizeof(buf), stdin);
    77. if (buf[strlen(buf) - 1] == '\n')
    78. buf[strlen(buf) - 1] = '\0';
    79. //printf("发送信息:\n");
    80. for (int j = 2; j <= nfds; ++j)
    81. {
    82. send(fds[j].fd, buf, sizeof(buf), 0);
    83. }
    84. }
    85. else if (fds[i].fd == sockfp)
    86. {
    87. acceptfp = accept(sockfp, (struct sockaddr *)&caddr, &len);
    88. if (acceptfp < 0)
    89. {
    90. perror("acceptfp");
    91. exit(0);
    92. }
    93. printf("port:%d ip: %s\n", ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr));
    94. fds[++nfds].fd = acceptfp;
    95. fds[nfds].events = POLLIN;
    96. }
    97. else
    98. {
    99. int recvbyte = recv(fds[i].fd, buf, sizeof(buf), 0);
    100. if (recvbyte < 0)
    101. {
    102. perror("recv err");
    103. return -1;
    104. }
    105. else if (recvbyte == 0)
    106. {
    107. printf("%d client is exit\n", i);
    108. close(fds[i].fd);
    109. //覆盖
    110. fds[i] = fds[nfds];
    111. //--i,--nfds后,最后一个循环不到
    112. --nfds, --i;
    113. }
    114. else
    115. {
    116. printf("%d : %s\n", i, buf);
    117. }
    118. }
    119. }
    120. }
    121. }
    122. return 0;
    123. }

    (4)poll超时时间检测

     timeout: 超时检测 (毫秒级):1000 == 1s      

                      如果-1,阻塞          如果0,不阻塞

    三.epoll实现

    (1)epoll流程:

    Epoll的使用:

    1.创建红黑树 和 就绪链表                                      int epfd = epoll_create(1);

    2.添加文件描述符和事件信息到树上

        event.events = EPOLLIN|EPOLLET;

        event.data.fd = 0;

        epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event

    3.阻塞等待事件的产生,一旦产生事件,则进行处理 

         int ret = epoll_wait(epfd,events,32,-1);

    4.根据链中准备处理的文件描述符 进行处理

    (2)epoll函数族 

    epoll 要使用一组函数:       epoll_create 创建红黑树 和 就序链表

                                              epoll_ctl   添加文件描述符和事件到树上 / 从树上删除

                                              epoll_wait  等待事件产生

    epoll_create 

    创建红黑树以及链表

    头文件:#include 

    声明:int epoll_create(int size);

    功能:创建红黑树根节点(创建epoll实例) , 同时也会创建就绪链表

    返回值:成功时返回一个实例(二叉树句柄),失败时返回-1。

    epoll_ctl

    控制epoll属性

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

    功能:控制epoll属性,比如给红黑树添加节点

    参数:  1. epfd:   epoll_create函数的返回句柄。//一个标识符

              2. op:表示动作类型,有三个宏:         

                    EPOLL_CTL_ADD:注册新的fd到epfd中

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

                    EPOLL_CTL_DEL:从epfd中删除一个fd

    3. 要操作的文件描述符

    4. 结构体信息:

            typedef union epoll_data {

                    int fd; //要添加的文件描述符,只用这个

                    uint32_t u32; typedef unsigned int

                    uint64_t u64; typedef unsigned long int

            } epoll_data_t;

            struct epoll_event {

                    uint32_t events; 事件

                    epoll_data_t data; //共用体(看上面)

            };

               关于events事件:

                EPOLLIN:  表示对应文件描述符可读

                EPOLLOUT: 可写

                EPOLLPRI:有紧急数据可读;

                EPOLLERR:错误;

                EPOLLHUP:被挂断;

                EPOLLET:触发方式,边缘触发;(默认使用边缘触发)

                ET模式:表示状态的变化;

                NULL: 删除一个文件描述符使用,无事件

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

    epoll_wait

    等待事件产生

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

    功能:等待事件产生

       内核会查找红黑树中有事件响应的文件描述符, 并将这些文件描述符放入就绪链表

        就绪链表中的内容, 执行epoll_wait会同时复制到第二个参数events

    参数:   epfd:句柄;

               events:用来保存从就绪链表中响应事件的集合;(传出参数,定义结构体数组)

               maxevents:  表示每次在链表中拿取响应事件的个数;

               timeout:超时时间,毫秒,0立即返回  ,-1阻塞

    返回值: 成功: 实际从链表中拿出的数目     失败时返回-1

    (4)epoll特点

    1.监听的最大的文件描述符没有个数限制(取决与你自己的系统 1GB - 10万个左右)

    2.异步I/O,epoll当有事件产生被唤醒之后,文件描述符主动调用callback(回调函数)函数直接拿到唤醒的文件描述符,不需要轮询,效率高

    3.epoll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可.

    (5)epoll机制

    select,poll都属于 同步IO机制(轮询)

    epoll属于异步IO机制(不轮询): 

    Epoll处理高并发,百万级

    1. 红黑树: 是特殊的二叉树(每个节点带有属性),Epoll怎样能监听很多个呢?首先创建树的根节点,每个节点都是一个fd以结构体的形式存储(节点里面包含了一些属性,callback函数)
    2. 就绪链表: 当某一个文件描述符产生事件后,会自动调用callback函数,通过回调callback函数来找到链表对应的事件(读时间还是写事件)。

     

     练习:

    epoll实现server

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include
    16. #include
    17. int acceptfp;
    18. int main(int argc, char const *argv[])
    19. {
    20. char buf[128] = {0};
    21. //1.创建套接字,返回建立链接的文件描述符
    22. int sockfp = socket(AF_INET, SOCK_STREAM, 0);
    23. if (sockfp == -1)
    24. {
    25. perror("socket is err");
    26. exit(0);
    27. }
    28. printf("%d\n", sockfp);
    29. //2.绑定ip和端口号
    30. struct sockaddr_in saddr, caddr;
    31. saddr.sin_family = AF_INET;
    32. saddr.sin_port = htons(atoi(argv[1]));
    33. saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    34. socklen_t len = sizeof(struct sockaddr_in);
    35. if (bind(sockfp, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    36. {
    37. perror("bind is err");
    38. exit(0);
    39. }
    40. //3.listen监听
    41. if (listen(sockfp, 5))
    42. {
    43. perror("liste err");
    44. exit(0);
    45. }
    46. printf("listen ok\n");
    47. //1.创建红黑树以及链表
    48. //树的跟节点/树的句柄
    49. int epfd = epoll_create(1);
    50. //2.上树
    51. struct epoll_event event;
    52. struct epoll_event events[32] ;
    53. event.events = EPOLLET | EPOLLIN;
    54. event.data.fd = 0;
    55. epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);
    56. event.data.fd = sockfp;
    57. epoll_ctl(epfd, EPOLL_CTL_ADD, sockfp, &event);
    58. while (1)
    59. {
    60. //3.阻塞等待文件描述符产生事件
    61. int ret = epoll_wait(epfd, events, 32, -1);
    62. printf("asdsdfgdsf\n");
    63. if (ret < 0)
    64. {
    65. perror("epoll err");
    66. return -1;
    67. }
    68. //4.根据文件描述符号,进行处理
    69. for (int i = 0; i < ret; ++i)
    70. {
    71. if (events[i].data.fd == 0)
    72. {
    73. fgets(buf, sizeof(buf), stdin);
    74. if (buf[strlen(buf) - 1] == '\n')
    75. buf[strlen(buf) - 1] = '\0';
    76. printf("发送信息:\n");
    77. //send(fds[j].fd, buf, sizeof(buf), 0);
    78. }
    79. else if (events[i].data.fd == sockfp)
    80. {
    81. acceptfp = accept(sockfp, (struct sockaddr *)&caddr, &len);
    82. if (acceptfp < 0)
    83. {
    84. perror("acceptfp");
    85. exit(0);
    86. }
    87. printf("port:%d ip: %s\n", ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr));
    88. //上树
    89. event.data.fd = acceptfp;
    90. epoll_ctl(epfd, EPOLL_CTL_ADD, acceptfp, &event);
    91. }
    92. else
    93. {
    94. int recvbyte = recv(events[i].data.fd, buf, sizeof(buf), 0);
    95. if (recvbyte < 0)
    96. {
    97. perror("recv err");
    98. return -1;
    99. }
    100. else if (recvbyte == 0)
    101. {
    102. printf("%d client is exit\n", events[i].data.fd);
    103. close(events[i].data.fd);
    104. //下树
    105. epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
    106. }
    107. else
    108. {
    109. printf("%d : %s\n", events[i].data.fd, buf);
    110. }
    111. }
    112. }
    113. }
    114. return 0;
    115. }

    对比 

  • 相关阅读:
    2022年全国研究生数学建模竞赛华为杯C题汽车制造涂装-总装缓存调序区调度优化问题求解全过程文档及程序
    c# xml 参数读取读取的简单使用
    pid,ppid,pgid,sid进程间关系
    YOLO免费数据集网站收集
    Apache DolphinScheduler 3.0.0 升级到 3.1.8 教程
    [论文笔记]E5
    关于ubuntu设置sh文件开机自启动python3和sudo python3问题
    Windows10中安装Apache2.4和PHP7.4
    ATFX汇市:9月非农再超预期,高利率并未导致美国宏观经济收缩
    【LeetCode】55. 跳跃游戏
  • 原文地址:https://blog.csdn.net/m0_73731708/article/details/132912805