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

思考题

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

相关推荐
数据知道11 分钟前
网络安全实战:API 安全攻防——RESTful 接口渗透测试方法论
网络·安全·web安全·网络安全·restful
布局呆星9 小时前
云服务器上部署单项目/多项目
运维·服务器
盐焗鹌鹑蛋9 小时前
【Linux】缓冲区
linux·运维·服务器
ltl11 小时前
向量库与图 RAG:HNSW、DiskANN 到 GraphRAG
linux
严谨的麻辣烫11 小时前
数据中心出口与住宅出口的技术差异:从 ASN 到 TCP 行为的一次拆解
网络·网络协议·tcp/ip
Freed&11 小时前
Zabbix TCP连接状态自定义监控完整部署手册
网络·tcp/ip·zabbix
学着改变27512 小时前
手持式超声波流量计为何首选行澳科技?便携巡检与外夹计量的性能标杆
大数据·网络·人工智能·科技·产品运营·量子计算
学代码的CJY12 小时前
Linux 库制作与原理:静态库、动态库、ELF 与动态链接完全指南
linux·运维·服务器
沸速存储13 小时前
CPU 和 GPU 核心差别在哪?为什么 AI 训练离不开 GPU
服务器·人工智能·科技·嵌入式硬件·电脑
2501_9159214313 小时前
抓包鹰 Traceeagle 解除证书绑定,SSL Pinning 解除
网络·网络协议·网络安全·ios·adb·https·ssl