如何在C++进程中调用自重启脚本并解决端口占用报错问题
问题核心原因
- 你调用
std::system时,系统会fork子进程执行脚本,子进程默认继承父进程所有打开的文件描述符,包括websocket监听9090端口的socket。哪怕原进程被杀死,持有socket的脚本子进程还在运行,导致端口一直被占用。 kill -9是强制终止信号,进程没有机会执行资源清理逻辑,进一步加剧了socket资源未释放的问题。- 你配置的
set_reuse_addr(true)仅能处理端口处于TIME_WAIT状态的复用,无法解决其他进程仍持有该端口socket文件描述符的场景。
可行解决方案
1. 修复C++端脚本调用逻辑,避免子进程继承socket fd
两种可选实现方式:
方式1:创建socket时自动设置FD_CLOEXEC
websocketpp创建socket前添加配置,让所有新建的socket自动带上执行时关闭标记,fork出的子进程会自动关闭这类fd:
m_endpoint.set_socket_init_handler([](websocketpp::connection_hdl, boost::asio::ip::tcp::socket& sock) { int fd = sock.native_handle(); int flags = fcntl(fd, F_GETFD, 0); fcntl(fd, F_SETFD, flags | FD_CLOEXEC); });
方式2:改用posix_spawn调用脚本,显式禁止继承非必要fd
相比std::system可以更精准控制fd继承:
#include <spawn.h> #include <sys/wait.h> extern char **environ; void call_restart_script() { pid_t pid; char* argv[] = { (char*)"sh", (char*)"-c", (char*)"/TEST/restart.sh", NULL }; posix_spawn_file_actions_t actions; posix_spawn_file_actions_init(&actions); // 关闭所有继承的fd,只保留stdin/stdout/stderr for (int i = 3; i < sysconf(_SC_OPEN_MAX); i++) { posix_spawn_file_actions_addclose(&actions, i); } int ret = posix_spawn(&pid, "/bin/sh", &actions, NULL, argv, environ); posix_spawn_file_actions_destroy(&actions); if (ret == 0) { // 父进程直接退出,不要等子进程 _exit(0); } }
2. 优化重启脚本,优先优雅终止进程
将kill -9替换为先发SIGTERM信号,给进程留资源清理的时间,仅优雅退出失败时再强制杀死:
#!/bin/bash PID=$(pidof processA) if [ -n "$PID" ]; then # 先发送SIGTERM让进程优雅退出 kill $PID # 最多等5秒确认进程退出 for i in {1..5}; do if ! kill -0 $PID 2>/dev/null; then break fi sleep 1 done # 进程还没退就强制杀 if kill -0 $PID 2>/dev/null; then kill -9 $PID sleep 1 fi fi /TEST/bin/processA &
3. 添加C++端优雅退出逻辑
在进程收到SIGTERM信号时主动关闭websocket服务,释放端口资源:
#include <signal.h> std::atomic<bool> g_running = true; WebSocketServer* g_ws_server = nullptr; void signal_handler(int sig) { if (sig == SIGTERM || sig == SIGINT) { g_running = false; if (g_ws_server) { // 停止websocket服务,关闭监听socket g_ws_server->m_endpoint.stop_listening(); g_ws_server->m_endpoint.stop(); } } } // 程序初始化时注册信号处理函数 int main() { signal(SIGTERM, signal_handler); signal(SIGINT, signal_handler); // 其他初始化逻辑,创建websocket服务赋值给g_ws_server // ... while (g_running) { // 主循环逻辑 } // 退出前清理资源 return 0; }
4. 兜底启用SO_REUSEPORT配置
除了现有set_reuse_addr(true)外,额外开启端口复用,兜底处理极端场景下的端口占用:
void WebSocketServer::run() { m_endpoint.set_reuse_addr(true); // 添加SO_REUSEPORT配置 m_endpoint.set_socket_init_handler([](websocketpp::connection_hdl, boost::asio::ip::tcp::socket& sock) { int opt = 1; setsockopt(sock.native_handle(), SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)); }); m_endpoint.listen(9090); m_endpoint.start_accept(); m_endpoint.run(); }
内容的提问来源于stack exchange,提问作者CryptoDude
相关产品推荐
相关产品推荐

