You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用close关闭通信socket后accept()仍阻塞的问题求助

TCP服务器Ctrl-C后accept阻塞无法退出问题解决

问题描述

开发支持Ctrl-C正常关闭的小型TCP服务器,触发Ctrl-C后,即便关闭通信socket,accept()函数仍处于阻塞状态,导致程序无法正常退出。

环境与编译命令

  • 开发环境:Windows + WSL Ubuntu 20
  • 编译命令:g++ -std=c++17 -pthread -g main.cpp TCPServer.cpp -o tcpserver

相关代码

main.cpp

#include <csignal>
#include "TCPServer.h"
#include <cstdlib>
#include <iostream>
#include <unistd.h>

bool _isRunning = false;

void signalHandler(int signum){
    _isRunning = false;
}

int main(int argc, char const *argv[])
{
    signal(SIGINT, signalHandler);
    TCPServer tcpserver(20002);

    _isRunning = true;
    std::cout << "Running !" << std::endl;
    while(_isRunning){
        usleep(10);
    }
    std::cout << "Time to Stop !" << std::endl;
    tcpserver.stop();
    tcpserver.join();
    return 0;
}

TCPServer.h

#include <sys/socket.h>
#include <netinet/in.h>
#include <unordered_set>
#include <thread>
#include <vector>

class TCPServer;

struct thread_params {
    sockaddr_in address;
    socklen_t addrlen;
    int socket;
    unsigned short listeningPort;
    TCPServer * server;
};

class TCPServer {

public:

    TCPServer(unsigned short port);
    ~TCPServer();
    int init();
    int start();
    int stop();
    static int run(TCPServer * server);
    static void startThreadCommunication(thread_params parameters){
        TCPServer *server = parameters.server;
        return server->communicationHandler(parameters);
    }
    inline void join(){
        _runningThread.join();
    }

private:

    // Connection
    unsigned short _connectionPort;
    int _connectionSocket;
    sockaddr_in _address;

    // Thread Management
    static void communicationHandler(thread_params);
    std::vector<std::thread> _comThreads;
    bool _isRunning;
    std::thread _runningThread;
    // Attributes
    unsigned short _nbConnections;
    static unsigned int _idGenerationNb;
};

TCPServer.cpp

#include "TCPServer.h"
#include <iostream>
#include <arpa/inet.h>
#include <thread>
#include <unistd.h>
#include <string>
#include <utility>
#include <cstring>
#include <cctype>
#include <functional>
#include <errno.h>

TCPServer::TCPServer(unsigned short port) : 
_connectionPort(port),
_isRunning(false),
_nbConnections(0)
{
    _runningThread = std::thread(TCPServer::run, this);
}

TCPServer::~TCPServer()
{
}

int TCPServer::run(TCPServer * server){

    if(server->init() < 0){
        std::cerr << "TCPServer failed to initialize for previous reasons" << std::endl;
        return -1;
    }
    if(server->start() < 0){
        return -1;
    }
    std::cout << "End Run" << std::endl;
    return 0;
}

int TCPServer::init()
{
    if((_connectionSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){
        std::cerr << "Socket creation failed : " << strerror(errno) << std::endl;
        return -1;
    }
    _address.sin_family = AF_INET;
    _address.sin_addr.s_addr = INADDR_ANY;
    _address.sin_port = htons(_connectionPort);
    if(bind(_connectionSocket, (sockaddr*) &_address, sizeof(_address)) < 0){
        std::cerr << "Socket binding failed : " << strerror(errno) << std::endl;
        return -1;
    }
    return 0;
}

void TCPServer::communicationHandler(thread_params threadParameters){
    // Do something for the client
    while(threadParameters.server->_isRunning) {
        usleep(100000);
        
        if(send(threadParameters.socket, "Hi\n", sizeof("Hi\n"), 0) < sizeof("Hi\n")){
            break;
        }
    }
    std::cout << "Bye" << std::endl;
    send(threadParameters.socket, "Bye\n", sizeof("Bye\n"), 0);
    close(threadParameters.socket);
    return;
}


int TCPServer::start()
{
    std::cout << "ADRESS OF THE SERVER START" << this << std::endl;
    if(listen(_connectionSocket, 6) < 0){
        std::cerr << "Socket listening failed : " << strerror(errno) << std::endl;
        return -1;
    }
    _isRunning = true;
    while (_isRunning){
        usleep(10);
        sockaddr_in newClientAddr;
        int newClientAddrLen = sizeof(newClientAddr);
        int newSocket;
        if((newSocket = accept(_connectionSocket, (sockaddr*)&newClientAddr, (socklen_t*)&newClientAddrLen)) < 0 && _isRunning){
            std::cerr << "Socket accepting failed : " << strerror(errno) << std::endl;
            return -1;
        }
        else {
            char ipAddress[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &(newClientAddr.sin_addr), ipAddress, INET_ADDRSTRLEN);
            // Accepting the client
            std::cout << "Client connected" << std::endl;
            thread_params params;
            params.address = newClientAddr;
            params.addrlen = sizeof(newClientAddr);
            params.socket = newSocket;
            params.server = this;
            _comThreads.push_back(std::thread(startThreadCommunication, params));
            // Creating the communication thread
        }
    }
    std::cout << "End start" << std::endl;
    return 0;
}

int TCPServer::stop(){
    std::cout << "Stop !" << std::endl;
    _isRunning = false;
    for(unsigned int i = 0; i < _comThreads.size(); i++){
        _comThreads[i].join();
    }
    close(_connectionSocket);
    return 0;
}

问题原因

  1. 双重状态变量冲突:主线程维护全局_isRunning,TCPServer内部维护成员_isRunning,两者独立。Ctrl-C触发后,主线程的_isRunning置为false,但TCPServer的_isRunning仍为true,直到主线程调用stop()才会修改,此时start()里的accept()已阻塞,无法响应状态变化。
  2. stop()流程错误:当前stop()先join通信线程,再关闭监听socket。但accept()仍阻塞在监听socket上,导致start()的循环无法退出,_runningThread无法被主线程join,程序卡住。
  3. 线程安全问题:_isRunning是普通bool,多线程下可能因为编译器优化导致线程无法及时读取到最新值。

解决方案

关键修改步骤

  1. 统一状态变量并保证线程安全:将_isRunning改为std::atomic<bool>,确保多线程下内存可见性。
  2. 调整stop()执行顺序:先设置_isRunning为false,再关闭监听socket,唤醒阻塞的accept(),最后join通信线程。
  3. 修正accept()错误处理:当accept()返回错误时,仅在_isRunning为true时打印错误,避免退出流程中误报。
  4. 简化主线程逻辑:让信号处理函数直接触发服务器停止,减少冗余状态判断。

修改后的关键代码

main.cpp

#include <csignal>
#include "TCPServer.h"
#include <cstdlib>
#include <iostream>
#include <unistd.h>
#include <atomic>

std::atomic<bool> g_isRunning(false);
TCPServer* g_server = nullptr;

void signalHandler(int signum){
    g_isRunning = false;
    if(g_server){
        g_server->stop();
    }
}

int main(int argc, char const *argv[])
{
    signal(SIGINT, signalHandler);
    TCPServer tcpserver(20002);
    g_server = &tcpserver;

    g_isRunning = true;
    std::cout << "Running !" << std::endl;
    while(g_isRunning){
        usleep(10);
    }
    std::cout << "Time to Stop !" << std::endl;
    tcpserver.join();
    return 0;
}

TCPServer.h(新增头文件+修改成员)

#include <sys/socket.h>
#include <netinet/in.h>
#include <unordered_set>
#include <thread>
#include <vector>
#include <atomic> // 新增头文件

// ... 其他代码不变 ...

class TCPServer {
    // ... 其他代码不变 ...
private:
    // ... 其他代码不变 ...
    std::atomic<bool> _isRunning; // 替换原bool _isRunning
    // ... 其他代码不变 ...
};

TCPServer.cpp(修改stop()和start())

// ... 其他代码不变 ...

int TCPServer::start()
{
    std::cout << "ADRESS OF THE SERVER START" << this << std::endl;
    if(listen(_connectionSocket, 6) < 0){
        std::cerr << "Socket listening failed : " << strerror(errno) << std::endl;
        return -1;
    }
    _isRunning = true;
    while (_isRunning){
        usleep(10);
        sockaddr_in newClientAddr;
        int newClientAddrLen = sizeof(newClientAddr);
        int newSocket = accept(_connectionSocket, (sockaddr*)&newClientAddr, (socklen_t*)&newClientAddrLen);
        
        if(newSocket < 0){
            if(_isRunning){ // 仅运行中报错
                std::cerr << "Socket accepting failed : " << strerror(errno) << std::endl;
            }
            continue; // 不直接返回,继续检查_isRunning
        }
        
        // 客户端连接处理逻辑不变
        char ipAddress[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &(newClientAddr.sin_addr), ipAddress, INET_ADDRSTRLEN);
        std::cout << "Client connected" << std::endl;
        thread_params params;
        params.address = newClientAddr;
        params.addrlen = sizeof(newClientAddr);
        params.socket = newSocket;
        params.server = this;
        _comThreads.push_back(std::thread(startThreadCommunication, params));
    }
    std::cout << "End start" << std::endl;
    return 0;
}

int TCPServer::stop(){
    std::cout << "Stop !" << std::endl;
    _isRunning = false;
    close(_connectionSocket); // 先关闭监听socket,唤醒accept
    // 处理通信线程
    for(auto& thread : _comThreads){
        if(thread.joinable()){
            thread.join();
        }
    }
    _comThreads.clear(); // 清理线程容器
    return 0;
}

修改说明

  • std::atomic<bool>确保_isRunning的修改对所有线程即时可见,避免编译器优化导致的状态同步问题。
  • stop()中先关闭监听socket,让阻塞的accept()立即返回错误,此时start()的循环会检测到_isRunning为false并退出。
  • 调整accept()错误处理逻辑,避免在退出流程中打印不必要的错误信息。

内容的提问来源于stack exchange,提问作者Kaldir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 01:37:18