2012-09-14 13 views
6

Sto facendo alcuni esperimenti in programmazione socket (in ambiente UNIX). Quello che sto cercando èTrasferimento handle socket tra processi indipendenti

  1. Il client invia richiesta al server.
  2. Il server deve inviare il socket client a Worker (processo indipendente)
  3. Il lavoratore deve rispondere al client.

È possibile?

Questo scenario funziona se Worker è figlio di Server.

Se Server e Worker sono processi indipendenti, funziona? Se sì, qualcuno può darmi qualche idea su questo? Esistono campioni disponibili per questo tipo di scenario?

risposta

11

The Linux Programming Interface Il libro contiene esempi per entrambi i descrittori di file sending e receiving tra processi non correlati, utilizzando un socket di dominio Unix.

Per divertimento, ho scritto i miei esempi da zero. 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; 
} 

e 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; 
} 

è possibile compilare utilizzando

gcc -W -Wall -O3 worker.c -o worker 
gcc -W -Wall -O3 server.c -o server 

ed eseguire utilizzando per esempio

rm -f connection 
./worker connection /bin/date & 
./server 127.0.0.1 8000 connection & 

Come si può vedere, le ./worker e ./server processi sono completamente separati. Consiglio di avviarli da finestre diverse (lasciando fuori & alla fine delle righe di comando, che altrimenti esegue i comandi sullo sfondo). Il connection è il percorso o il nome del socket del dominio Unix utilizzato per trasferire il descrittore del file di connessione di rete. Il /bin/date è un comando (non un comando shell, un eseguibile) che verrà eseguito per ogni connessione, con input, output ed errore standard collegati direttamente al client di rete - molto simile a inetd o xinetd, solo ossa nude.

È possibile testare la connessione tramite ad es.

nc 127.0.0.1 8000 

o

telnet 127.0.0.1 8000 

Il comando sopra /bin/date sarà solo in uscita la data corrente standard output, ma se si utilizza un comando operaio intelligente po ', diciamo

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' 

è possibile utilizzare il tuo browser (http://127.0.0.1:8000/) da testare.

Il progetto è tale che worker.c ascolta un socket di dominio Unix (connection nella directory di lavoro corrente in tutti i comandi di esempio sopra). Accetta prima una connessione (da un singolo server), quindi si aspetta che ogni byte in entrata sia associato ai dati ausiliari SCM_RIGHTS contenenti il ​​descrittore di file che fa riferimento alla connessione client. Se si verifica un problema o la connessione viene interrotta, torna in attesa di una nuova connessione da un server. Se riceve un descrittore del client, forca un processo figlio, reindirizza il suo input, output ed errore standard al descrittore del client ed esegue il comando specificato sulla riga di comando ./worker. Il processo genitore chiude la sua copia del descrittore del client e torna ad aspettarne uno nuovo.

server.c ascolta le connessioni in entrata per l'indirizzo IPv4 o IPv6 e la porta specificata sulla riga di comando. Quando riceve una connessione, trasferisce il descrittore di file collegato al processo worker.c superiore tramite il socket dominio Unix specificato nella riga di comando (connection), chiude la propria copia e torna in attesa di una nuova connessione. Si noti che se il server perde la connessione al lavoratore, si interrompe; ti consigliamo di iniziare ./worker sempre prima dello ./server.

Sia server.c e worker.c installare semplici gestori di segnale in modo che si può dire loro di uscire inviando loro un segnale HUP o INT (Ctrl-C, se si esegue i comandi in primo piano nei terminali separati o conchiglie). Hanno anche un ragionevole controllo degli errori, quindi quando escono, ti dicono esattamente perché. Per essere onesti, l'ho fatto perché in questo modo riceverai occasionalmente errori EINTR e, a meno che non li tratti correttamente (riprovando i syscalls pertinenti a meno che non venga richiesto di uscire), i tuoi processi saranno fragili e si bloccheranno dai minimi cambiamenti di condizioni. Sii robusto; non è così difficile, e i risultati sono molto più user/sysadmin-friendly.

Spero che il codice sia interessante. Sarei felice di elaborare, se avete domande sui dettagli. Ricorda solo che l'ho scritto da zero in pochissimo tempo, ed è solo inteso come un semplice esempio. C'è un sacco di spazio per il miglioramento.

+1

Grazie mille .. Questo è quello che sto cercando .. – Suyambu

2

Il socket UNIX viene utilizzato per passare i descrittori di file tra processi.

+2

Per qualche aiuto con questo, vedere ad esempio [questo sito] (http://infohost.nmt.edu/~eweiss/222_book/222_book/0201433079/ch17lev1sec4.html#ch17lev2sec6). –

1

Secondo this post dovrebbe essere possibile. Ti serve un po '(tubi o prese di corrente vengono in mente) per consentire al tuo lavoratore di elaborare le prese.

Sfortunatamente, non ho esperienza con la programmazione UNIX, quindi non posso darti informazioni più concrete.

+0

Puoi passare il socket fd sulla riga di comando: è solo un 'int'! Ma se si esegue una 'fork' senza un' exec' non è necessario passare nulla - è lo stesso programma (ma un processo diverso). – cdarke

+1

Ma ha chiesto specificamente una soluzione, in cui il processo di lavoro è indipendente dal server e NON un processo figlio. Di nuovo, non so molto su unix/linux, ma credo che fork() crei un processo figlio. – Wutz

Problemi correlati