2012-09-14 2 views
6

소켓 프로그래밍 (유닉스 환경에서)에서 몇 가지 실험을하고 있습니다. 내가 시도한 것은독립적 인 프로세스 간 소켓 핸들 전송

  1. 클라이언트가 서버에 요청을 보냅니다.
  2. 서버가 클라이언트 소켓을 작업자에게 보내야합니다 (독립 프로세스)
  3. 작업자가 클라이언트에 응답해야합니다.

이것이 가능합니까?

이 시나리오는 Worker가 Server의 하위 노드 인 경우 작동합니다.

서버와 작업자가 독립적 인 프로세스 인 경우이 방법이 효과가 있습니까? 그렇다면 누군가 내게이 아이디어를 줄 수 있습니까? 이 유형의 시나리오에 사용할 수있는 샘플이 있습니까?

답변

11

The Linux Programming Interface 책에는 Unix 도메인 소켓을 사용하여 관련이없는 프로세스간에 파일 설명자인 sendingreceiving이 모두 들어 있습니다.

재미있게, 나는 처음부터 내 자신의 예제를 썼습니다. 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을 재 시도하지 않으면) 프로세스가 깨지기 쉽고 조건이 약간 변경되어 충돌합니다. 튼튼하라. 그렇게 어렵지는 않으며 결과는 훨씬 더 많은 사용자/시스템 관리자에게 친숙합니다.

코드가 재미 있기를 바랍니다. 세부 사항에 대해 궁금한 점이 있으면 자세히 설명 드리겠습니다. 아주 짧은 시간에 처음부터 썼다는 것을 기억하십시오. 그것은 단지 간단한 예제로 의도 된 것입니다. 많은 개선의 여지가입니다.

+1

고마워 ..이게 내가 찾는거야. – Suyambu

2

UNIX 소켓은 프로세스간에 파일 설명자를 전달하는 데 사용됩니다.

+2

일부 도움말은 [this site] (http://infohost.nmt.edu/~eweiss/222_book/222_book/0201433079/ch17lev1sec4.html#ch17lev2sec6)를 참조하십시오. –

1

this post에 따르면 가능해야합니다. 작업자 프로세스가 소켓 핸들을 알 수 있도록하기 위해 어떤 방법 (파이프 또는 소켓이 마음에와 있습니다)이 필요합니다.

유감스럽게도 저는 유닉스 프로그래밍 경험이 없으므로 더 구체적인 정보를 드릴 수는 없습니다.

+0

커맨드 라인에서 소켓 fd를 넘겨 줄 수 있습니다 : 그것은 단지'int'입니다! 그러나'exec '없이'fork'만하면, 아무 것도 전달할 필요가 없습니다 - 같은 프로그램 (그러나 다른 프로세스). – cdarke

+1

그러나 그는 구체적으로 작업자 프로세스가 서버와 독립적이며 자식 프로세스가 아닌 솔루션을 요청했습니다. 다시, 나는 unix/linux에 대해 많이 알지 못하지만 fork()가 자식 프로세스를 생성한다고 생각합니다. – Wutz