2012-09-14 13 views
6

Estoy haciendo algunos experimentos en la programación de socket (en el entorno Unix). Lo que estoy intentando esTransferencia del controlador de socket entre procesos independientes

  1. El cliente envía la solicitud al servidor.
  2. El servidor debe enviar el socket de los clientes al Trabajador (Un proceso independiente)
  3. El trabajador debe responder de nuevo al Cliente.

¿Esto es posible?

Este escenario funciona si Worker es hijo del servidor.

Si el servidor y el trabajador son procesos independientes ¿funciona esto? En caso afirmativo, ¿alguien puede darme algunas ideas al respecto? ¿Hay muestras disponibles para este tipo de escenario?

Respuesta

11

The Linux Programming Interface libro tiene ejemplos para ambos sending y receiving descriptores de archivos entre procesos no relacionados, utilizando un socket de dominio Unix.

Por diversión, escribí mis propios ejemplos desde cero. 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; 
} 

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

puede compilar usando

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

y ejecutar utilizando, por ejemplo,

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

Como se puede ver, los ./worker./server y procesos están completamente separados. Recomiendo iniciarlos desde diferentes ventanas (omitiendo el & al final de las líneas de comando, que de lo contrario ejecuta los comandos en el fondo). El connection es la ruta o el nombre del socket de dominio Unix utilizado para transferir el descriptor de archivo de conexión de red. El /bin/date es un comando (no un comando de shell, un ejecutable) que se ejecutará para cada conexión, con entrada, salida y error estándar conectados directamente al cliente de red, muy parecido a inetd o xinetd, simplemente.

Puedes probar la conexión mediante, por ejemplo.

nc 127.0.0.1 8000 

o

telnet 127.0.0.1 8000 

El comando anterior /bin/date se acaba de emitir la fecha actual en la salida estándar, pero si se utiliza un comando trabajador inteligente poco, digamos

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' 

puede utilizar su navegador (http://127.0.0.1:8000/) para probar.

El diseño es tal que worker.c escucha un socket de dominio Unix (connection en el directorio de trabajo actual en todos los comandos de ejemplo anteriores). Primero acepta una conexión (desde un solo servidor) y luego espera que cada byte entrante se asocie con SCM_RIGHTS datos auxiliares que contienen el descriptor de archivo que hace referencia a la conexión del cliente. Si hay un problema o se corta la conexión, vuelve a esperar una nueva conexión de un servidor. Si recibe un descriptor de cliente, bifurca un proceso hijo, redirige su entrada, salida y error estándar al descriptor del cliente y ejecuta el comando especificado en la línea de comando ./worker. El proceso principal cierra su copia del descriptor de cliente y vuelve a esperar una nueva.

server.c escucha las conexiones entrantes a la dirección IPv4 o IPv6 y al puerto especificado en su línea de comandos. Cuando obtiene una conexión, transfiere el descriptor de archivo conectado al proceso anterior worker.c a través del socket de dominio Unix especificado en la línea de comando (connection), cierra su propia copia y vuelve a esperar una nueva conexión. Tenga en cuenta que si el servidor pierde la conexión con el trabajador, aborta; querrá comenzar ./worker siempre antes del ./server.

Tanto server.c y worker.c instalar los gestores de señales sencillas para que se les puede decir a la salida mediante el envío de una señal HUP o INT (Ctrl-C, si ejecuta los comandos en el primer plano en terminales separados o conchas). También tienen una comprobación de errores razonable, por lo que cuando salen, te dicen exactamente por qué. Para ser sincero, lo hice porque de esa manera usted recibirá errores de EINTR ocasionalmente, y a menos que los trate correctamente (reintentando los syscalls relevantes a menos que se le pida salir), sus procesos serán frágiles y colapsarán por los más mínimos cambios en las condiciones. Ser robusto; no es tan difícil, y los resultados son mucho más amigables para el usuario/administrador de sistemas.

Espero que encuentre el código interesante. Estaré encantado de elaborarlo, si tiene alguna pregunta sobre los detalles. Solo recuerda que lo escribí desde cero en muy poco tiempo, y solo tiene la intención de ser un simple ejemplo. Hay mucho de margen de mejora.

+1

Muchas gracias .. Esto es lo que estoy buscando ... – Suyambu

2

El socket UNIX se utiliza para pasar descriptores de archivos entre procesos.

+2

Para obtener ayuda con esto, consulte, por ejemplo, [este sitio] (http://infohost.nmt.edu/~eweiss/222_book/222_book/0201433079/ch17lev1sec4.html#ch17lev2sec6). –

1

Según this post debería ser posible. Necesitas algo (te vienen a la mente las cañerías o los enchufes) para que el proceso de tu trabajador conozca el manejo de los enchufes.

Lamentablemente, no tengo experiencia con la programación de Unix, por lo que no puedo darle más información concreta.

+0

Puede pasar el socket fd en la línea de comandos: ¡es solo un 'int'! Pero si solo hace un 'fork' sin un' exec', no tiene que pasar nada, es el mismo programa (pero un proceso diferente). – cdarke

+1

Pero pidió específicamente una solución, en la que el proceso de trabajo es independiente del servidor y NO un proceso secundario. De nuevo, no sé mucho sobre unix/linux, pero creo que fork() crea un proceso hijo. – Wutz

Cuestiones relacionadas