在不相关的进程之间共享套接字,例如 systemd
Share socket between unrelated processes like systemd
如何做有多个问答,但两个进程必须配合
- Can I open a socket and pass it to another process in Linux
- Share socket (listen) between unrelated processes
- Portable way to pass file descriptor between different processes
等等
在systemd
中,有一个特征socket activation,你只是在你的进程中打开并准备了文件描述符,没有任何合作。您可以只使用文件描述符 3
(SD_LISTEN_FDS_START
) 并且它已被 systemd 激活套接字。
systemd
是怎么做到的?我找不到任何相关的源代码。
编辑:
我知道如何编写 systemd 套接字激活服务,但我对从 systemd
角度将文件描述符传递到我的服务的过程感兴趣。
例如如果我想编写自己的套接字激活器,它的行为与 systemd 完全相同。
systemd
与共享套接字的进程并非无关。 systemd
启动并监督整个系统,因此它可以在 exec()
期间轻松传递套接字文件描述符。 systemd
代表服务进行监听,只要有连接进来,就会生成相应服务的实例。 Here 是实现:
int main(int argc, char **argv, char **envp) {
int r, n;
int epoll_fd = -1;
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r <= 0)
return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
r = install_chld_handler();
if (r < 0)
return EXIT_FAILURE;
n = open_sockets(&epoll_fd, arg_accept);
if (n < 0)
return EXIT_FAILURE;
if (n == 0) {
log_error("No sockets to listen on specified or passed in.");
return EXIT_FAILURE;
}
for (;;) {
struct epoll_event event;
r = epoll_wait(epoll_fd, &event, 1, -1);
if (r < 0) {
if (errno == EINTR)
continue;
log_error_errno(errno, "epoll_wait() failed: %m");
return EXIT_FAILURE;
}
log_info("Communication attempt on fd %i.", event.data.fd);
if (arg_accept) {
r = do_accept(argv[optind], argv + optind, envp, event.data.fd);
if (r < 0)
return EXIT_FAILURE;
} else
break;
}
...
}
一旦连接进来,它会调用do_accept()
:
static int do_accept(const char* name, char **argv, char **envp, int fd) {
_cleanup_free_ char *local = NULL, *peer = NULL;
_cleanup_close_ int fd_accepted = -1;
fd_accepted = accept4(fd, NULL, NULL, 0);
if (fd_accepted < 0)
return log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd);
getsockname_pretty(fd_accepted, &local);
getpeername_pretty(fd_accepted, true, &peer);
log_info("Connection from %s to %s", strna(peer), strna(local));
return fork_and_exec_process(name, argv, envp, fd_accepted);
}
最后,it calls execvpe(name, argv, envp);
and wrap the fd up in envp
. There is a trick in it, if fd_accepted
is not equal to SD_LISTEN_FDS_START
, it call dup2()
使 SD_LISTEN_FDS_START
成为 fd_accepted
的副本:
if (start_fd != SD_LISTEN_FDS_START) {
assert(n_fds == 1);
r = dup2(start_fd, SD_LISTEN_FDS_START);
if (r < 0)
return log_error_errno(errno, "Failed to dup connection: %m");
safe_close(start_fd);
start_fd = SD_LISTEN_FDS_START;
}
所以你可以在你的应用程序中像这样使用文件描述符 3,sd_listen_fds
将解析从 envp
:
传递的环境变量 LISTEN_FDS
int listen_sock;
int fd_count = sd_listen_fds(0);
if (fd_count == 1) { // assume one socket only
listen_sock = SD_LISTEN_FDS_START; // SD_LISTEN_FDS_START is a macro defined to 3
} else {
// error
}
struct sockaddr addr;
socklen_t addrlen;
while (int client_sock = accept(listen_sock, &addr, &addrlen)) {
// do something
}
如何做有多个问答,但两个进程必须配合
- Can I open a socket and pass it to another process in Linux
- Share socket (listen) between unrelated processes
- Portable way to pass file descriptor between different processes
等等
在systemd
中,有一个特征socket activation,你只是在你的进程中打开并准备了文件描述符,没有任何合作。您可以只使用文件描述符 3
(SD_LISTEN_FDS_START
) 并且它已被 systemd 激活套接字。
systemd
是怎么做到的?我找不到任何相关的源代码。
编辑:
我知道如何编写 systemd 套接字激活服务,但我对从 systemd
角度将文件描述符传递到我的服务的过程感兴趣。
例如如果我想编写自己的套接字激活器,它的行为与 systemd 完全相同。
systemd
与共享套接字的进程并非无关。 systemd
启动并监督整个系统,因此它可以在 exec()
期间轻松传递套接字文件描述符。 systemd
代表服务进行监听,只要有连接进来,就会生成相应服务的实例。 Here 是实现:
int main(int argc, char **argv, char **envp) {
int r, n;
int epoll_fd = -1;
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r <= 0)
return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
r = install_chld_handler();
if (r < 0)
return EXIT_FAILURE;
n = open_sockets(&epoll_fd, arg_accept);
if (n < 0)
return EXIT_FAILURE;
if (n == 0) {
log_error("No sockets to listen on specified or passed in.");
return EXIT_FAILURE;
}
for (;;) {
struct epoll_event event;
r = epoll_wait(epoll_fd, &event, 1, -1);
if (r < 0) {
if (errno == EINTR)
continue;
log_error_errno(errno, "epoll_wait() failed: %m");
return EXIT_FAILURE;
}
log_info("Communication attempt on fd %i.", event.data.fd);
if (arg_accept) {
r = do_accept(argv[optind], argv + optind, envp, event.data.fd);
if (r < 0)
return EXIT_FAILURE;
} else
break;
}
...
}
一旦连接进来,它会调用do_accept()
:
static int do_accept(const char* name, char **argv, char **envp, int fd) {
_cleanup_free_ char *local = NULL, *peer = NULL;
_cleanup_close_ int fd_accepted = -1;
fd_accepted = accept4(fd, NULL, NULL, 0);
if (fd_accepted < 0)
return log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd);
getsockname_pretty(fd_accepted, &local);
getpeername_pretty(fd_accepted, true, &peer);
log_info("Connection from %s to %s", strna(peer), strna(local));
return fork_and_exec_process(name, argv, envp, fd_accepted);
}
最后,it calls execvpe(name, argv, envp);
and wrap the fd up in envp
. There is a trick in it, if fd_accepted
is not equal to SD_LISTEN_FDS_START
, it call dup2()
使 SD_LISTEN_FDS_START
成为 fd_accepted
的副本:
if (start_fd != SD_LISTEN_FDS_START) {
assert(n_fds == 1);
r = dup2(start_fd, SD_LISTEN_FDS_START);
if (r < 0)
return log_error_errno(errno, "Failed to dup connection: %m");
safe_close(start_fd);
start_fd = SD_LISTEN_FDS_START;
}
所以你可以在你的应用程序中像这样使用文件描述符 3,sd_listen_fds
将解析从 envp
:
LISTEN_FDS
int listen_sock;
int fd_count = sd_listen_fds(0);
if (fd_count == 1) { // assume one socket only
listen_sock = SD_LISTEN_FDS_START; // SD_LISTEN_FDS_START is a macro defined to 3
} else {
// error
}
struct sockaddr addr;
socklen_t addrlen;
while (int client_sock = accept(listen_sock, &addr, &addrlen)) {
// do something
}