Пример использования функции.
#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);
}
}