Ваша простая серверная программа может выиграть от применения select
для одновременной обработки множественных клиентов, не прибегая к помощи дочерних процессов. Используя этот метод в реальных приложениях, вы должны следить за тем, чтобы другие клиенты не ждали слишком долго, пока вы обрабатываете первого подключившегося клиента.
Сервер может применять функцию select
одновременно к сокету, ожидающему запросы на подключение, и к сокетам клиентских соединений. Как только активность зафиксирована, можно использовать макрос FD_ISSET
для проверки в цикле всех возможных файловых дескрипторов и выявления активных среди них.
Если сокет, ожидающий запросов на подключение, готов к вводу, это означает, что клиент пытается подсоединиться, и вы можете вызывать функцию accept
без риска блокировки. Если клиентский дескриптор указывает на готовность, это означает, что есть запрос клиента, ждущий, что вы сможете прочесть и обработать его. Чтение 0 байтов означает, что клиентский процесс завершился, и вы можете закрыть сокет и удалить его из множества своих дескрипторов.
Выполните упражнение 15.9.
1. В финальный пример программы server5.с вы включите заголовочные файлы sys/time.h и sys/ioctl.h вместо signal.h, использованного в предыдущей программе, и объявите несколько дополнительных переменных для работы с вызовом select
:
#include
#include
#include
#include
#include
#include
#include
#include
int main {
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
int result;
fd_set readfds, testfds;
2. Создайте сокет для сервера и присвойте ему имя:
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(9734);
server_len = sizeof(server_address);
bind(serversockfd, (struct sockaddr *)&server_address, server_len);
3. Создайте очередь запросов на соединение и инициализируйте множество readfds
для обработки ввода с сокета server_sockfd
:
listen(server_sockfd, 5);
FD_ZERO(&readfds);
FD_SET(server_sockfd, &readfds);
4. Теперь ждите запросы от клиентов. Поскольку вы передали пустой указатель как параметр timeout
, не будет наступать истечения времени ожидания. Программа завершится и сообщит об ошибке, если select
возвращает значение, меньшее 1.
while(1) {
char ch;
int fd;
int nread;
testfds = readfds;
printf("server waiting\n");
result = select(FD_SETSIZE, &testfds, (fd_set *)0,
(fd_set *)0, (struct timeval *)0);
if (result < 1) {
perror("server5");
exit(1);
}
5. После того как вы определили, что есть активность, можно выяснить, какой из дескрипторов активен, проверяя каждый из них по очереди с помощью макроса FD_ISSET
:
for (fd = 0; fd < FD_SETSIZE; fd++) {
if (FD_ISSET(fd, &testfds)) {
6. Если зафиксирована активность на server_sockfd
, это может быть запрос на новое соединение, и вы добавляете в множество дескрипторов соответствующий client_sockfd
:
if (fd == server_sockfd) {
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr*)&client_address, &client_len);
FD_SET(client_sockfd, &readfds);
printf("adding client on fd %d\n", client_sockfd);
}
Если активен не сервер, значит, активность проявляет клиент. Если получен close
, клиент исчезает, и можно удалить его из множества дескрипторов. В противном случае вы "обслуживаете" клиента, как и в предыдущих примерах.
else {
ioctl(fd, FIONREAD, &nread);
if (nread == 0) {
close(fd);
FD_CLR(fd, &readfds);
printf("removing client on fd %d\n", fd);
} else {
read(fd, &ch, 1);
sleep(5);
printf("serving client on fd %d\n", fd);
ch++;