从 C++ 应用程序启动 Linux 服务时避免套接字继承

Avoid socket inheritance when starting Linux service from C++ application

我有一个 Linux 服务(守护进程),它有多个线程并使用 boost io_service 侦听 TCP 套接字。当我在该套接字上收到一条消息时,我想启动另一项服务,例如/etc/init.d/corosync start

问题是在启动服务后,当我退出自己的服务时,其他服务继承了我自己服务的套接字,它仍然处于一种奇怪的状态,我无法以通常的方式停止它。

在退出我的进程之前"MonitorSipServer"打开的套接字显示如下:

netstat -anop |grep 144
tcp        0      0 0.0.0.0:20144           0.0.0.0:*               LISTEN          4480/MonitorSipServ off (0.00/0/0)
tcp        0      0 140.0.24.181:20144      140.0.101.75:47036      ESTABLISHED 4480/MonitorSipServ off (0.00/0/0)

退出我的进程后"MonitorSipServer"打开的套接字显示如下:

netstat -anop |grep 144
tcp        0      0 0.0.0.0:20144           0.0.0.0:*               LISTEN      4502/corosync    off (0.00/0/0)
tcp        0      0 140.0.24.181:20144      140.0.101.75:47036      ESTABLISHED 4502/corosync    off (0.00/0/0)

我已经尝试过 systempopenfork + execvexecve 以及 null 环境。结果总是相同或更糟。 我最后的希望是 Linux setsid 命令,但它也没有用。

如有任何帮助,我们将不胜感激。 问候, 一月

如果您指的是套接字描述符本身被 exec 的子进程继承,并且这是不可取的,那么您可以在使用 socket(2) 创建套接字时传递 SOCK_CLOEXEC 以使确保在执行其他程序时关闭它们。 (顺便说一句,这不会关闭连接,因为您的程序仍然有对套接字的引用。)

如果你正在使用一些更高级别的库,那么检查是否有一些方法让它通过这个标志,或者做 fcntl(sock_fd, F_SETFD, fcntl(sock_fd, F_GETFD) | FD_CLOEXEC) 在描述符之后设置 close-on-exec 标志如果你能得到它,它就会被创建。 (尽管 fcntl(2) 方法在多线程环境中可能很活泼,因为某些线程可能 exec(3) 在创建套接字和在其上设置 FD_CLOEXEC 之间的程序。 )

如果以上方法不起作用,那么您可以在执行服务之前手动 fork(2) 然后 close(2) 套接字描述符。 SOCK_CLOEXEC 的优点是只有当 exec*() 确实成功时才会关闭套接字,这有时更容易从错误中恢复。此外,SOCK_CLOEXEC 可以避免一些竞争,并且更难忘记关闭描述符。

I have a Linux service (daemon) that has multiple-threads and uses boost io_service listening on a TCP socket. When I receive a certain message on that socket I want to start another service with e.g. /etc/init.d/corosync start

对于带有 systemd 的现代 Linux,您可能希望查看 socket activation。也就是说,您的主服务守护进程只会将数据报发送到 unix 套接字(该数据报可能包含您希望显式传递给其他服务的文件描述符)。如果尚未启动,systemd 将为您启动其他服务。

使用 systemd 的好处是您的服务不需要 运行 作为 root 来启动另一个服务,并且服务进程彼此完全隔离(没有任何继承当 forking).

Boost.Asio supports the fork() system call:

  • 需要程序准备并通知分叉的io_service io_service::notify_fork():

    io_service_.notify_fork(boost::asio::io_service::fork_prepare);
    if (fork() == 0)
    {
      io_service_.notify_fork(boost::asio::io_service::fork_child);
      ...
    }
    else
    {
      io_service_.notify_fork(boost::asio::io_service::fork_parent);
      ...
    }
    

    未能使用此模式会导致未指定的行为。对于某些配置,父级将无法接收事件通知,因为它们被子级消耗了。

  • 程序负责处理可通过 Boost.Asio 的 public API 访问的任何文件描述符。例如,如果父进程有一个打开的 acceptor,那么子进程需要在生成自己的子进程或替换进程映像之前显式调用 acceptor::close()。分叉支持文档指出:

    Note that any file descriptors accessible via Boost.Asio's public API (e.g. the descriptors underlying basic_socket<>, posix::stream_descriptor, etc.) are not altered during a fork. It is the program's responsibility to manage these as required.

  • Boost.Asio 的 fork 支持对于多线程进程是不安全的。对于多线程进程,fork() states that the child may only invoke async-signal-safe operations between fork() and one of the exec() functions. io_service::notify_fork() does not make this guarantee, and the current implementation 调用非异步信号安全操作。

尽管如此,我已经看到应用程序在多线程进程中使用 Boost.Asio 的 fork() 支持时没有观察到不良行为。但是,如果希望同时满足 fork() 和 Boost.Asio 的要求,那么一种解决方案是在守护进程仍然是单线程时 fork 守护进程。当父进程通过进程间通信告知子进程执行 fork()exec() 时,子进程将保持单线程并执行 fork()exec()this 答案中建议并演示了此解决方案。