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

C++ HTTP服务器调用popen启动脚本后端口被守护进程占用问题

问题:HTTP服务器端口被popen启动的守护进程占用,无法重启

摘要

我用C++编写了一个HTTP服务器应用,通过popen()调用脚本启动wpa_supplicant和udhcpd两个守护进程。服务器停止后,这些守护进程会占用服务器端口,导致无法重启,这是为什么?

问题详情

初始化阶段,我的HTTP服务器通过popen()执行脚本启动wpa_supplicant和udhcpd以确保网络接口就绪,之后服务器正常监听80端口。

问题现象:当服务器关闭并执行所有析构函数,且已通过close(int_socket_val)正确关闭套接字后,再次启动服务器会失败,提示80端口不可用。

执行netstat -tulpn查看,发现端口80被wpa_supplicant或udhcpd占用;甚至服务器运行时,netstat也显示端口由这两个进程持有,而非HTTP服务器本身。执行killall -9 wpa_supplicant udhcpd可释放端口,使服务器能重启,但原因未知。

相关代码参考

启动脚本的C++实现

std::string ConnectionManager::exec(const std::string& command, bool strip)
{
   char buffer[EXEC_BUFFER_LEN];
   std::string result = "";

   // Open pipe to file
   FILE* pipe = popen(command.c_str(), "r");
   if (!pipe)
   {
       std::cout << "ERROR: ConnectionManager::exec() - failed to open command: " << command << std::endl;
      return result;
   }

   // read till end of process:
   while (!feof(pipe))
   {
      // use buffer to read and add to result
      if (fgets(buffer, EXEC_BUFFER_LEN, pipe) != NULL)
      {
        result += buffer;
      }
   }

   pclose(pipe);

   if ( strip )
   {
       removeLineEndings(result);
   }

   return result;
}

netstat输出示例

端口被udhcpd占用:

root@device:/usr/bin# netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
.........
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      4267/udhcpd
.........
root@device:/usr/bin#

端口被wpa_supplicant占用:

root@device:/usr/bin# netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
.........
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      4393/wpa_supplicant
.........
root@device:/usr/bin#

调用守护进程的脚本片段

wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf
udhcpc -i wlan0

解答

核心原因是进程文件描述符继承:

  1. 当你用popen()启动脚本时,子进程(包括脚本本身以及它启动的wpa_supplicant/udhcpd)会继承父进程(你的HTTP服务器)所有打开的文件描述符,其中就包括监听80端口的套接字。
  2. 即便你的服务器调用close()关闭了套接字,只要还有其他进程(比如被继承了该套接字的守护进程)持有这个描述符,内核就不会真正释放端口的监听状态。
  3. 加上你用-B或默认方式把wpa_supplicant/udhcpd启动成了守护进程,它们会脱离父进程会话,但依然持有继承来的套接字描述符,导致端口被一直占用。

解决方法

1. 启动守护进程前关闭无关文件描述符

修改脚本,在启动守护进程时显式关闭所有非必要的文件描述符,避免继承:

# 关闭标准输入输出外的所有描述符后启动进程
exec wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf 0>&- 1>&- 2>&-
exec udhcpc -i wlan0 0>&- 1>&- 2>&-

或者用setsid彻底隔离会话和描述符:

setsid wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf < /dev/null > /dev/null 2>&1
setsid udhcpc -i wlan0 < /dev/null > /dev/null 2>&1

2. 给监听套接字加复用选项

在创建HTTP服务器的监听套接字时,设置SO_REUSEADDR和SO_REUSEPORT选项,这样即使端口处于异常占用状态,也能重新绑定:

int opt = 1;
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)) < 0) {
    perror("setsockopt failed");
    exit(EXIT_FAILURE);
}

注意:这只是缓解方案,无法从根源解决描述符继承问题。

3. 用fork()+exec()替代popen()

popen()会默认继承所有描述符,改用fork()后,在子进程中先关闭所有不需要的文件描述符(包括监听套接字),再执行脚本:

pid_t pid = fork();
if (pid == 0) {
    // 子进程:关闭所有无关文件描述符
    for (int fd = 3; fd < getdtablesize(); fd++) {
        close(fd);
    }
    // 执行脚本
    execl("/path/to/your/script.sh", "script.sh", NULL);
    exit(EXIT_FAILURE);
} else if (pid > 0) {
    // 父进程:等待子进程结束(可选)
    waitpid(pid, NULL, 0);
} else {
    perror("fork failed");
}

这种方式能彻底避免子进程继承监听套接字。

4. 服务器退出时主动清理守护进程

在HTTP服务器的析构函数或退出逻辑中,主动杀死wpa_supplicant和udhcpd,确保它们释放所有资源:

system("killall wpa_supplicant udhcpd");

更严谨的方式是记录启动时的进程PID,退出时精准杀死对应实例,避免误杀其他同名进程。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 08:20:26