Socket编程踩坑记:为什么accept返回的socket fd总是0?

问题背景

最近在学习Linux网络编程,跟着视频教程实现一个简单的TCP服务器。代码写完后运行,发现日志中显示的socket文件描述符总是0,而不是预期的4:

bash 复制代码
get ad new link, socket address: [:13976] sockfd: 0

而视频教程中显示的是sockfd: 4。这个差异引起了我的注意,开始了一段有趣的debug之旅。

初始代码

我的TcpSocket类实现如下(简化版):

cpp 复制代码
class TcpSocket : public Socket {
public:
    TcpSocket() : _sockfd(0) {}           // 默认构造函数
    TcpSocket(int sockfd) : _sockfd(sockfd) {}  // 带参构造函数
    
    std::shared_ptr<Socket> Accept(InetAddr& clientaddr) override {
        InetAddr addr;
        socklen_t len = sizeof(addr);
        int sockfd = accept(_sockfd, CONV(&addr), &len);
        
        if(sockfd < 0) {
            LOG(LogLevel::WARNING) << "accept error ";
            return nullptr;
        }
        
        clientaddr = addr;
        return std::make_shared<TcpSocket>();  // 问题在这!
    }
    
    int Sockfd() { return _sockfd; }
    
private:
    int _sockfd;
};

问题定位

1. 文件描述符的基础知识

在Unix/Linux系统中,文件描述符的分配规律是:

  • 0:标准输入(stdin)

  • 1:标准输出(stdout)

  • 2:标准错误(stderr)

  • 3:第一个打开的socket/文件

  • 4:第二个打开的socket/文件

  • 以此类推...

2. 代码执行流程

cpp 复制代码
// 服务器启动
TcpServer server(8080);  // 创建监听socket,获得fd=3

// 客户端连接
while(true) {
    auto conn = listensocket->Accept(clientaddr);  // accept返回新fd=4
    LOG << "sockfd: " << conn->Sockfd();  // 应该显示4,但显示0
}

3. 问题根源

仔细检查Accept方法:

cpp 复制代码
std::shared_ptr<Socket> Accept(InetAddr& clientaddr) {
    int new_sockfd = accept(_sockfd, ...);  // new_sockfd = 4
    
    // 错误:使用默认构造函数,_sockfd被初始化为0
    return std::make_shared<TcpSocket>();  // ❌ 没有传入new_sockfd
}

accept系统调用确实返回了正确的文件描述符(4),但是在创建TcpSocket对象时,我使用了默认构造函数,导致新对象的_sockfd被初始化为0。

解决方案

修复很简单:将accept返回的文件描述符传给TcpSocket的构造函数。

cpp 复制代码
std::shared_ptr<Socket> Accept(InetAddr& clientaddr) override {
    InetAddr addr;
    socklen_t len = sizeof(addr);
    int new_sockfd = accept(_sockfd, CONV(&addr), &len);  // 获得新fd=4
    
    if(new_sockfd < 0) {
        LOG(LogLevel::WARNING) << "accept error ";
        return nullptr;
    }
    
    clientaddr = addr;
    // 正确:传入new_sockfd
    return std::make_shared<TcpSocket>(new_sockfd);  // ✅ 现在Sockfd()返回4
}

验证结果

修改后运行,终于得到了正确的结果:

cpp 复制代码
监听socket fd: 3
get ad new link, socket address: [:13976] sockfd: 4  ✅

深入理解:监听socket vs 连接socket

这个bug也让我更深入理解了两种socket的区别:

特性 监听socket 连接socket
文件描述符 固定(如fd=3) 每次accept新分配
主要功能 accept新连接 recv/send数据
数量 通常1个 多个(每个客户端一个)
生命周期 整个程序运行期 单次连接期间

经验教训

  1. 不要被表面现象迷惑 :日志显示0并不意味着accept失败,可能是对象构造的问题。

  2. 构造函数的选择至关重要:在多态场景下,确保使用正确的构造函数传递关键数据。

  3. 添加调试信息:在关键位置打印中间值:

    cpp 复制代码
    int new_sockfd = accept(...);
    std::cout << "debug: accept returned " << new_sockfd << std::endl;

最后修改的代码

cpp 复制代码
return std::make_shared<TcpSocket>(new_sockfd);  // 传入accept返回的fd

思考题

如果多个客户端同时连接,文件描述符会如何分配?

相关推荐
白菜欣23 分钟前
Linux —《开发三件套:gcc/g++、gdb、make/Makefile 全解析》
linux·运维
senijusene1 小时前
基于 imx6ull平台按键驱动开发:input子系统+中断子系统+platform总线
linux·驱动开发
大卡片1 小时前
IO模型与并发服务器设计
运维·服务器·网络
莎士比亚的文学花园1 小时前
Linux驱动开发(1)——系统移植
linux·运维·服务器
IpdataCloud2 小时前
IPv6商用数据的IP离线库能解决哪些业务问题?适用场景与接入指南
网络·网络协议·tcp/ip
PH = 72 小时前
OverlayFS联合文件系统使用示例
java·linux·服务器
AC赳赳老秦2 小时前
OpenClaw进阶技巧:批量修改文件内容、替换关键词,解放双手
java·linux·人工智能·python·算法·测试用例·openclaw
志栋智能2 小时前
超自动化巡检:解锁运维数据的深层价值
运维·服务器·数据库·自动化
Joseph Cooper3 小时前
STM32MP157 Linux驱动学习笔记(四):典型总线与设备模型(SPI/USB)
linux·stm32·学习
坚持就完事了3 小时前
Linux中的mv命令
linux·运维·服务器