【问题标题】:Avoid socket inheritance when starting Linux service from C++ application从 C++ 应用程序启动 Linux 服务时避免套接字继承
【发布时间】:2015-05-08 03:22:26
【问题描述】:

我有一个 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 + execvexecvenull 环境。结果总是一样或更糟。 我最后的希望是 Linux setsid 命令,但它也没有奏效。

任何帮助将不胜感激。 问候, 一月

【问题讨论】:

    标签: c++ linux sockets boost boost-asio


    【解决方案1】:

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

    如果您正在使用一些更高级别的库,请检查是否有某种方法可以让它传递此标志,或者在创建描述符后执行fcntl(sock_fd, F_SETFD, fcntl(sock_fd, F_GETFD) | FD_CLOEXEC) 在描述符上设置 close-on-exec 标志,如果你可以得到它。 (不过,fcntl(2) 方法在多线程环境中可能会很活跃,因为某些线程可以在创建套接字的点和设置 FD_CLOEXEC 之间的程序 exec(3)。)

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

    【讨论】:

    • 还请注意,在许多情况下,您无法控制所有文件描述符以及它们是否使用 SOCK_CLOEXEC 创建,在这种情况下,您可以运行一个循环来关闭()直到 sysconf 的所有描述符(_SC_OPEN_MAX),在 fork() 之后(如果你想单独留下 stdin/out/err,可能从 fd 3 开始,或者将它们重定向到 /dev/null)
    • 目前,并非所有文件描述符都可以通过设置FD_CLOEXEC 自动创建。在多线程应用程序中,另一个线程可能会在创建文件描述符之后但在设置FD_CLOEXEC 之前分叉一个进程。因此,子进程可能会意外继承文件描述符。
    • stackoverflow.com/questions/899038/… 提供了一些额外的见解。关闭描述符以避免继承。
    • 谢谢。这有很大帮助。对于boost::asio::ip::tcp::acceptor,我必须使用native_handler() 作为fcntl 的文件描述符。正如您所提到的,尽管将CLOEXEC 设置为boost::asio::ip::tcp::socket,已建立的连接仍保持打开状态。所以我必须手动关闭它们。子进程中的一些阻塞信号仍然存在问题,但我想我会为此另开一个线程。
    • @jgaida 似乎您未能创建该问题,但答案很可能是boost.org/doc/libs/1_63_0/doc/html/…
    【解决方案2】:

    Boost.Asio supports the fork() system call:

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

      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 的公共 API 访问的文件描述符是程序的责任。例如,如果父进程有一个打开的acceptor,那么子进程需要在生成自己的子进程或替换进程映像之前显式调用acceptor::close()。分叉支持文档指出:

      请注意,任何可通过 Boost.Asio 的公共 API 访问的文件描述符(例如,basic_socket<>posix::stream_descriptor 的底层描述符等)在分叉期间都不会更改。根据需要管理这些是程序的责任。

    • Boost.Asio 的 fork 支持对于多线程进程是不安全的。对于多线程进程,fork() 声明子进程只能在fork()exec() 函数之一之间调用异步信号安全操作。 io_service::notify_fork() 不做此保证,当前的 implementation 调用非异步信号安全操作。

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

    【讨论】:

    • 谢谢@tanner-sansbury,我使用了notify_fork,因为我认为这是正确的方法。虽然,对于套接字没有区别。我认为最好是在开始多线程之前分叉一个子进程,但对于我想做的“简单”事情来说似乎有点太复杂了。
    【解决方案3】:

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

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

    使用 systemd 的好处是您的服务不需要以 root 身份运行以便能够启动另一个服务,并且服务进程彼此完全隔离(没有任何继承 forking 时的任何东西) .

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-30
      • 2016-04-10
      • 1970-01-01
      • 2014-10-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多