fd_set的一些认识

 

    这两天,突然想写一个与平台无关的socket(仅仅是windows和linux平台)。查资料,敲代码,测试,修改。。。如此不断反复,搞了2-3天了。在此期间,发现fd_set在windows和linux平台下面的实现是不同的。特此记录下。
在win下面,实现是(VS2005):
typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;
很明了,一个计数的fd_count,另一个就是SOCKET数组。其中,FD_SETSIZE是64.(具体可以去查看vs的代码)
win下面FD_SET就是检查SOCKET在数组中是否存在,如果不存在,那么就插入到数组最后。而FD_CLR(fd, set)是把fd后面的东西往前拷贝,然后计数减1.FD_ZERO仅仅是把计数置为0(这个要注意!!)!FD_ISSET就看不到实现了。

下面是fd_set在linux下的实现:
typedef struct
  {
    /* XPG4.2 requires this member name.  Otherwise avoid the name
       from the global namespace.  */
#ifdef __USE_XOPEN
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
  } fd_set;

根据UNIX网络编程对fd_set的介绍,fd_set是个整数数组,用每个bit位来表示fd的。比如,一个32位整数,则数组第一个整数表示0-31的fd,以此类推,第二个整数表示32-63.。。。(下面有截图,详见UNIX网络编程第6章,I/O复用,6.3节)
查看linux的FD_SET、FD_CLR是用汇编实现的。根据说明可以知道,就是给bit置位。
(在我的系统中,__FD_SETSIZE是1024,__NFDBITS是sizeof(long int)即32,就是说,fd_set是4个long int 数组,最多放128个文件描述符(是不是太少了啊???))

由于fd_set实现不同,导致selet也不太一样,其他第一个参数在windows下就没有意义了(MSDN: The nfds parameter is included only for compatibility with Berkeley sockets),而在linux下面,是fd_set种的最大的一个描述符+1.具体可以参考其他资料。

你提供的代码: ```c ssize_t n; long sent = offset; while (sent < filesize) { n = read(fd, buffer, BUFFER_SIZE); if (n <= 0) break; write(client_sock, buffer, n); sent += n; } ``` 报错: > **`sent undeclared (first use in this function)`** --- ## 🔍 错误原因分析 这个错误说明:**编译器不认识变量 `sent`**,也就是说你在使用它之前没有正确声明。 但你看代码里明明写了: ```c long sent = offset; ``` 那为什么还报“未声明”? ### ✅ 最可能的原因是:**你把这段代码放在了 C 文件中,却用了 C++ 风格的“在中间声明变量”方式** 在 **C89/C90 标准**中: > 所有局部变量必须在函数开头集中声明,不能在执行语句之后再声明变量! 而你的写法是: ```c ssize_t n; // 这里已经有表达式了吗?比如 read/write? long sent = offset; // ❌ 如果前面已有执行语句,则此行属于“中途声明”,在 C89 中非法 ``` 即使你写了 `ssize_t n;`,但如果后续插入了其他语句(如 `read()`),再声明 `sent` 就出错了。 --- ## 🛠️ 示例:错误写法(C 编译器下会失败) ```c void send_file(int client_sock, int fd, off_t offset, off_t filesize) { char buffer[4096]; ssize_t n = read(fd, buffer, sizeof(buffer)); // 执行了一条语句 long sent = offset; // ❌ 错误!在执行语句后才声明变量 —— 在 C89 中不允许! while (sent < filesize) { write(client_sock, buffer, n); sent += n; n = read(fd, buffer, sizeof(buffer)); if (n <= 0) break; } } ``` 📌 虽然现代编译器默认支持 C99,但如果你: - 使用老旧编译选项 - 没有启用 C99 标准 - 或项目强制使用 `-std=c89` 就会触发这个错误。 --- ## ✅ 正确修复方法 ### ✅ 方法一:【推荐】将所有变量声明移到函数最前面 ```c void send_file(int client_sock, int fd, off_t offset, off_t filesize) { char buffer[4096]; ssize_t n; long sent; // 先声明 // 设置起始偏移 if (lseek(fd, offset, SEEK_SET) == -1) { perror("lseek failed"); return; } sent = offset; // 后赋值 while (sent < filesize) { n = read(fd, buffer, sizeof(buffer)); if (n <= 0) break; // 注意:write 可能未完全写出,建议用 write_all(见下文) write(client_sock, buffer, n); sent += n; } } ``` ✅ 这种风格兼容 **C89、C99、C11** 所有标准。 --- ### ✅ 方法二:启用 C99 或更高标准(允许中间声明) 如果你坚持想写: ```c ssize_t n; long sent = offset; ``` 那你需要确保编译时启用了 **C99** 或以上标准。 #### 编译命令加选项: ```bash gcc -std=c99 -Wall -g server.c -o server ``` 或更现代的: ```bash gcc -std=gnu99 -Wall -g server.c -o server ``` > `gnu99` = C99 + GNU 扩展,广泛支持且实用。 --- ## ⚠️ 补充问题:即使声明了 `sent`,还有潜在 bug! 虽然解决了“undeclared”问题,但原始逻辑仍有缺陷: ### ❌ `write(client_sock, buffer, n);` 的返回值没检查! TCP 套接字的 `write()` 不保证全部数据发出。你应该改用可靠的 `write_all` 函数。 --- ## ✅ 推荐最终修复版(安全 + 兼容性好) ```c #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> // 确保数据全部写出 static ssize_t write_all(int fd, const void *buf, size_t count) { const char *ptr = buf; size_t left = count; ssize_t written; while (left > 0) { written = write(fd, ptr, left); if (written == -1) { if (errno == EINTR) continue; return -1; } ptr += written; left -= written; } return count; } void send_file(int client_sock, int fd, off_t offset, off_t filesize) { char buffer[8192]; ssize_t n; long sent; // ✅ 所有变量声明在最前 int ret; // 移动文件指针 ret = lseek(fd, offset, SEEK_SET); if (ret == -1) { perror("lseek failed"); return; } sent = offset; while (sent < filesize) { size_t to_read = (filesize - sent > sizeof(buffer)) ? sizeof(buffer) : (size_t)(filesize - sent); n = read(fd, buffer, to_read); if (n == 0) break; // EOF if (n == -1) { if (errno == EINTR) continue; perror("read error"); break; } if (write_all(client_sock, buffer, n) != n) { // ✅ 使用 write_all perror("network write failed"); break; } sent += n; } } ``` --- ## ✅ 总结 | 问题 | 解决方案 | |------|----------| | `sent undeclared` | 把 `long sent;` 放到函数顶部声明 | | C89 不允许中间声明 | 使用 `-std=c99` 或统一前置声明 | | `write()` 不完整 | 使用 `write_all()` 包装确保全发 | | 忽略 `EINTR` | 系统调用被信号中断需重试 | --- ##
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值