Программирование сокетов на C / C ++: обработка нескольких клиентов на сервере без многопоточности
В этом руководстве предполагается, что у вас есть базовые знания о программировании сокетов, т. Е. Вы знакомы с базовой моделью сервера и клиента. В базовой модели сервер одновременно обслуживает только одного клиента, что является большим допущением, если вы хотите разработать любую масштабируемую модель сервера.
Простым способом обработки нескольких клиентов было бы создание нового потока для каждого нового клиента, подключенного к серверу. Этот метод категорически не рекомендуется из-за различных недостатков, а именно:
- Потоки сложно кодировать, отлаживать и иногда они дают непредсказуемые результаты.
- Служебное переключение контекста
- Не масштабируется для большого количества клиентов
- Могут возникнуть взаимоблокировки
Выбирать()
Лучший способ работать с несколькими клиентами - использовать команду select () linux.
- Команда Select позволяет отслеживать несколько файловых дескрипторов, ожидая, пока один из файловых дескрипторов станет активным.
- Например, если есть какие-то данные для чтения в одном из сокетов, select предоставит эту информацию.
- Select работает как обработчик прерывания, который активируется, как только дескриптор файла отправляет какие-либо данные.
Структура данных, используемая для выбора: fd_set
Он содержит список файловых дескрипторов, которые нужно отслеживать на предмет некоторых действий.
С fd_set связаны четыре функции:
fd_set readfds; // Очистить fd_set FD_ZERO (& readfds); // Добавляем дескриптор в fd_set FD_SET (master_sock, & readfds); // Удаляем дескриптор из fd_set FD_CLR (master_sock, & readfds); // Если что-то случилось с главным сокетом, то это входящее соединение FD_ISSET (master_sock, & readfds);
Активация select: прочтите справочную страницу для select, чтобы проверить все аргументы для команды select.
activity = select (max_fd + 1, & readfds, NULL, NULL, NULL);
Выполнение:
//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
" ; //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
" , 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
" , 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
" , 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
" , 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] = ' |