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

思考题

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

相关推荐
数智化管理手记5 小时前
精益生产中的TPM管理是什么?一文破解设备零故障的密码
服务器·网络·数据库·低代码·制造·源代码管理·精益工程
Vect__6 小时前
深刻理解进程、线程、程序
linux
@insist1237 小时前
网络工程师-生成树协议(STP/RSTP/MSTP)核心原理与应用
服务器·开发语言·网络工程师·软考·软件水平考试
末日汐7 小时前
传输层协议UDP
linux·网络·udp
zzzsde10 小时前
【Linux】库的制作和使用(3)ELF&&动态链接
linux·运维·服务器
CQU_JIAKE10 小时前
4.3【A]
linux·运维·服务器
RopenYuan10 小时前
FastAPI -API Router的应用
前端·网络·python
AI周红伟10 小时前
OpenClaw是什么?OpenClaw能做什么?OpenClaw详细介绍及保姆级部署教程-周红伟
大数据·运维·服务器·人工智能·微信·openclaw
qing2222222210 小时前
Linux中修改mysql数据表
linux·运维·mysql
@insist12310 小时前
网络工程师-VLAN 技术原理与配置指南(软考局域网核心考点)
网络·网络工程师·软考·软件水平考试