Linux编程接口这本书中有关于在不相关进程之间传递文件描述符的例子,包括发送和接收,使用Unix域套接字。
出于兴趣,我自己从零开始编写了一些示例。其中server.c
为:
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define LISTEN_BACKLOG 32
#ifndef UNIX_PATH_LEN
#define UNIX_PATH_LEN 108
#endif
volatile sig_atomic_t done = 0;
void handle_done_signal(int signum)
{
if (!done)
done = signum;
return;
}
int set_done_signal(const int signum)
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = handle_done_signal;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
else
return 0;
}
char *wildcard(char *address)
{
if (!address)
return NULL;
if (!address[0])
return NULL;
if (address[0] == '-' || address[0] == '?' ||
address[0] == '*' || address[0] == ':')
return NULL;
return address;
}
int main(int argc, char *argv[])
{
struct addrinfo hints;
struct addrinfo *list, *curr;
int listenfd, failure;
struct sockaddr_un worker;
int workerfd, workerpathlen;
struct sockaddr_in6 conn;
socklen_t connlen;
struct msghdr connhdr;
struct iovec conniov;
struct cmsghdr *connmsg;
char conndata[1];
char connbuf[CMSG_SPACE(sizeof (int))];
int connfd;
int result;
ssize_t written;
if (argc != 4) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s ADDRESS PORT WORKER\n", argv[0]);
fprintf(stderr, "This creates a server that binds to ADDRESS and PORT,\n");
fprintf(stderr, "and passes each connection to a separate unrelated\n");
fprintf(stderr, "process using an Unix domain socket at WORKER.\n");
fprintf(stderr, "\n");
return (argc == 1) ? 0 : 1;
}
if (set_done_signal(SIGINT) ||
set_done_signal(SIGHUP) ||
set_done_signal(SIGPIPE) ||
set_done_signal(SIGTERM)) {
fprintf(stderr, "Error: Cannot install signal handlers.\n");
return 1;
}
memset(&worker, 0, sizeof worker);
worker.sun_family = AF_UNIX;
workerpathlen = strlen(argv[3]);
if (workerpathlen < 1) {
fprintf(stderr, "Worker Unix domain socket path cannot be empty.\n");
return 1;
} else
if (workerpathlen >= UNIX_PATH_LEN) {
fprintf(stderr, "%s: Worker Unix domain socket path is too long.\n", argv[3]);
return 1;
}
memcpy(&worker.sun_path, argv[3], workerpathlen);
workerfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (workerfd == -1) {
fprintf(stderr, "Cannot create an Unix domain socket: %s.\n", strerror(errno));
return 1;
}
if (connect(workerfd, (const struct sockaddr *)(&worker), (socklen_t)sizeof worker) == -1) {
fprintf(stderr, "Cannot connect to %s: %s.\n", argv[3], strerror(errno));
close(workerfd);
return 1;
}
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE
| AI_ADDRCONFIG
| AI_NUMERICSERV
;
hints.ai_protocol = 0;
result = getaddrinfo(wildcard(argv[1]), argv[2], &hints, &list);
if (result) {
fprintf(stderr, "%s %s: %s.\n", argv[1], argv[2], gai_strerror(result));
close(workerfd);
return 1;
}
listenfd = -1;
failure = EINVAL;
for (curr = list; curr != NULL; curr = curr->ai_next) {
listenfd = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol);
if (listenfd == -1)
continue;
if (bind(listenfd, curr->ai_addr, curr->ai_addrlen) == -1) {
if (!failure)
failure = errno;
close(listenfd);
listenfd = -1;
continue;
}
break;
}
freeaddrinfo(list);
if (listenfd == -1) {
fprintf(stderr, "Cannot bind to %s port %s: %s.\n", argv[1], argv[2], strerror(failure));
close(workerfd);
return 1;
}
if (listen(listenfd, LISTEN_BACKLOG) == -1) {
fprintf(stderr, "Cannot listen for incoming connections to %s port %s: %s.\n", argv[1], argv[2], strerror(errno));
close(listenfd);
close(workerfd);
return 1;
}
printf("Now waiting for incoming connections to %s port %s\n", argv[1], argv[2]);
fflush(stdout);
while (!done) {
memset(&conn, 0, sizeof conn);
connlen = sizeof conn;
connfd = accept(listenfd, (struct sockaddr *)&conn, &connlen);
if (connfd == -1) {
if (errno == EINTR)
continue;
printf("Failed to accept a connection: %s\n", strerror(errno));
fflush(stdout);
continue;
}
memset(&connhdr, 0, sizeof connhdr);
memset(&conniov, 0, sizeof conniov);
memset(&connbuf, 0, sizeof connbuf);
conniov.iov_base = conndata;
conniov.iov_len = 1;
conndata[0] = 0;
connhdr.msg_name = NULL;
connhdr.msg_namelen = 0;
connhdr.msg_iov = &conniov;
connhdr.msg_iovlen = 1;
connhdr.msg_control = connbuf;
connhdr.msg_controllen = sizeof connbuf;
connmsg = CMSG_FIRSTHDR(&connhdr);
connmsg->cmsg_level = SOL_SOCKET;
connmsg->cmsg_type = SCM_RIGHTS;
connmsg->cmsg_len = CMSG_LEN(sizeof (int));
memcpy(CMSG_DATA(connmsg), &connfd, sizeof (int));
connhdr.msg_controllen = connmsg->cmsg_len;
do {
written = sendmsg(workerfd, &connhdr, MSG_NOSIGNAL);
} while (written == (ssize_t)-1 && errno == EINTR);
if (written == (ssize_t)-1) {
const char *const errmsg = strerror(errno);
if (!done) {
if (errno == EPIPE)
done = SIGPIPE;
else
done = -1;
}
printf("Cannot pass connection to worker: %s.\n", errmsg);
fflush(stdout);
close(connfd);
break;
}
do {
result = close(connfd);
} while (result == -1 && errno == EINTR);
if (result == -1)
printf("Error closing leftover connection descriptor: %s.\n", strerror(errno));
printf("Connection transferred to the worker process.\n");
fflush(stdout);
}
close(listenfd);
close(workerfd);
switch (done) {
case SIGTERM:
printf("Terminated.\n");
break;
case SIGPIPE:
printf("Lost connection.\n");
break;
case SIGHUP:
printf("Hanging up.\n");
break;
case SIGINT:
printf("Interrupted; exiting.\n");
break;
default:
printf("Exiting.\n");
}
return 0;
}
和 worker.c
:
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define LISTEN_BACKLOG 32
#ifndef UNIX_PATH_LEN
#define UNIX_PATH_LEN 108
#endif
volatile sig_atomic_t done = 0;
void handle_done_signal(int signum)
{
if (!done)
done = signum;
return;
}
int set_done_signal(const int signum)
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = handle_done_signal;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
else
return 0;
}
static int copy_fd(const int fromfd, const int tofd)
{
int result;
if (fromfd == tofd)
return 0;
if (fromfd == -1 || tofd == -1)
return errno = EINVAL;
do {
result = dup2(fromfd, tofd);
} while (result == -1 && errno == EINTR);
if (result == -1)
return errno;
return 0;
}
int main(int argc, char *argv[])
{
struct sockaddr_un worker;
int workerfd, workerpathlen;
int serverfd, clientfd;
pid_t child;
struct msghdr msghdr;
struct iovec msgiov;
struct cmsghdr *cmsg;
char data[1];
char ancillary[CMSG_SPACE(sizeof (int))];
ssize_t received;
if (argc < 3) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s WORKER COMMAND [ ARGS .. ]\n", argv[0]);
fprintf(stderr, "This creates a worker that receives connections\n");
fprintf(stderr, "from Unix domain socket WORKER.\n");
fprintf(stderr, "Each connection is served by COMMAND, with the\n");
fprintf(stderr, "connection connected to its standard input and output.\n");
fprintf(stderr, "\n");
return (argc == 1) ? 0 : 1;
}
if (set_done_signal(SIGINT) ||
set_done_signal(SIGHUP) ||
set_done_signal(SIGPIPE) ||
set_done_signal(SIGTERM)) {
fprintf(stderr, "Error: Cannot install signal handlers.\n");
return 1;
}
memset(&worker, 0, sizeof worker);
worker.sun_family = AF_UNIX;
workerpathlen = strlen(argv[1]);
if (workerpathlen < 1) {
fprintf(stderr, "Worker Unix domain socket path cannot be empty.\n");
return 1;
} else
if (workerpathlen >= UNIX_PATH_LEN) {
fprintf(stderr, "%s: Worker Unix domain socket path is too long.\n", argv[1]);
return 1;
}
memcpy(&worker.sun_path, argv[1], workerpathlen);
workerfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (workerfd == -1) {
fprintf(stderr, "Cannot create an Unix domain socket: %s.\n", strerror(errno));
return 1;
}
if (bind(workerfd, (const struct sockaddr *)(&worker), (socklen_t)sizeof worker) == -1) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
close(workerfd);
return 1;
}
if (listen(workerfd, LISTEN_BACKLOG) == -1) {
fprintf(stderr, "%s: Cannot listen for messages: %s.\n", argv[1], strerror(errno));
close(workerfd);
return 1;
}
printf("Listening for descriptors on %s.\n", argv[1]);
fflush(stdout);
while (!done) {
serverfd = accept(workerfd, NULL, NULL);
if (serverfd == -1) {
if (errno == EINTR)
continue;
printf("Failed to accept a connection from the server: %s.\n", strerror(errno));
fflush(stdout);
continue;
}
printf("Connection from the server.\n");
fflush(stdout);
while (!done && serverfd != -1) {
memset(&msghdr, 0, sizeof msghdr);
memset(&msgiov, 0, sizeof msgiov);
msghdr.msg_name = NULL;
msghdr.msg_namelen = 0;
msghdr.msg_control = &ancillary;
msghdr.msg_controllen = sizeof ancillary;
cmsg = CMSG_FIRSTHDR(&msghdr);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof (int));
msghdr.msg_iov = &msgiov;
msghdr.msg_iovlen = 1;
msgiov.iov_base = &data;
msgiov.iov_len = 1;
received = recvmsg(serverfd, &msghdr, 0);
if (received == (ssize_t)-1) {
if (errno == EINTR)
continue;
printf("Error receiving a message from server: %s.\n", strerror(errno));
fflush(stdout);
break;
}
cmsg = CMSG_FIRSTHDR(&msghdr);
if (!cmsg || cmsg->cmsg_len != CMSG_LEN(sizeof (int))) {
printf("Received a bad message from server.\n");
fflush(stdout);
break;
}
memcpy(&clientfd, CMSG_DATA(cmsg), sizeof (int));
printf("Executing command with descriptor %d: ", clientfd);
fflush(stdout);
child = fork();
if (child == (pid_t)-1) {
printf("Fork failed: %s.\n", strerror(errno));
fflush(stdout);
close(clientfd);
break;
}
if (!child) {
close(workerfd);
close(serverfd);
if (copy_fd(clientfd, STDIN_FILENO) ||
copy_fd(clientfd, STDOUT_FILENO) ||
copy_fd(clientfd, STDERR_FILENO))
return 126;
if (clientfd != STDIN_FILENO &&
clientfd != STDOUT_FILENO &&
clientfd != STDERR_FILENO)
close(clientfd);
execvp(argv[2], argv + 2);
return 127;
}
printf("Done.\n");
fflush(stdout);
close(clientfd);
}
close(serverfd);
printf("Closed connection to server.\n");
fflush(stdout);
}
close(workerfd);
switch (done) {
case SIGTERM:
printf("Terminated.\n");
break;
case SIGPIPE:
printf("Lost connection.\n");
break;
case SIGHUP:
printf("Hanging up.\n");
break;
case SIGINT:
printf("Interrupted; exiting.\n");
break;
default:
printf("Exiting.\n");
}
return 0;
}
您可以使用编译它们
gcc -W -Wall -O3 worker.c -o worker
gcc -W -Wall -O3 server.c -o server
并使用例如运行。
rm -f connection
./worker connection /bin/date &
./server 127.0.0.1 8000 connection &
如您所见,
./worker
和
./server
进程是完全独立的。我建议从不同的窗口开始它们(在命令行末尾省略
&
,否则会在后台运行命令)。
connection
是用于传输网络连接文件描述符的Unix域套接字的路径或名称。
/bin/date
是一个命令(不是shell命令,是可执行文件),将为每个连接执行,并且标准输入、输出和错误直接连接到网络客户端,非常类似于
inetd
或
xinetd
,只是更加简单。
您可以通过例如以下方式测试连接:
nc 127.0.0.1 8000
或者
telnet 127.0.0.1 8000
上述的
/bin/date
命令只会将当前日期输出到标准输出,但是如果你使用更加聪明的worker命令,比如说:
rm -f connection
./worker connection printf 'HTTP/1.0 200 Ok\r\nConnection: close\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 67\r\n\r\n<html><head><title>Works</title></head><body>Works!</body></html>\r\n'
您可以使用浏览器 (http://127.0.0.1:8000/
) 进行测试。
设计如下:worker.c
监听 Unix 域套接字 (connection
在所有上述示例命令的当前工作目录中)。 它首先接受连接 (来自单个服务器),然后期望每个传入的字节都与包含指向客户端连接的文件描述符的 SCM_RIGHTS
辅助数据相关联。 如果出现问题或连接断开,则它会返回到等待从服务器获取新连接的状态。 如果它收到客户端描述符,则它会分叉一个子进程,将其标准输入、输出和错误重定向到客户端描述符,并在 ./worker
命令行上执行指定的命令。 父进程关闭其客户端描述符的副本,并返回到等待新的客户端描述符的状态。
server.c
监听其命令行中指定的 IPv4 或 IPv6 地址和端口的传入连接。当它接收到一个连接时,它通过命令行上指定的 Unix 域套接字(
connection
)将已连接的文件描述符传输到上面的
worker.c
进程,关闭自己的副本,并等待新的连接。请注意,如果服务器失去与工作进程的连接,则会中止;您需要在启动
./server
之前始终启动
./worker
。
server.c
和 worker.c
都安装了简单的信号处理程序,以便您可以通过发送 HUP 或 INT 信号(如果在单独的终端或 shell 中运行命令,则为 Ctrl-C)告诉它们退出。它们还具有合理的错误检查,因此当它们退出时,它们会告诉您出错的原因。老实说,我这样做是因为这样你会偶尔收到 EINTR 错误,除非你正确地处理它们(重试相关的系统调用,除非被要求退出),否则你的进程将很脆弱,并且在条件发生最轻微的变化时就会崩溃。要坚韧不拔;这并不难,而且结果更加用户友好/系统管理员友好。
我希望您能对这段代码感兴趣。如果您对细节有任何问题,我很乐意进行详细说明。请记住,我在很短的时间内从头开始编写了它,它仅作为一个简单的示例。还有
很多改进的空间。