Este tutorial asume que tiene conocimientos básicos de programación de sockets, es decir, está familiarizado con el servidor básico y el modelo de cliente. En el modelo básico, el servidor maneja solo un cliente a la vez, lo cual es una gran suposición si desea desarrollar cualquier modelo de servidor escalable.
La forma sencilla de manejar múltiples clientes sería generar un nuevo hilo para cada nuevo cliente conectado al servidor. Este método no se recomienda encarecidamente debido a varias desventajas, a saber:
- Los subprocesos son difíciles de codificar, depurar y, a veces, tienen resultados impredecibles.
- Sobrecarga de cambio de contexto
- No escalable para un gran número de clientes
- Pueden ocurrir interbloqueos
Seleccione()
Una mejor manera de manejar múltiples clientes es usando el comando select() de Linux.
- El comando Seleccionar permite monitorear múltiples descriptores de archivo, esperando hasta que uno de los descriptores de archivo se active.
- Por ejemplo, si hay algunos datos para leer en uno de los zócalos, seleccione proporcionará esa información.
- Select funciona como un controlador de interrupciones, que se activa tan pronto como cualquier descriptor de archivo envía datos.
Estructura de datos utilizada para seleccionar: fd_set
Contiene la lista de descriptores de archivos para monitorear alguna actividad.
Hay cuatro funciones asociadas con fd_set:
fd_set readfds; // Clear an fd_set FD_ZERO(&readfds); // Add a descriptor to an fd_set FD_SET(master_sock, &readfds); // Remove a descriptor from an fd_set FD_CLR(master_sock, &readfds); //If something happened on the master socket , then its an incoming connection FD_ISSET(master_sock, &readfds);
Activación de selección: lea la página del manual de selección para verificar todos los argumentos para el comando de selección.
activity = select( max_fd + 1 , &readfds , NULL , NULL , NULL);
Implementación:
//Example code: A simple server side code, which echos back the received message. //Handle multiple socket connections with select and fd_set on Linux #include <stdio.h> #include <string.h> //strlen #include <stdlib.h> #include <errno.h> #include <unistd.h> //close #include <arpa/inet.h> //close #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/time.h> //FD_SET, FD_ISSET, FD_ZERO macros #define TRUE 1 #define FALSE 0 #define PORT 8888 int main(int argc , char *argv[]) { int opt = TRUE; int master_socket , addrlen , new_socket , client_socket[30] , max_clients = 30 , activity, i , valread , sd; int max_sd; struct sockaddr_in address; char buffer[1025]; //data buffer of 1K //set of socket descriptors fd_set readfds; //a message char *message = "ECHO Daemon v1.0 \r\n"; //initialise all client_socket[] to 0 so not checked for (i = 0; i < max_clients; i++) { client_socket[i] = 0; } //create a master socket if( (master_socket = socket(AF_INET , SOCK_STREAM , 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } //set master socket to allow multiple connections , //this is just a good habit, it will work without this if( setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 ) { perror("setsockopt"); exit(EXIT_FAILURE); } //type of socket created address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons( PORT ); //bind the socket to localhost port 8888 if (bind(master_socket, (struct sockaddr *)&address, sizeof(address))<0) { perror("bind failed"); exit(EXIT_FAILURE); } printf("Listener on port %d \n", PORT); //try to specify maximum of 3 pending connections for the master socket if (listen(master_socket, 3) < 0) { perror("listen"); exit(EXIT_FAILURE); } //accept the incoming connection addrlen = sizeof(address); puts("Waiting for connections ..."); while(TRUE) { //clear the socket set FD_ZERO(&readfds); //add master socket to set FD_SET(master_socket, &readfds); max_sd = master_socket; //add child sockets to set for ( i = 0 ; i < max_clients ; i++) { //socket descriptor sd = client_socket[i]; //if valid socket descriptor then add to read list if(sd > 0) FD_SET( sd , &readfds); //highest file descriptor number, need it for the select function if(sd > max_sd) max_sd = sd; } //wait for an activity on one of the sockets , timeout is NULL , //so wait indefinitely activity = select( max_sd + 1 , &readfds , NULL , NULL , NULL); if ((activity < 0) && (errno!=EINTR)) { printf("select error"); } //If something happened on the master socket , //then its an incoming connection if (FD_ISSET(master_socket, &readfds)) { if ((new_socket = accept(master_socket, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) { perror("accept"); exit(EXIT_FAILURE); } //inform user of socket number - used in send and receive commands printf("New connection , socket fd is %d , ip is : %s , port : %d \n" , new_socket , inet_ntoa(address.sin_addr) , ntohs (address.sin_port)); //send new connection greeting message if( send(new_socket, message, strlen(message), 0) != strlen(message) ) { perror("send"); } puts("Welcome message sent successfully"); //add new socket to array of sockets for (i = 0; i < max_clients; i++) { //if position is empty if( client_socket[i] == 0 ) { client_socket[i] = new_socket; printf("Adding to list of sockets as %d\n" , i); break; } } } //else its some IO operation on some other socket for (i = 0; i < max_clients; i++) { sd = client_socket[i]; if (FD_ISSET( sd , &readfds)) { //Check if it was for closing , and also read the //incoming message if ((valread = read( sd , buffer, 1024)) == 0) { //Somebody disconnected , get his details and print getpeername(sd , (struct sockaddr*)&address , \ (socklen_t*)&addrlen); printf("Host disconnected , ip %s , port %d \n" , inet_ntoa(address.sin_addr) , ntohs(address.sin_port)); //Close the socket and mark as 0 in list for reuse close( sd ); client_socket[i] = 0; } //Echo back the message that came in else { //set the string terminating NULL byte on the end //of the data read buffer[valread] = '\0'; send(sd , buffer , strlen(buffer) , 0 ); } } } } return 0; }
Compile el archivo y ejecute el servidor.
Utilice telnet para conectar el servidor como cliente.
Intente ejecutar en diferentes máquinas usando el siguiente comando:
telnet localhost 8888
Explicación del código:
- Hemos creado una variable fd_set readfds, que monitoreará todos los descriptores de archivos activos de los clientes más los del socket de escucha del servidor principal.
- Cada vez que se conecte un nuevo cliente, se activará master_socket y se abrirá un nuevo fd para ese cliente. Almacenaremos su fd en nuestra lista de clientes y en la próxima iteración lo agregaremos a los readfds para monitorear la actividad de este cliente.
- Del mismo modo, si un cliente antiguo envía algunos datos, se activarán readfds y comprobaremos en la lista de clientes existentes para ver qué cliente ha enviado los datos.
Alternativas:
Existen otras funciones que pueden realizar tareas similares a seleccionar. pseleccionar , encuesta , pencuesta
Este artículo es una contribución de Akshat Sinha . Si te gusta GeeksforGeeks y te gustaría contribuir, también puedes escribir un artículo usando write.geeksforgeeks.org o enviar tu artículo por correo a review-team@geeksforgeeks.org. Vea su artículo que aparece en la página principal de GeeksforGeeks y ayude a otros Geeks.
Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema tratado anteriormente.
Publicación traducida automáticamente
Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA