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

思考题

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

相关推荐
wanhengidc2 小时前
服务器 科技生活
服务器·科技·生活
WJ.Polar2 小时前
Ansible Ad-Hoc命令
linux·运维·网络·ansible
小吴编程之路2 小时前
Linux基础命令大全
linux·运维·服务器
zmjjdank1ng2 小时前
如何保证ansible的幂等性
linux·服务器·ansible
崎岖Qiu2 小时前
【计算机网络 | 第十八篇】子网掩码和子网划分方法
网络·计算机网络·子网划分·子网掩码
吴声子夜歌2 小时前
小程序——跳转API
服务器·前端·小程序
鹿角片ljp2 小时前
前后端分离项目打包部署教程
java·服务器
深圳市九鼎创展科技2 小时前
国产高性能 AIoT 核心板!九鼎创展 Z3576 核心板全面解析(基于瑞芯微 RK3576)
大数据·linux·人工智能·嵌入式硬件·ubuntu
云边云科技_云网融合2 小时前
SD-WAN 专线:为亚马逊云、微软云访问提速的核心逻辑
网络·人工智能·安全·microsoft·架构