fd_set解析

用到select,就难免不了要接触一下几个宏

  fd_set set;
  FD_ZERO(&set);
  FD_SET(read_fd,&set);
  select(MAX_NUM+1,&set,NULL,NULL,NULL);
  if(FD_ISSET(read_fd,set){……}

这几个经常是按这种次序组合出现的,之前虽然也用过,但说实话,也就是用,根本不知道怎么回事,现在有了初步的了解。

首先是 fd_set,这是什么?其实这是一个数组的宏定义,数组的类型是(unsigned) long型的(个人推测无符号),占有128位,无论是32位的linux下,还是64位下,他都是128个字节(128*8位=1024位,也就是可以检测1024个任务,这也是select默认的个数,可以通过编译内核修改),但是由于不同位数的系统下long型的长度不同,所以导致数组的元素个数也是不同的。理解fd_set可以根据位图的方式来理解。

代码:

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <sys/select.h>  
  3. #include <unistd.h>  
  4. int main(void)  
  5. {  
  6.         fd_set fdset;  
  7.         FD_ZERO (&fdset);  
  8.         FD_SET(STDIN_FILENO,&fdset);  
  9.         FD_SET(STDOUT_FILENO,&fdset);  
  10.         FD_SET(STDERR_FILENO,&fdset);  
  11.         FD_SET(7,&fdset);  
  12.         if(FD_ISSET(STDOUT_FILENO,&fdset)!=0)  
  13.                 printf("stdout has been set\n");  
  14.         else  
  15.                 printf("stdout has not been set\n");  
  16.         FD_CLR(STDOUT_FILENO,&fdset);  
  17.         if(FD_ISSET(STDOUT_FILENO,&fdset)!=0)  
  18.                 printf("stdout has been set\n");  
  19.         else  
  20.                 printf("stdout has not been set\n");  
  21.         return 0;  
  22. }  

下面是我的简单调试

[cpp]  view plain  copy
  1. GNU gdb (GDB) Red Hat Enterprise Linux (7.0.1-42.el5)  
  2. Copyright (C) 2009 Free Software Foundation, Inc.  
  3. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>  
  4. This is free software: you are free to change and redistribute it.  
  5. There is NO WARRANTY, to the extent permitted by law.  Type "show copying"  
  6. and "show warranty" for details.  
  7. This GDB was configured as "x86_64-redhat-linux-gnu".  
  8. For bug reporting instructions, please see:  
  9. <http://www.gnu.org/software/gdb/bugs/>...  
  10. Reading symbols from /home/fy/SystemV_ipc/select/run...done.  
  11. (gdb) l  
  12. 1       #include <stdio.h>  
  13. 2       #include <sys/select.h>  
  14. 3       #include <unistd.h>  
  15. 4       int main(void)  
  16. 5       {  
  17. 6               fd_set fdset;  
  18. 7               FD_ZERO (&fdset);  
  19. 8               FD_SET(STDIN_FILENO,&fdset);  
  20. 9               FD_SET(STDOUT_FILENO,&fdset);  
  21. 10             FD_SET(STDERR_FILENO,&fdset);  
  22. (gdb) b 7   
  23. Breakpoint 1 at 0x4004a3: file select.c, line 7.  
  24. (gdb) b 8  
  25. Breakpoint 2 at 0x4004d2: file select.c, line 8.  
  26. (gdb) b 9  
  27. Breakpoint 3 at 0x4004e4: file select.c, line 9.  
  28. (gdb) b 10  
  29. Breakpoint 4 at 0x4004f6: file select.c, line 10.  
  30. (gdb) l  
  31. 11              FD_SET(7,&fdset);  
  32. 12              if(FD_ISSET(STDOUT_FILENO,&fdset)!=0)  
  33. 13                      printf("stdout has been set\n");  
  34. 14              else  
  35. 15                      printf("stdout has not been set\n");  
  36. 16              FD_CLR(STDOUT_FILENO,&fdset);  
  37. 17              if(FD_ISSET(STDOUT_FILENO,&fdset)!=0)  
  38. 18                      printf("stdout has been set\n");  
  39. 19              else  
  40. 20                      printf("stdout has not been set\n");  
  41. (gdb) b 11  
  42. Breakpoint 5 at 0x400508: file select.c, line 11.  
  43. (gdb) b 12  
  44. Breakpoint 6 at 0x400518: file select.c, line 12.  
  45. (gdb) r  
  46. Starting program: /home/fy/SystemV_ipc/select/run   
  47. warning: no loadable sections found in added symbol-file system-supplied DSO at 0x2aaaaaaab000  
  48. Breakpoint 1, main () at select.c:7  
  49. 7               FD_ZERO (&fdset);  
  50. (gdb) p fdset  注释:在执行FD_ZERO (&fdset);可以看下fdset里面到底放着什么  
  51. $1 = {__fds_bits = {0, 15774429, 194, 15774429, 140737488348710, 140737488348711, 0, 267984686016, 4195728, 4195203, 705412764499181569, 4195783, 0, 267984686016, 4195728, 0}}  
  52. (gdb) p sizeof(fdset)注释:看看fdset有多少位  
  53. $2 = 128  
  54. (gdb) c  
  55. Continuing.  
  56. Breakpoint 2, main () at select.c:8  
  57. 8               FD_SET(STDIN_FILENO,&fdset);  
  58. (gdb) p fdset 注释:在执行FD_ZERO (&fdset);后可以看下fdset里面到底放着什么,这也告诉了我们FD_ZERO的作用  
  59. $3 = {__fds_bits = {0 <repeats 16 times>}}注释:我的系统是64位,long占有8个字节,128个字节空间,所以是<repeats 16 times>(gdb) c  
  60. Continuing.也就是一个有16个元素的数组  
  61. Breakpoint 3, main () at select.c:9  
  62. 9               FD_SET(STDOUT_FILENO,&fdset);  
  63. (gdb) p fdset  注释:在执行FD_SET(STDIN_FILENO,&fdset);STDIN_FILENO为标准输入为0 最低八位00000001,0占了第0位  
  64. $4 = {__fds_bits = {1, 0 <repeats 15 times>}}  
  65. (gdb) c  
  66. Continuing.  
  67. Breakpoint 4, main () at select.c:10  
  68. 10              FD_SET(STDERR_FILENO,&fdset);  
  69. (gdb) p fdset  
  70. $5 = {__fds_bits = {3, 0 <repeats 15 times>}}注释:加入了STDOUT(1),最低八位为00000011,所以FD_SET应该是设置位的  
  71. (gdb) c  
  72. Continuing.  
  73. Breakpoint 5, main () at select.c:11  
  74. 11              FD_SET(7,&fdset);  
  75. (gdb) p fdset  
  76. $6 = {__fds_bits = {7, 0 <repeats 15 times>}}注释:加入了标准出粗输出(2),最低八位为00000111==7  
  77. (gdb) c  
  78. Continuing.  
  79. Breakpoint 6, main () at select.c:12  
  80. 12              if(FD_ISSET(STDOUT_FILENO,&fdset)!=0)注释:j加入了自定义7后,最低八位10000111==135  
  81. (gdb) p fdset  
  82. $7 = {__fds_bits = {135, 0 <repeats 15 times>}}  
  83. (gdb) c  
  84. Continuing.  
  85. stdout has been set  
  86. stdout has not been set  
  87. Program exited normally.  
  88. (gdb)   

OK。这就是fd_set。
经过这个调试输出,那么对于这几个宏的作用,一定不会陌生了吧。

FD_ZERO(&set); /*将set清零使集合中不含任何fd*/
FD_SET(fd, &set); /*将fd加入set集合*/
FD_CLR(fd, &set); /*将fd从set集合中清除*/
FD_ISSET(fd, &set); /*测试fd是否在set集合中*/

对于这几个宏的实现,自然越是难事,只要你位操作熟练掌握。

PS:另外说一下,STDIN_FILENO==0,STDOUT_FILENO=1,STDERR_FILENO==2,这也说明了为什么我们建立某些文件描述符从3开始。

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/time.h> #define MAX_CLIENTS 10 #define BUFFER_SIZE 1024 int main() { int server_fd, client_fd, max_fd; struct sockaddr_in server_addr, client_addr; socklen_t client_addr_len; fd_set readfds, temp_readfds; char buffer[BUFFER_SIZE]; int client_sockets[MAX_CLIENTS] = {0}; // 创建套接字 server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } // 绑定地址和端口 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(12345); if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } // 监听 if (listen(server_fd, MAX_CLIENTS) < 0) { perror("listen failed"); exit(EXIT_FAILURE); } // 初始化fd_set FD_ZERO(&readfds); FD_SET(server_fd, &readfds); max_fd = server_fd; while (1) { temp_readfds = readfds; // select等待就绪文件描述符 int activity = select(max_fd + 1, &temp_readfds, NULL, NULL, NULL); if (activity < 0) { perror("select failed"); exit(EXIT_FAILURE); } // 检查服务器套接字 if (FD_ISSET(server_fd, &temp_readfds)) { client_addr_len = sizeof(client_addr); client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len); if (client_fd < 0) { perror("accept failed"); exit(EXIT_FAILURE); } printf("New client connected, socket fd is %d\n", client_fd); // 添加到客户端套接字数组 for (int i = 0; i < MAX_CLIENTS; i++) { if (clie
03-14
<think>好的,用户问的是“fd_set的作用”。我需要先理解这个结构体在C语言网络编程中的用途。用户可能刚开始学习套接字编程,或者在处理多路复用IO时遇到了问题。他们可能想知道fd_set是什么,为什么需要它,以及如何使用它。 首先,fd_set是用于select函数的一种数据结构,用来管理多个文件描述符。用户可能已经了解过select,但需要更深入的理解。我需要解释清楚fd_set的结构,比如它实际上是一个位数组,每个位代表一个文件描述符的状态。 然后,用户可能想知道如何操作这个结构体,比如FD_SETFD_CLR等宏的作用。需要详细说明每个宏的功能,以及它们如何影响fd_set中的位。此外,还要提到FD_ISSET宏用于检查文件描述符是否在集合中,这在select返回后非常重要。 接下来,应该结合select函数的用法,说明fd_set在其中的角色。用户可能想知道为什么select需要三个不同的fd_set(读、写、异常),以及它们如何配合工作。这里可以举一个例子,比如服务器监听多个客户端连接时,如何用fd_set来管理这些套接字。 还需要注意常见的问题,比如fd_set的大小限制(通常1024),这可能影响程序的扩展性。用户可能在处理大量连接时遇到问题,这时候可能需要提到替代方案,如epoll或kqueue。 另外,用户可能对为什么使用位操作而不是其他数据结构有疑问。需要解释位操作的高效性,尤其是在处理大量文件描述符时,位操作的速度快,内存占用少。 最后,给出一个代码示例,展示fd_set的典型用法,帮助用户更直观地理解。确保示例包括初始化和使用各个宏的步骤,以及select调用的流程。同时,提醒用户在使用前清空集合,并检查返回值处理错误情况。</think>以下是关于 `fd_set` 的详细解析: --- ### **1. 核心作用** **`fd_set` 是用于 `select()` 多路复用 I/O 的核心数据结构**,本质是一个 **文件描述符集合的位掩码(bitmask)**。它通过二进制位(0/1)标记需要监控的文件描述符(socket、文件等),配合 `select()` 函数实现 **同时监听多个I/O事件** 的能力。 --- ### **2. 数据结构本质** - **物理结构**:`fd_set` 通常实现为一个整型数组(如 `long int` 数组),每个元素的每一位对应一个文件描述符。 - **逻辑含义**: - **位值为 1**:该文件描述符被加入监控集合 - **位值为 0**:该文件描述符未被监控 **示例**: 若 `fd_set` 中第 3 位为 1,表示文件描述符 `3` 被加入集合。 --- ### **3. 关键操作宏** 系统提供四个宏来操作 `fd_set`: | 宏 | 作用 | 等效伪代码 | |--------------------|---------------------------------|--------------------------| | `FD_ZERO(fd_set *)` | 清空集合(所有位置0) | `mask = 0` | | `FD_SET(int fd, fd_set *)` | 将 `fd` 加入集合(对应位置1) | `mask \|= (1 << fd)` | | `FD_CLR(int fd, fd_set *)` | 将 `fd` 移出集合(对应位置0) | `mask &= ~(1 << fd)` | | `FD_ISSET(int fd, fd_set *)` | 检查 `fd` 是否在集合中 | `return (mask & (1 << fd))` | --- ### **4. 与 `select()` 的配合流程** `fd_set` 需与 `select()` 函数配合使用,典型流程如下: ```c #include <sys/select.h> fd_set read_fds; // 定义读事件集合 FD_ZERO(&read_fds); // 清空集合 FD_SET(sockfd, &read_fds); // 将 sockfd 加入监控 // 设置超时时间(非必须) struct timeval timeout = {5, 0}; // 5秒 // 调用 select() 监听事件 int ready = select(sockfd + 1, &read_fds, NULL, NULL, &timeout); if (FD_ISSET(sockfd, &read_fds)) { // 检查 sockfd 是否可读 // 处理读事件 } ``` --- ### **5. 技术限制** - **最大文件描述符数量**: `fd_set` 的位数由宏 `FD_SETSIZE` 决定(通常为 1024),因此最多只能监控 `FD_SETSIZE` 个文件描述符。 - **效率问题**: 每次调用 `select()` 需遍历所有监控的文件描述符,当数量较大时效率较低。替代方案:`epoll`(Linux)、`kqueue`(BSD)。 --- ### **6. 典型应用场景** | 场景 | 具体行为 | |---------------------|---------------------------------------------| | 多客户端TCP服务器 | 监控多个客户端套接字的读/写事件 | | 非阻塞I/O超时控制 | 通过 `timeout` 参数设置等待时间 | | 混合监听多种I/O类型 | 同时监控套接字、管道、标准输入等不同文件类型 | --- ### **7. 代码示例:监听标准输入与套接字** ```c #include <stdio.h> #include <sys/select.h> #include <unistd.h> int main() { fd_set read_fds; FD_ZERO(&read_fds); FD_SET(STDIN_FILENO, &read_fds); // 监控标准输入(文件描述符0) struct timeval timeout = {5, 0}; int ready = select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout); if (ready == -1) { perror("select error"); } else if (ready) { if (FD_ISSET(STDIN_FILENO, &read_fds)) { printf("标准输入有数据可读\n"); } } else { printf("超时,无事件发生\n"); } return 0; } ``` --- ### **8. 对比:`fd_set` vs `epoll`** | 特性 | `fd_set` + `select()` | `epoll` | |---------------|-----------------------------------|-----------------------------------| | 最大并发量 | 受限于 `FD_SETSIZE`(默认1024) | 仅受系统内存限制 | | 效率 | O(n) 遍历 | O(1) 事件通知 | | 触发模式 | 水平触发(Level-Triggered) | 支持水平触发/边缘触发 | | 适用场景 | 少量文件描述符 | 高并发场景(如Web服务器) | --- ### **总结** - `fd_set` 是 `select()` 多路复用的核心工具,通过位掩码管理文件描述符集合。 - 需配合 `FD_*` 宏进行集合操作,适用于低并发或跨平台场景。 - 高并发场景建议使用更高效的 `epoll`(Linux)或 `kqueue`(BSD)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值