The Linux Programming Interface 책에는 Unix 도메인 소켓을 사용하여 관련이없는 프로세스간에 파일 설명자인 sending과 receiving이 모두 들어 있습니다.
재미있게, 나는 처음부터 내 자신의 예제를 썼습니다. 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>
/* How many concurrent pending connections are allowed */
#define LISTEN_BACKLOG 32
/* Unix domain socket path length (including NUL byte) */
#ifndef UNIX_PATH_LEN
#define UNIX_PATH_LEN 108
#endif
/* Flag to indicate we have received a shutdown request. */
volatile sig_atomic_t done = 0;
/* Shutdown request signal handler, of the basic type. */
void handle_done_signal(int signum)
{
if (!done)
done = signum;
return;
}
/* Install shutdown request signal handler on signal signum. */
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;
}
/* Return empty, -, and * as NULL, so users can use that
* to bind the server to the wildcard address.
*/
char *wildcard(char *address)
{
/* NULL? */
if (!address)
return NULL;
/* Empty? */
if (!address[0])
return NULL;
/* - or ? or * or : */
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;
}
/* Handle HUP, INT, PIPE, and TERM signals,
* so when the user presses Ctrl-C, the worker process cannot be contacted,
* or the user sends a HUP or TERM signal, this server closes down cleanly. */
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;
}
/* Unix domain socket to the worker */
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);
/* Note: the terminating NUL byte was set by memset(&worker, 0, sizeof worker) above. */
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;
}
/* Initialize the address info hints */
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* Stream socket */
hints.ai_flags = AI_PASSIVE /* Wildcard ADDRESS */
| AI_ADDRCONFIG /* Only return IPv4/IPv6 if available locally */
| AI_NUMERICSERV /* Port must be a number */
;
hints.ai_protocol = 0; /* Any protocol */
/* Obtain the chain of possible addresses and ports to bind to */
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;
}
/* Bind to the first working entry in the chain */
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;
}
/* Bind successfully */
break;
}
/* Discard the chain, as we don't need it anymore.
* Note: curr is no longer valid after this. */
freeaddrinfo(list);
/* Failed to bind? */
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) {
/* Did we just receive a signal? */
if (errno == EINTR)
continue;
/* Report a connection failure. */
printf("Failed to accept a connection: %s\n", strerror(errno));
fflush(stdout);
continue;
}
/* Construct the message to the worker process. */
memset(&connhdr, 0, sizeof connhdr);
memset(&conniov, 0, sizeof conniov);
memset(&connbuf, 0, sizeof connbuf);
conniov.iov_base = conndata; /* Data payload to send */
conniov.iov_len = 1; /* We send just one (dummy) byte, */
conndata[0] = 0; /* a zero. */
/* Construct the message (header) */
connhdr.msg_name = NULL; /* No optional address */
connhdr.msg_namelen = 0; /* No optional address */
connhdr.msg_iov = &conniov; /* Normal payload - at least one byte */
connhdr.msg_iovlen = 1; /* Only one vector in conniov */
connhdr.msg_control = connbuf; /* Ancillary data */
connhdr.msg_controllen = sizeof connbuf;
/* Construct the ancillary data needed to pass one descriptor. */
connmsg = CMSG_FIRSTHDR(&connhdr);
connmsg->cmsg_level = SOL_SOCKET;
connmsg->cmsg_type = SCM_RIGHTS;
connmsg->cmsg_len = CMSG_LEN(sizeof (int));
/* Copy the descriptor to the ancillary data. */
memcpy(CMSG_DATA(connmsg), &connfd, sizeof (int));
/* Update the message to reflect the ancillary data length */
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);
/* Lost connection to the other end? */
if (!done) {
if (errno == EPIPE)
done = SIGPIPE;
else
done = -1;
}
printf("Cannot pass connection to worker: %s.\n", errmsg);
fflush(stdout);
close(connfd);
/* Break main loop. */
break;
}
/* Since the descriptor has been transferred to the other process,
* we can close our end. */
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);
}
/* Shutdown. */
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>
/* How many concurrent pending connections are allowed */
#define LISTEN_BACKLOG 32
/* Unix domain socket path length (including NUL byte) */
#ifndef UNIX_PATH_LEN
#define UNIX_PATH_LEN 108
#endif
/* Flag to indicate we have received a shutdown request. */
volatile sig_atomic_t done = 0;
/* Shutdown request signal handler, of the basic type. */
void handle_done_signal(int signum)
{
if (!done)
done = signum;
return;
}
/* Install shutdown request signal handler on signal signum. */
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;
}
/* Helper function to duplicate file descriptors.
* Returns 0 if success, errno error code otherwise.
*/
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;
}
/* Handle HUP, INT, PIPE, and TERM signals,
* so when the user presses Ctrl-C, the worker process cannot be contacted,
* or the user sends a HUP or TERM signal, this server closes down cleanly. */
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;
}
/* Unix domain socket */
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);
/* Note: the terminating NUL byte was set by memset(&worker, 0, sizeof worker) above. */
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; /* Just one byte */
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) {
/* This is the child process. */
close(workerfd);
close(serverfd);
if (copy_fd(clientfd, STDIN_FILENO) ||
copy_fd(clientfd, STDOUT_FILENO) ||
copy_fd(clientfd, STDERR_FILENO))
return 126; /* Exits the client */
if (clientfd != STDIN_FILENO &&
clientfd != STDOUT_FILENO &&
clientfd != STDERR_FILENO)
close(clientfd);
execvp(argv[2], argv + 2);
return 127; /* Exits the client */
}
printf("Done.\n");
fflush(stdout);
close(clientfd);
}
close(serverfd);
printf("Closed connection to server.\n");
fflush(stdout);
}
/* Shutdown. */
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
은 표준 연결, 출력 및 오류가 직접 inetd
또는 xinetd
과 같은 네트워크 클라이언트에 직접 연결되어있는 각 연결에 대해 실행되는 명령 (셸 명령, 실행 파일 아님)입니다.
예를 들어 다음을 통해 연결을 테스트 할 수 있습니다.
nc 127.0.0.1 8000
또는
telnet 127.0.0.1 8000
위 /bin/date
명령은 표준 출력에 현재 날짜 바로 출력하지만 조금 영리한 작업자 명령을 사용하는 경우,
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 주소 및 포트로 들어오는 연결을 수신 대기합니다. 연결이되면 연결된 파일 설명자를 명령 줄 (connection
)에 지정된 Unix 도메인 소켓을 통해 worker.c
프로세스 위로 전송하고 자체 복사본을 닫은 다음 새 연결을 기다리는 상태로 돌아갑니다. 서버가 작업자와의 연결을 잃으면 작업이 중단됩니다. 항상 ./server
앞에 ./worker
을 시작하려고합니다. 당신이 (당신은 별도의 단자 나 껍질에 포 그라운드에서 명령을 실행하는 경우, Ctrl-C를) 그들에게 HUP 또는 INT 신호를 전송하여 종료를 알 수 있도록
모두
server.c
및 worker.c
간단한 신호 처리기를 설치합니다. 합리적인 오류 검사도 있으므로 종료 할 때 정확한 이유를 알려줍니다. 솔직히 말해서, 때때로 EINTR 에러를받는 경우가 있기 때문에 올바르게 처리했기 때문에 (exit를하지 않는 한 관련 syscall을 재 시도하지 않으면) 프로세스가 깨지기 쉽고 조건이 약간 변경되어 충돌합니다. 튼튼하라. 그렇게 어렵지는 않으며 결과는 훨씬 더 많은 사용자/시스템 관리자에게 친숙합니다.
코드가 재미 있기를 바랍니다. 세부 사항에 대해 궁금한 점이 있으면 자세히 설명 드리겠습니다. 아주 짧은 시간에 처음부터 썼다는 것을 기억하십시오. 그것은 단지 간단한 예제로 의도 된 것입니다. 많은 개선의 여지가입니다.
고마워 ..이게 내가 찾는거야. – Suyambu