Использование неблокирующих сокетов и обслуживание клиентов одним процессом при помощи функции select().




Пример использования функции.

#include <netdb.h>

#include <stdio.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <string.h>

 

main(int argc, char *argv[])

{

int i;

struct hostent *host;

struct in_addr addr;

host = gethostbyname(argv[1]);

bzero(&addr,sizeof(addr));

if(host!=NULL)

{

printf(”Official name: %s\n”, host->h_name);

for(i=0;host->h_aliases[i]!=0;i++)

{

printf(”alias[%d]: %s\n”, i+1, host->h_aliases[i]);

};

printf(” Adress type=%d\n”, host->h_addrtype);

for(i=0; i< 2; i++)

{

bcopy(host->h_addr_list[i],&addr,host->h_length);

printf(”Addr[%d]: %s\n ”, i+1, inet_ntoa(addr));

};

}else{

perror(argv[1]);

};

};

return 0;

};

 

В приведённом примере определяются только два адреса для хоста с заданным в качестве аргумента при запуске программы именем.

Заметим, что данная функция преобразует имя хоста в IP адрес в том порядке байтов, который необходим для его использования в структуре sockaddr_in.

 

При создании сервера, обслуживающего сразу несколько клиентов существует несколько путей решения данной задачи, например:

1. Использование блокирующих сокетов и обслуживание взаимодействия с каждым клиентом отдельным процессом.

2. Использование неблокирующих сокетов и обслуживание клиентов одним процессом при помощи функции select().

Рассмотрим каждый из предлагаемых путей.

Использование неблокирующих сокетов и обслуживание клиентов одним процессом при помощи функции select().

Вначале, после создания сокета, необходимо перевести его в неблокирующий режим. Это делается с помощью следующих системных вызовов

 



#include <sys/unistd.h>
#include <sys/fcntl.h>

 

.........................................................................................................



int s = socket(PF_INET, SOCK_STREAM, 0);
fcntl(s, F_SETFL, O_NONBLOCK); // установка в неблокирующий режим

 

Неблокирующий режим сокетов характеризуется тем, что при вызове операции чтения (записи) данных на одном конце сокета в отсутствии вызова операции записи (чтения) данных на другом конце сокета, вызов чтения (записи) завершится с кодом выполнения =-1 ошибкой EWOULDBLOCK в переменной errno.

Дальнейшие действия по обслуживанию нескольких клиентов могут быть реализованы следующим образом - сервер в цикле будет опрашивать все созданные сокеты (например функциями recv() и send()), если сокет не готов для приема или передачи данных, то системные вызова будут возвращать код =-1 и значение errno равное EWOULDBLOCK, если же сокет готов к приему или передачи данных то системные вызовы записи или чтения данных в (из) сокет выполняться успешно и далее продолжится по циклу опрос всех сокетов.

Однако, есть белее элегантное решение с использованием функции select(). Данная функция имеет следующий синтаксис и описание

 



#include <sys/select.h>
int select(int n, fd_set * readfds, fd_set * writefds, fd_set * exceptfds,
struct timeval * timeout);


FD_SET(int fd, fd_set * set);

FD_CLR(int fd, fd_set * set);

FD_ISSET(int fd, fd_set * set);

FD_ZERO(fd_set * set);

 

Функция select() даёт нам возможность одновременной проверки нескольких сокетов, чтобы увидеть, если у них данные, ожидающие recv() или можете ли вы send() данные в сокет без блокирования. Данная функция работает в режиме блокировки, пока либо не произойдут события, связанные с появлением возможности чтения или записи в сокеты, либо не истечет время тайм атута, задаваемое для этого вызова. Аргументы функции select() имеют следующий смысл:

fd_set * readfds, fd_set * writefds, fd_set * exceptfds - это указатели на наборы дескрипторов сокетов, предназначенных для операций чтения, записи и исключительных ситуаций. Если в программе, например, используются только мониторинг сокетов для возможности чтения из них (т.е применения функции recv()), то вместо остальных наборов дескрипторов сокетов необходимо использовать NULL.

int n - это число определяется, как максимальное значение дескриптора, находящегося в наборах readfds, writefds и exceptfds плюс 1. Структура struct timeval * timeout позволяет указать, как долго должен ждать вызов select(), чтобы произошли события в наборах дискрипторов. Структура struct timeval состоит из двух полей: tv_sec - число секунд, и tv_usec - число миллисекунд (1000000 миллисекунд = 1 секунда).

Функция select() возвращает число дескрипторов в наборах, готовых к операциям чтения или записи, 0 - если истекло время тайм аута, а готовности дескрипторов нет и -1 в случае ошибки. При этом наборы дескрипторов readfds, writefds и exceptfds модифицируются для указания какие дескрипторы готовы к операциям ввода или вывода.

Макросы, указанные после определения функции выше, выполняют следующие функции:

FD_SET(int fd, fd_set * set); добавляет дескриптор int fd в набор fd_set * set

FD_CLR(int fd, fd_set * set); удаляет дескриптор int fd из набора fd_set * set

FD_ISSET(int fd, fd_set * set); возвращает true если int fd из набора fd_set * set готов к выполненю операциы ввода или вывода.

FD_ZERO(fd_set * set); обнуляет содержимое набора fd_set * set

Теперь приведём пример простейшей программы с использованием функции select(), которая будет просматривать сокеты только на предмет возможности чтения из них данных.

 



int s1, s2, n, rv;
fd_set readfds;
struct timeval tv;
char buf1[256], buf2[256];

// предположим, что мы связались с обоими серверами в данной точке
//s1 = socket(...);
//s2 = socket(...);
//connect(s1,...)...
//connect(s2,...)...

// очищаем набор дескрипторов
FD_ZERO(&readfds);

// добавляем наши дескрипторы в набор
FD_SET(s1, &readfds);
FD_SET(s2, &readfds);

//так, как мы по тексту программы получили дескриптор s2 вторым, то его значение

// "больше", и мы будем его использовать для задания параметра n в select()
n = s2 + 1;

// задаём время тайм аута 10.5 сек
tv.tv_sec = 10;
tv.tv_usec = 500000;
rv = select(n, &readfds, NULL, NULL, &tv);

if (rv == -1) {
perror("select"); // ошибка при вызове select()
} else if (rv == 0) {
printf("Timeout occurred! No data after 10.5 seconds.\n");
} else {
// один или оба дескриптора имеют данные для использования функции recv()
if (FD_ISSET(s1, &readfds)) {
recv(s1, buf1, sizeof buf1, 0);
}
if (FD_ISSET(s2, &readfds)) {
recv(s1, buf2, sizeof buf2, 0);
}
}

 

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-08-20 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: