Escuche múltiples puertos desde un servidor

¿Es posible enlazar y escuchar múltiples puertos en Linux en una aplicación?

Por cada puerto que desee escuchar, usted:

  1. Crear un socket separado con socket .
  2. Enlazar al puerto apropiado con bind .
  3. Llame a listen en el zócalo para que esté configurado con una cola de escucha.

En ese punto, su progtwig está escuchando en múltiples sockets. Para aceptar conexiones en esos sockets, necesita saber a qué socket se está conectando un cliente. Ahí es donde entra la select . A medida que sucede, tengo un código que hace exactamente esto sentado, así que aquí hay un ejemplo completo probado de espera de conexiones en múltiples sockets y la devolución del descriptor de archivo de una conexión. La dirección remota se devuelve en parámetros adicionales (la persona que llama debe proporcionar el búfer, al igual que aceptar).

( socket_type aquí es un typedef para int en sistemas Linux, e INVALID_SOCKET es -1 . Están ahí porque este código también se ha portado a Windows).

 socket_type network_accept_any(socket_type fds[], unsigned int count, struct sockaddr *addr, socklen_t *addrlen) { fd_set readfds; socket_type maxfd, fd; unsigned int i; int status; FD_ZERO(&readfds); maxfd = -1; for (i = 0; i < count; i++) { FD_SET(fds[i], &readfds); if (fds[i] > maxfd) maxfd = fds[i]; } status = select(maxfd + 1, &readfds, NULL, NULL, NULL); if (status < 0) return INVALID_SOCKET; fd = INVALID_SOCKET; for (i = 0; i < count; i++) if (FD_ISSET(fds[i], &readfds)) { fd = fds[i]; break; } if (fd == INVALID_SOCKET) return INVALID_SOCKET; else return accept(fd, addr, addrlen); } 

Este código no le dice a la persona que llama a qué puerto se conectó el cliente, pero podría agregar fácilmente un parámetro int * que obtendría el descriptor de archivo que vio la conexión entrante.

Solo bind() a un solo socket, luego listen() y accept() – el socket para el enlace es para el servidor, el fd del accept() es para el cliente. Usted hace su selección en este último buscando cualquier socket de cliente que tenga datos pendientes en la entrada.

En tal situación, puede ser interesado por libevent . Hará el trabajo del select() por ti, probablemente utilizando una interfaz mucho mejor como epoll() .

El gran inconveniente con select() es el uso de las macros FD_... que limitan el número de socket al número máximo de bits en la variable fd_set (de aproximadamente 100 a 256). Si tienes un pequeño servidor con 2 o 3 conexiones, estarás bien. Si pretende trabajar en un servidor mucho más grande, entonces fd_set podría desbordarse fácilmente.

Además, el uso de select() o poll() permite evitar subprocesos en el servidor (es decir, puede poll() su socket y saber si puede accept() , read() o write() en ellos. )

Pero si realmente quieres hacerlo como Unix, entonces debes considerar fork() -ing antes de llamar a accept() . En este caso, no necesita absolutamente select() o poll() (a menos que esté escuchando en muchas IP / puertos y quiera que todos los niños puedan responder a cualquier conexión entrante, pero tiene inconvenientes con esos … el kernel puede enviarle otra solicitud mientras ya está manejando una solicitud, mientras que, con solo accept() , el kernel sabe que está ocupado si no está en la llamada a accept() , bueno, no funciona exactamente así, pero como usuario, así es como funciona para ti.)

Con la fork() prepara el socket en el proceso principal y luego llama a handle_request() en un proceso secundario para llamar a la función accept() . De esa manera, puede tener cualquier número de puertos y uno o más hijos para escuchar en cada uno. Esa es la mejor manera de responder muy rápidamente a cualquier conexión entrante en Linux (es decir, como usuario y mientras tenga procesos secundarios, espere un cliente, esto es instantáneo).

 void init_server(int port) { int server_socket = socket(); bind(server_socket, ...port...); listen(server_socket); for(int c = 0; c < 10; ++c) { pid_t child_pid = fork(); if(child_pid == 0) { // here we are in a child handle_request(server_socket); } } // WARNING: this loop cannot be here, since it is blocking... // you will want to wait and see which child died and // create a new child for the same `server_socket`... // but this loop should get you started for(;;) { // wait on children death (you'll need to do things with SIGCHLD too) // and create a new children as they die... wait(...); pid_t child_pid = fork(); if(child_pid == 0) { handle_request(server_socket); } } } void handle_request(int server_socket) { // here child blocks until a connection arrives on 'server_socket' int client_socket = accept(server_socket, ...); ...handle the request... exit(0); } int create_servers() { init_server(80); // create a connection on port 80 init_server(443); // create a connection on port 443 } 

Tenga en cuenta que la función handle_request() se muestra aquí para manejar una solicitud. La ventaja de manejar una única solicitud es que puede hacerlo a la manera de Unix: asigne los recursos según sea necesario y una vez que se responda la solicitud, exit(0) . La exit(0) llamará al close() , free() , etc. necesario para usted.

Por el contrario, si desea manejar varias solicitudes seguidas, debe asegurarse de que los recursos se desasignen antes de regresar a la llamada accept() . Además, la función sbrk() casi nunca será llamada para reducir la huella de memoria de su hijo. Esto significa que tenderá a crecer un poco de vez en cuando. Esta es la razón por la que un servidor como Apache2 está configurado para responder a un cierto número de solicitudes por niño antes de comenzar un nuevo hijo (por defecto, está entre 100 y 1.000 en estos días).