Функция getsockopt() возвращает значение параметра. Кроме вышеперечисленных параметров, функция getsockopt() может использовать следующие параметры:
♦ SO_ERROR — возвращает номер ошибки (будет в возвращаемом значении);
♦ SO_TYPE — возвращает тип сокета.
Рассмотрим небольшой пример работы с опциями сокетов. Мы установим размер буфера TCP.
#include "sock.h"
#include "stdio.h"
main() {
 int sd; /* дескриптор сокета */
 int optval; /* значение опции */
 int optlen; /* длина optval */
 int new_buffsize = 8192; /* новый размер буфера */
 /* создаем сокет */
 sd = socket(AF_INET, SOCK_STREAM, 0);
 /* считывание длины буфера TCP */
 optlen = sizeof(optval);
 getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &optval, &optlen);
 printf("Size of send buffer %dn", optval);
 getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &optval, &optlen);
 printf("Size of recv buffer %dn", optval);
 /* изменяем длину буфера */
 setsockopt(sd, SOL_SOCKET, SO_RCVBUF,
  &new_buffsize, sizeof(new_buffsize));
 setsockopt(sd, SOL_SOCKET, SO_SNDBUF,
  &new_buffsize, sizeof(new_buffsize));
 /* выводим измененную информацию */
 getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &optval, &optlen);
 printf("New size of send buffer %dn", optval);
 getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &optval, &optlen);
 printf("New size of recv buffer %dn", optval);
}
27.3.10. Сигналы и сокеты
С сокетами связаны три сигнала:
♦ SIGIO — сокет готов к вводу/выводу. Сигнал посылается процессу, который связан с сокетом;
♦ SIGURG — сокет получил экспресс-данные (мы их использовать не будем, поэтому особо останавливаться на них нет смысла);
♦ SIGPIPE — запись в сокет больше невозможна. Сигнал посылается процессу, связанному с сокетом. Например, функция write() вызывает сигнал SIGPIPE, если удаленный процесс завершен или связь по сети невозможна.
Пример обработки сигнала SIGPIPE приведен ниже.
Листинг 27.6. Обработка сигнала SIGPIPE
#include "sock.h"
#include <signal.h>
/* обработчик сигнала SIGPIPE */
sigpipe_handler() {
 err_quit("Получен SIGPIPE n");
}
main() {
 int sock; /* дескриптор сокета */
 /* установка обработчика сигнала SIGPIPE */
 signal(SIGPIPE, sigpipe_handler);
 /* работа с сокетом */
}
27.3.11. Мультиплексирование
В этой главе мы рассматривали пример программы-сервера, обрабатывающей запросы только от одного клиента. На практике все выглядит намного сложнее: серверу приходится одновременно обрабатывать запросы многих клиентов. Для мультиплексирования запросов клиентов используется системный вызов select(). Этот вызов использует, например, суперсервер xinetd.
Листинг 27.7. Мультиплексирование запросов
#include "sock.h"
#include <sys/time.h>
main() {
 int sock; /* дескриптор исходного сокета */
 int new_sock; /* дескриптор, полученный с помощью accept */
 int retval; /* возвращаемое значение */
 struct sockaddr_in server; /* адрес сокета */
 fd_set readv; /* переменная для select */
 fd_set writev; /* переменная для select */
 struct timeval tout; /* тайм-аут для select */
 /* бесконечный цикл ожидания */
 for (;;) {
  /* процесс ждет операцию ввода-вывода на сокете;
     одновременно можно ждать и другие операции */
  FD_ZERO(&readv);
  FD_ZERO(&writev);
  FD_SET(sock, &readv);
  FD_SET(sock, &writev);
  tout.tv_sec = 10; /* 10 секунд */
  retval = select(sock+1, &readv, &writev, 0, &to);
  /* если select возвращает нулевое значение, значит тайм-аут */
  if (retval == 0) {
   err_ret("timeout");
   continue;
  }
  /* в противном случае, ищем соответствующий дескриптор */
  if ( (FD_ISSET(sock, &readv)) || (FD_ISSET(sock, &writev))) {
   /* прием связи с сокета */
   new_sock = accept(sock, (struct sockaddr *)0, (int *)0);
   /* работа с сокетом new_sock */
   ...
   /* закрытие текущей связи */
   close(new_sock);
  } else {
   err_ret("Это не сокет! Проверьте все дескрипторыn");
  }
 }
}
Системный вызов select() принимает 5 аргументов:
int select(int fd, fd_set *input, fd_set *output,
 fd_set *error, struct timeval *timeout);
Первый аргумент, fd, — это файловый дескриптор, который может быть сокетом. Следующие три аргумента задают множества файловых дескрипторов для ожидания условий ввода (input), вывода (output) и ошибок (error). Последний аргумент — это тайм-аут.
Множества файловых дескрипторов инициализируются с помощью трех макросов:
FD_ZERO(fd_set);
FD_SET(fd, fd_set);
FD_CLR(fd, fd_set);
Первый макрос полностью очищает множество, следующие два макроса, соответственно, добавляют и удаляют файловый дескриптор. Мы использовали два макроса для ввода и два для вывода. Сначала мы полностью очистили множество, а потом добавили в него соответствующие дескрипторы:
FD_ZERO(&readv);
FD_ZERO(&writev);
FD_SET(sock, &readv);
FD_SET(sock, &writev);
Особого разговора требует последний параметр — тайм-аут. Тайм-аут можно задавать в секундах и миллисекундах. Например, следующие операторы объявляют тайм-аут длительностью 2 секунды и 5 миллисекунд:
struct timeval tout; /* тайм-аут для select */
tout.tv_sec = 2; /* 2 секунды */
tout.tv_usec = 5; /* 5 миллисекунд */
Если вы хотите не использовать тайм-аут (то есть ждать бесконечно), укажите NULL в качестве последнего аргумента.
Функция select() возвращает число файловых дескрипторов, на которых выполнились ожидаемые условия (ввод/вывод/ошибка) или -1 при ошибке.
Вот еще один пример использования функции select(). Мы будем ожидать ввода из файла и из сокета. Если будет достигнут тайм-аут в 20 секунд, пользователь увидит соответствующее сообщение; в противном случае он увидит сообщение: «Получен ввод из файла/сокета».
Листинг 27.8. Еще один пример использования select()
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/select.h>
int k;
int sock;
int fd;
int max_fd;
fd_set input;
struct timeval timeout;
/* инициализация файла и сокета */
...
/* Инициализируем множество ввода */
FD_ZERO(input);
FD_SET(fd, input);
FD_SET(sock, input);
max_fd = (sock > fd ? sock : fd) + 1;
/* Задаем тайм-аут */
timeout.tv_sec = 20;
k = select(max_fd, &input, NULL, NULL, &timeout);
if (k < 0)
 perror("Ошибка при вызове select");
else if (k == 0) puts("TIMEOUT");
else {
 /* Получен ввод */
 if (FD_ISSET(fd, input))
 printf("Получен ввод из файла");
 if (FD_ISSET(sock, input))
  printf("Получен ввод из сокета");
}
Вроде бы код программы очень прост, но комментария заслуживает макрос FD_ISSET. С его помощью мы проверяем, есть ли во множестве ввода ввод из какого-либо источника.
27.3.12. Неблокирующие операции
Некоторые функции для работы с сокетами блокируют программу в случае, если удаленный процесс не осуществил требуемую операцию. Примеры таких функций:
♦ accept();
♦ connect();
♦ read();
♦ write().
Блокирование процесса очень нежелательно, поскольку во время ожидания можно было бы заняться чем-нибудь другим: например, обработать информацию, поступившую с другого сокета. Вы можете объявить сокеты неблокирующими с помощью системного вызовы ioctl().
Особенности работы некоторых функций в неблокирующем режиме:
♦ функция accept() сразу же завершает работу с ошибкой EWOULDBLOCK;
♦ функция connect() тоже завершает работу, но с другой ошибкой: EINPROGRESS;
♦ функции чтения (read(), recv(), recvfrom()) возвращают -1 или 0, если нет считываемых данных.
Ясное дело, что в таком режиме нужно периодически проверять наличие данных — ведь теперь процесс не будет их ожидать: если их нет, то функции просто возвратят -1 или 0.
Пример создания неблокирующих сокетов приведен ниже:
Листинг 27.9. Использование системного вызова ioctl()
#include "sock.h"
#include <sys/ioctl.h>
void main() {
 int sock;
 int on = 1, off = 0; /* значение дня ioctl() */
 /* Создаем неблокирующий сокет */