调用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; }
问题原因
- 双重状态变量冲突:主线程维护全局
_isRunning,TCPServer内部维护成员_isRunning,两者独立。Ctrl-C触发后,主线程的_isRunning置为false,但TCPServer的_isRunning仍为true,直到主线程调用stop()才会修改,此时start()里的accept()已阻塞,无法响应状态变化。 - stop()流程错误:当前
stop()先join通信线程,再关闭监听socket。但accept()仍阻塞在监听socket上,导致start()的循环无法退出,_runningThread无法被主线程join,程序卡住。 - 线程安全问题:
_isRunning是普通bool,多线程下可能因为编译器优化导致线程无法及时读取到最新值。
解决方案
关键修改步骤
- 统一状态变量并保证线程安全:将
_isRunning改为std::atomic<bool>,确保多线程下内存可见性。 - 调整stop()执行顺序:先设置
_isRunning为false,再关闭监听socket,唤醒阻塞的accept(),最后join通信线程。 - 修正accept()错误处理:当
accept()返回错误时,仅在_isRunning为true时打印错误,避免退出流程中误报。 - 简化主线程逻辑:让信号处理函数直接触发服务器停止,减少冗余状态判断。
修改后的关键代码
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
相关产品推荐
相关产品推荐

