Системный вызов listen()
Системный вызов listen() является первым из еще неизвестных нам вызовов, применяемым на TCP–сервере. В его задачу входит перевод TCP–сокета в пассивное (слушающее) состояние и создание очередей для порождаемых при установлении соединения присоединенных сокетов, находящихся в состоянии не полностью установленного соединения и полностью установленного соединения. Для этого вызов имеет два параметра: дескриптор TCP–сокета и число, определяющее глубину создаваемых очередей.
Рис.
15-16.8.
Схема установления TCP соединения
Системный вызов listen()
Прототип системного вызова
#include <sys/types.h> #include <sys/socket.h> int listen(int sockd, int backlog);
Описание системного вызова
Системный вызов listen используется сервером, ориентированным на установление связи путем виртуального соединения, для перевода сокета в пассивный режим и установления глубины очереди для соединений.
Параметр sockd является дескриптором созданного ранее сокета, который должен быть переведен в пассивный режим, т. е. значением, которое вернул системный вызов socket() . Системный вызов listen требует предварительной настройки адреса сокета с помощью системного вызова bind() .
Параметр backlog определяет максимальный размер очередей для сокетов, находящихся в состояниях полностью и не полностью установленных соединений.
Возвращаемое значение
Системный вызов возвращает значение 0 при нормальном завершении и значение -1 при возникновении ошибки.
Последний параметр на разных UNIX-подобных операционных системах и даже на разных версиях одной и той же системы может иметь различный смысл. Где-то это суммарная длина обеих очередей, где-то он относится к очереди не полностью установленных соединений (например, Linux до версии ядра 2.2) где-то – к очереди полностью установленных соединений (например, Linux, начиная с версии ядра 2.2), где-то – вообще игнорируется.
Системный вызов accept()
Системный вызов accept() позволяет серверу получить информацию о полностью установленных соединениях. Если очередь полностью установленных соединений не пуста, то он возвращает дескриптор для первого присоединенного сокета в этой очереди, одновременно удаляя его из очереди. Если очередь пуста, то вызов ожидает появления полностью установленного соединения. Системный вызов также позволяет серверу узнать полный адрес клиента, установившего соединение. У вызова есть три параметра: дескриптор слушающего сокета, через который ожидается установление соединения; указатель на структуру, в которую при необходимости будет занесен полный адрес сокета
клиента, установившего соединение; указатель на целую переменную, содержащую максимально допустимую длину этого адреса. Как и в случае вызова recvfrom() , последний параметр является модернизируемым, а если нас не интересует, кто с нами соединился, то вместо второго и третьего параметров можно указать значение NULL.
Системный вызов accept()
Прототип системного вызова
#include <sys/types.h> #include <sys/socket.h> int accept(int sockd, struct sockaddr *cliaddr, int *clilen);
Описание системного вызова
Системный вызов accept используется сервером, ориентированным на установление связи путем виртуального соединения, для приема полностью установленного соединения .
Параметр sockd является дескриптором созданного и настроенного сокета, предварительного переведенного в пассивный (слушающий) режим с помощью системного вызова listen() .
Системный вызов accept требует предварительной настройки адреса сокета с помощью системного вызова bind() .
Параметр cliaddr служит для получения адреса клиента, установившего логическое соединение, и должен содержать указатель на структуру, в которую будет занесен этот адрес.
Параметр clilen содержит указатель на целую переменную, которая после возвращения из вызова будет содержать фактическую длину адреса клиента. Заметим, что перед вызовом эта переменная должна содержать максимально допустимое значение такой длины. Если параметр cliaddr имеет значение NULL, то и параметр clilen может иметь значение NULL.
Возвращаемое значение
Системный вызов возвращает при нормальном завершении дескриптор присоединенного сокета, созданного при установлении соединения для последующего общения клиента и сервера, и значение -1 при возникновении ошибки.
Пример простого TCP-сервера
Рассмотрим программу 15–16-4.c, реализующую простой TCP—сервер для сервиса echo.
/* Пример простого TCP-сервера для сервиса echo */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <errno.h> #include <unistd.h> void main() { int sockfd, newsockfd; /* Дескрипторы для слушающего и присоединенного сокетов */ int clilen; /* Длина адреса клиента */ int n; /* Количество принятых символов */ char line[1000]; /* Буфер для приема информации */ struct sockaddr_in servaddr, cliaddr; /* Структуры для размещения полных адресов сервера и клиента */ /* Создаем TCP-сокет */ if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){ perror(NULL); exit(1); } /* Заполняем структуру для адреса сервера: семейство протоколов TCP/IP, сетевой интерфейс – любой, номер порта 51000. Поскольку в структуре содержится дополнительное не нужное нам поле, которое должно быть нулевым, побнуляем ее всю перед заполнением */ bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_port= htons(51000); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Настраиваем адрес сокета */ if(bind(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){ perror(NULL); close(sockfd); exit(1); } /* Переводим созданный сокет в пассивное (слушающее) состояние. Глубину очереди для установленных соединений описываем значением 5 */ if(listen(sockfd, 5) < 0){ perror(NULL); close(sockfd); exit(1); } /* Основной цикл сервера */ while(1){ /* В переменную clilen заносим максимальную длину ожидаемого адреса клиента */ clilen = sizeof(cliaddr); /* Ожидаем полностью установленного соединения на слушающем сокете. При нормальном завершении у нас в структуре cliaddr будет лежать полный адрес клиента, установившего соединение, а в переменной clilen – его фактическая длина. Вызов же вернет дескриптор присоединенного сокета, через который будет происходить общение с клиентом. Заметим, что информация о клиенте у нас в дальнейшем никак не используется, поэтому вместо второго и третьего параметров можно было поставить значения NULL. */ if((newsockfd = accept(sockfd, (struct sockaddr *) &cliaddr, &clilen)) < 0){ perror(NULL); close(sockfd); exit(1); } /* В цикле принимаем информацию от клиента до тех пор, пока не произойдет ошибки (вызов read() вернет отрицательное значение) или клиент не закроет соединение (вызов read() вернет значение 0). Максимальную длину одной порции данных от клиента ограничим 999 символами. В операциях чтения и записи пользуемся дескриптором присоединенного сокета, т. е. значением, которое вернул вызов accept().*/ while((n = read(newsockfd, line, 999)) > 0){ /* Принятые данные отправляем обратно */ if((n = write(newsockfd, line, strlen(line)+1)) < 0){ perror(NULL); close(sockfd); close(newsockfd); exit(1); } } /* Если при чтении возникла ошибка – завершаем работу */ if(n < 0){ perror(NULL); close(sockfd); close(newsockfd); exit(1); } /* Закрываем дескриптор присоединенного сокета и уходим ожидать нового соединения */ close(newsockfd); } }
Листинг
15-16.4.
Программа 15–16-4.c . Пример простого TCP-сервера для сервиса echo.
Наберите и откомпилируйте программу. Запустите ее на выполнение. Модифицируйте текст программы TCP-клиента (программа 15–16-3.c ), заменив номер порта с 7 на 51000. Запустите клиента с другого виртуального терминала или с другого компьютера и убедитесь, что клиент и сервер взаимодействуют корректно.
Перевод сокета в пассивное состояние. Создание очереди соединений
listenQ
accept().
write()
close()
— |
Ожидание |
♦ |
|
г*- |
Ожидание
информации. |
Обработка |
|
Отправление |
|
Окончание |
|
connect()
Передача |
write() |
і |
|
Ожидание |
read() |
Окончание
соединения
Рис.
14-15.7.
Схема
взаимодействия клиента и сервера для
протокола TCP
Установление
логического соединения. Системный вызов
connectQ
Среди
системных вызовов со стороны клиента
появляется только один новый — connect
().
Системный вызов connect
()
при работе с ТСР-сокетами служит для
установления логического соединения
со стороны клиента. Вызов connect
()
скрывает внутри себя настройку сокета
на выбранный системой порт и произвольный
сетевой интерфейс (по сути дела, вызов
bind()
с нулевым номером порта и IP-адресом
INADDR_ANY).
Вызов
блокируется до тех пор, пока не будет
установлено логическое соединение, или
пока не пройдет определенный промежуток
времени, который может регулироваться
системным администратором.
Для
установления соединения необходимо
задать три параметра: дескриптор
активного сокета, через который будет
устанавливаться соединение, полный
адрес сокета сервера и его длину.
Системный
вызов connect()
Прототип
системного вызова
Mnclude
<sys/types.h> Mnclude <sys/socket.h>
int
connect(int sockd, struct sockaddr *servaddr, int addrlen);
Описание
системного вызова
Системный
вызов connect
служит
для организации связи клиента с сервером.
Чаще всего он используется для установления
логического соединения, хотя может быть
применен и при связи с помощью датаграмм
(connectionless).
Данное описание не является полным
описанием системного вызова, а
предназначено только для использования
в нашем курсе. Полную информацию можно
найти в UNIX
Manual.
Параметр
sockd
является
дескриптором созданного ранее
коммуникационного узла, т. е. значением,
которое вернул системный вызов socket
().
Параметр
servaddr
представляет
собой адрес структуры, содержащей
информацию о полном адресе сокета
сервера. Он имеет тип указателя на
структуру-шаблон struct
sockaddr,
которая
должна быть конкретизирована в зависимости
от используемого семейства протоколов
и заполнена перед вызовом.
Параметр
addrlen
должен
содержать фактическую длину структуры,
адрес которой передается в качестве
второго параметра. Эта длина меняется
в зависмости от семейства протоколов
и различается даже в пределах одного
семейства протоколов (например, для
UNIX
Domain).
При
установлении виртуального соединения
системный вызов не возвращается до его
установления или до истечения
установленного в системе времени —
timeout.
При использовании его в connectionless
связи вызов возвращается немедленно.
Возвращаемое
значение
Системный
вызов возвращает значение 0
при
нормальном завершении и отрицательное
значение, если в процессе его выполнения
возникла ошибка.
Пример
программы TCP-клиента
Рассмотрим
пример — программу 14—15-З.с. Это простой
ТСР-кли-ент, обращающийся к стандартному
системному сервису echo.
Стандартный
сервис принимает от клиента текстовую
датаграмму и, не изменяя ее, отправляет
обратно. За сервисом зарезервирован
номер порта 7. Заметим, что это порт 7 TCP
— не путать с портом 7 UDP
из примера в разделе «Пример программы
UDP-клиента»!
Для правильного запуска программы
необходимо указать символьный IP-адрес
сетевого интерфейса компьютера, к
сервису которого требуется обратиться,
в качестве аргумента командной строки,
например:
a.out
192.168.253.12
Для
того чтобы подчеркнуть, что после
установления логического соединения
клиент и сервер могут обмениваться
информацией неоднократно, клиент трижды
запрашивает текст с экрана, отсылает
его серверу и печатает полученный ответ.
Ниже представлен текст программы.
/*
Простой пример TCP-клиента
для сервиса echo
*/
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<string.h>
#include
<stdio.h>
#include
<errno.h>
#include
<unistd.h>
void
main(int argc, char **argv)
{
int
sockfd;
/* Дескриптор сокета */
int
n;
/* Количество переданных или прочитанных
символов
*/ int
i;
/* Счетчик цикла */
char
sendline[1000],recvline[1000]
; /* Массивы для отсылаемой и принятой
строки */ struct
sockaddr_in
servaddr;
/* Структура для
адреса
сервера */ /* Сначала проверяем наличие
второго аргумента в командной строке.
При его отсутствии прекращаем работу
*/ if(argc
!= 2){
printf(«Usage:
a.out
<IP
address>n»);
exit(l);
}
/*
Обнуляем символьные массивы */
bzero(sendline,1000);
bzero(recvline,1000);
/* Создаем ТСР-сокет */
if((sockfd
= socket(PF_INET, SOCK_STREAM, 0)) < 0){ perror(NULL); /* Печатаем
сообщение
об
ошибке
*/
exit
(1) ;
}
/*
Заполняем структуру для адреса сервера:
семейство протоколов TCP/IP, сетевой
интерфейс — из аргумента командной
строки, номер порта 7.
Поскольку в структуре содержится
дополнительное не нужное нам поле,
хоторое должно быть нулевым, перед
заполнением обнуляем ее всю */
bzero(&servaddr,
sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port =
htons(51000);
іf(inet_aton(argv[1],
kservaddr.sin_addr) == 0){ printf(«Invalid IP addressn»);
close(sockfd); exit(1);
}
/*
Устанавливаем логическое соединение
через созданный сокет с сокетом сервера,
адрес которого мы занесли в структуру
*/
if
(connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))
< 0){
perror(NULL);
close(sockfd);
exit(1);
}
/*
Три раза в цикле вводим строку с
клавиатуры, отправляем ее серверу и
читаем полученный ответ */ for(i=0;
i<3;
i++){
printf(«String
=> «); fflush(stdin);
fgets(sendline,
1000, stdin)• if( (n = write(sockfd, sendline, strlen(sendline)+1))
< 0){
perror(«Can’t
writen»);
close(sockfd);
exit(1)
;
}
if
( (n = read(sockfd,recvline, 999)) < 0){ perror(«Can1t
readn»); close(sockfd); exit(1) ;
)
printf(«%s»,
recvline);
}
/*
Завершаем
соединение
*/ close(sockfd);
}
Наберите
и откомпилируйте программу. Перед
запуском узнайте
у своего системного администратора,
запущен
ли в системе стандартный TCP-сервис
echo
и,
если нет, попросите это сделать. Запустите
программу с запросом к сервису своего
компьютера, к сервисам других компьютеров.
Если в качестве IP-адреса
указать несуществующий адрес или адрес
выключенной машины, то программа сообщит
об ошибке при работе вызова connect
()
(правда, возможно, придется подождать
окончания time-out’a).
При задании адреса компьютера, на котором
не работает сервис echo,
об
ошибке станет известно сразу же. Протокол
TCP
является надежным протоколом. Если
логическое соединение установить не
удалось, то отправитель будет знать об
этом.
Как
происходит установление виртуального
соединения
Протокол
TCP
является надежным дуплексным протоколом.
С точки зрения пользователя работа
через протокол TCP
выглядит как обмен информацией через
поток данных. Внутри сетевых частей
операционных систем поток данных
отправителя нарезается на пакеты данных,
которые, собственно, путешествуют по
сети и на машине-получателе вновь
собираются в выходной поток данных. В
лекции 4
речь
шла о том, каким образом может обеспечиваться
надежность передачи информации в
средствах связи, использующих в своей
основе передачу пакетов данных. В
протоколе TCP
используются приемы нумерации передаваемых
пакетов и контроля порядка их получения,
подтверждения о приеме пакета со стороны
получателя и насчет контрольных сумм
по передаваемой информации. Для
правильного порядка получения пакетов
получатель должен знать начальный номер
первого пакета отправителя. Поскольку
связь является дуплексной, и в роли
отправителя пакетов данных могут
выступать обе взаимодействующие стороны,
они до передачи пакетов данных должны
обменяться, по крайней мере, информацией
об их начальных номерах. Согласование
начальных номеров происходит по
инициативе клиента при выполнении
системного вызова connect
().
Для
такого согласования клиент посылает
серверу специальный пакет информации,
который принято называть SYN
(от слова synchronize
— синхронизировать). Он содержит, как
минимум, начальный номер для пакетов
данных, который будет использовать
клиент. Сервер должен подтвердить
получение пакета SYN
от клиента и отправить ему свой пакет
SYN
с начальным номером для пакетов данных,
в виде единого пакета с сегментами SYN
и АСК (от слова acknowledgement
— подтверждение). В ответ клиент пакетом
данных АСК должен подтвердить прием
пакета данных от сервера.
Описанная
выше процедура, получившая название
трехэтапного
рукопожатия (three—way
handshake),
схематично
изображена на рисунке 14-15.8. При приеме
на машине-сервере пакета SYN,
направленного на пассивный (слушающий)
сокет, сетевая часть создает операционной
системе копию этого сокета — присоединенный
сокет — для последующего общения,
отмечая его как сокет сне полностью
установленным соединением. После приема
от клиента пакета АСК этот сокет
переводится в состояние полностью
установленного соединения, и тогда он
готов к дальнейшей работе с использованием
вызовов read
()
и write
().
Системный
вызов listen()
Системный
вызов listen
()
является первым из еще неизвестных нам
вызовов, применяемым на TCP-сервере.
В его задачу входит перевод ТСР-сокета
в пассивное (слушающее) состояние и
создание очередей для порождаемых при
установлении соединения присоединенных
сокетов, находящихся в состоянии не
полностью установленного соединения
и полностью установленного соединения.
Для этого вызов имеет два параметра:
дескриптор ТСР-сокета и число, определяющее
глубину создаваемых очередей.
Клиент
Сервер
Начало
вызова connectQ
Создание
присоединенного сокета в очереди не
полностью установленных соединений
Перевод
присоединенного сокета в очередь
полностью установленных соединений
Рис.
14-15.8. Схема установления ТСР-соединения
Системный
вызов listen()
Прототип
системного вызова
#include
<sys/types.h>
#include
<sys/socket.h>
int
listen{int
sockd,
int
backlog);
Описание
системного вызова
Системный
вызов listen
используется
сервером, ориентированным на установление
связи путем виртуального соединения,
для перевода сокета в пассивный режим
и установления глубины очереди для
соединений.
Параметр
sockd
является
дескриптором созданного ранее сокета,
который должен быть переведен в пассивный
режим, т. е. значением, которое вернул
системный вызов socket
().
Системный
вызов 1
i
st
en
требует
предварительной настройки адреса сокета
с помощью системного вызова bind
().
Параметр
backlog
определяет
максимальный размер очередей для
сокетов, находящихся в состояниях
полностью и не полностью установленных
соединений.
Возвращаемое
значение
Системный
вызов возвращает значение 0
при
нормальном завершении и значение -1
при
возникновении ошибки.
Последний
параметр на разных UNIX-подобных
операционных системах и даже на разных
версиях одной и той же системы может
иметь различный смысл. Где-то это
суммарная длина обеих очередей, где-то
он относится к очереди не полностью
установленных соединений (например,
Linux
до версии ядра 2.2) где-то — к очереди
полностью установленных соединений
(например, Linux,
начиная с версии ядра 2.2), где-то — вообще
игнорируется.
Системный
вызов accept()
Системный
вызов accept
()
позволяет серверу получить информацию
о полностью установленных соединениях.
Если очередь полностью установленных
соединений не пуста, то он возвращает
дескриптор для первого присоединенного
сокета в этой очереди, одновременно
удаляя его из очереди. Если очередь
пуста, то вызов ожидает появления
полностью установленного соединения.
Системный вызов также позволяет серверу
узнать полный адрес клиента, установившего
соединение. У вызова есть три параметра:
дескриптор слушающего сокета, через
который ожидается установление
соединения; указатель на структуру, в
которую при необходимости будет занесен
полный адрес сокета клиента, установившего
соединение; указатель на целую переменную,
содержащую максимально допустимую
длину этого адреса. Как и в случае вызова
recvfromO,
последний
параметр является модернизируемым, а
если нас не интересует, кто с нами
соединился, то вместо второго и третьего
параметров можно указать значение NULL.
Системный
вызов accept()
Прототип
системного вызова
#include
<sys/types.h> #include <sys/socket.h>
int
accept(int sockd, struct sockaddr *cliaddr, int *clilen);
Описание
системного вызова
Системный
вызов accept
используется
сервером, ориентированным на установление
связи путем виртуального соединения,
для приема полностью установленного
соединения.
Параметр
sockd
является
дескриптором созданного и настроенного
сокета, предварительного переведенного
в пассивный (слушающий) режим с помощью
системного вызова listen
().
Системный
вызов accept
требует
предварительной настройки адреса сокета
с помощью системного вызова bind().
Параметр
cliaddr
служит
для получения адреса клиента, установившего
логическое соединение, и должен содержать
указатель на структуру, в которую будет
занесен этот адрес.
Параметр
с
1
i
1
en
содержит
указатель на целую переменную, которая
после возвращения из вызова будет
содержать фактическую длину адреса
клиента. Заметим,
что перед вызовом эта переменная должна
содержать максимально
допустимое значение такой длины. Если
параметр cliaddr
имеет
значение NULL,
то
и параметр clilen
может
иметь значение NULL.
Возвращаемое
значение
Системный
вызов возвращает при нормальном
завершении дескриптор присоединенного
сокега, созданного при установлении
соединения для последующего общения
клиента и сервера, и значение -1
при
возникновении ошибки.
Пример
простого ТСР-сервера
Рассмотрим
программу 14—15-4.С,
реализующую
простой ТСР-сер-вер для сервиса echo:
/*
Пример простого TCP-сервера
для сервиса echo
*/
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<string.h>
#include
<stdio.h>
#include
<errno.h>
#include
<unistd.h>
void
main()
{
int
sockfd, newsockfd; /* Дескрипторы
для
слушающего
и присоединенного сокетов */
int
clilen;
/* Длина адреса клиента */
int
п; /* Количество принятых символов */
char
line[1000];
/* Буфер для приема информации */
struct
sockaddr_in servaddr, cliaddr; /* Структуры
для
размещения полных адресов сервера и
клиента
*/ /* Создаем
ТСР-сокет
*/
if((sockfd
= socket(AF_INET, SOCK_STREAM, 0)) < 0){ perror(NULL); exit(1);
/*
Заполняем структуру для адреса сервера:
семейство протоколов TCP/IP, сетевой
интерфейс — любой, номер порта 51000.
Поскольку
в структуре содержится дополнительное
не нужное нам поле, которое должно быть
нулевым, обнуляем ее всю перед заполнением
*/ bzero(&servaddr,
sizeof(servaddr));
servaddr.sin_family=
AF_INET;
servaddr.sin_port=
htons(51000);
servaddr.sin_addr.s_addr
= htonl(INADDR_ANY);
/* Настраиваем адрес сокета */
іf(bind(sockfd,
(struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
perror(NULL);
close(sockfd);
exit
(1) ;
}
/*
Переводим
созданный
сокет в пассивное (слушающее) состояние.
Глубину очереди для установленных
соединений описываем значением 5 */ if
(listenfsockfd,
5) < 0){
perror(NULL);
close(sockfd);
exit
(1) ;
}
/*
Основной цикл сервера */ while(l){
/*
В переменную clilen
заносим максимальную длину ожидаемого
адреса клиента */ clilen
= sizeof(cliaddr);
/*
Ожидаем полностью установленного
соединения на слушающем сокете. При
нормальном завершении у нас в структуре
cliaddr
будет лежать полный адрес клиента,
установившего соединение, а в переменной
clilen
— его фактическая длина. Вызов же вернет
дескриптор присоединенного сокета,
через который будет происходить общение
с клиентом. Заметим, что информация о
клиенте у нас в дальнейшем никак не
используется, поэтому вместо второго
и третьего параметров можно было
поставить значения NULL.
*/
if((newsockfd = accept(sockfd,
(struct
sockaddr *) &cliaddr, &clilen)) < 0){
perror(NULL);
close(sockfd); exit(1);
}
/*
В цикле принимаем информацию от клиента
до тех пор, пока не произойдет ошибки
(вызов read()
вернет отрицательное значение) или
клиент не закроет соединение (вызов
read()
вернет значение 0). Максимальную длину
одной порции данных от клиента ограничим
999 символами. В
операциях чтения и записи пользуемся
дескриптором присоединенного сокета,
т. е. значением, которое вернул вызов
accept().*/
while((n
= read(newsockfd,
line,
999)) > 0){ /* Принятые данные отправляем
обратно */ if((n
= write(newsockfd,
line,
strlen(line)+1))
< 0){
perror(NULL);
closet
sockfd) ;
close(newsockfd);
exit(1)
;
}
}
/*
Если при чтении возникла ошибка —
завершаем работу */ if(п
< 0){
perror(NULL);
close(sockfd);
close(newsockfd);
exit(1);
}
/*
Закрываем дескриптор присоединенного
сокета и уходим ожидать нового соединения
*/ close(newsockfd);
}
}
Наберите
и откомпилируйте программу. Запустите
ее на выполнение. Модифицируйте текст
программы ТСР-клиента (программа 14—
15-З.с), заменив номер порта с 7 на 51000.
Запустите клиента с другого виртуального
терминала или с другого компьютера и
убедитесь, что клиент и сервер
взаимодействуют корректно.
Создание
программы с параллельной обработкой
запросов клиентов
В
приведенном выше примере сервер
осуществлял последовательную обработку
запросов от разных клиентов. При таком
подходе клиенты могут подолгу простаивать
после установления соединения, ожидая
обслуживания. Поэтому обычно применяется
схема псевдопараллельной обработки
запросов. После приема установленного
соединения сервер порождает процесс-ребенок,
которому и поручает дальнейшую работу
с клиентом. Процесс-родитель закрывает
присоединенный сокет и уходит на ожидание
нового соединения. Схематично организация
такого сервера изображена на рис.
14-15.9.
Напишите,
откомпилируйте и запустите такой
параллельный сервер. Убедитесь в его
работоспособности. Не забудьте о
необходимости удаления зомби-процессов.
Применение
интерфейса сетевых вызовов для других
семейств протоколов. UNIX
Domain
протоколы. Файлы типа «сокет»
Рассмотренный
нами интерфейс умеет работать не только
со стеком протоколов TCP/IP, но и с другими
семействами протоколов. При этом
требуется лишь незначительное изменение
написанных с его помощью программ.
Рассмотрим действия, которые необходимо
выполнить для модернизации написанных
для TCP/IP программ под другое семейство
протоколов:
-
Изменяется
тип сокета, поэтому для его точной
спецификации нужно задавать другие
параметры в системном вызове socket
(). -
В
различных семействах протоколов
применяются различные адресные
пространства для удаленных и локальных
адресов сокетов. Поэтому меняется
состав структуры для хранения полного
адреса сокета, название ее типа,
наименования полей и способ их заполнения. -
Описание
типов данных и предопределенных констант
будет находиться в других include-файлах,
поэтому потребуется заменить include-файлы
<netinet/in.h>
и
<агра/inet.h>
на
файлы, относящиеся к выбранному семейству
протоколов. -
Может
измениться способ вычисления фактической
длины полного адреса сокета и указания
его максимального размера.
И
все!!!
Давайте
подробнее рассмотрим эти изменения на
примере семейства UNIX
Domain
протоколов. Семейство UNIX
Domain
протоколов предназначено для общения
локальных процессов с использованием
интер фейса системных вызовов. Оно
содержит один потоковый и один
дата-граммный протокол. Никакой сетевой
интерфейс при этом не используется, а
вся передача информации реально
происходит через адресное пространство
ядра операционной системы. Многие
программы, взаимодействующие и с
локальными, и с удаленными процессами
(например, Х-ЭДпс!оу8),
для
локального общения используют этот
стек протоколов.
Процесс-ребенок
Закрытие
пассивного сокета
Ожидание
получения информации. Чтение информации
Обработка
информации
Отправление
ответа
Завершение
соединения. Закрытие присоединенного
сокета
closeQ
read
()
writeQ
cIose()
Закрытие
присоединенного сокета
closeQ
Окончание
работы
exit()
Рис.
14-15.9. Схема работы ТСР-сервера с
параллельной обработкой запросов
Поскольку
общение происходит в рамках одной
вычислительной системы, в полном адресе
сокета его удаленная часть отсутствует.
В качестве адресного пространства
портов — локальной части адреса —
выбрано адресное пространство, совпадающее
с множеством всех допустимых имен файлов
в файловой системе.
При
этом в качестве имени сокета требуется
задавать имя несуществующего еще файла
в директории, к которой у вас есть права
доступа как на запись, так и на чтение.
При настройке адреса (системный вызов
bind())
под этим именем будет создан файл типа
«сокет» — последний еще неизвестный
нам тип файла. Этот файл для сокетов
играет роль файла-метки типа FIFO
для именованных pip’oe.
Если на вашей машине функционируют
Х-Windows,
то вы сможете обнаружить такой файл в
директории с именем /стр/
. Xll-unix
—
это файл типа «сокет», служащий для
взаимодействия локальных процессов с
оконным сервером.
Для
хранения полного адреса сокета
используется структура следующего
вида, описанного в файле <sys/un.
h>:
struct
sockaddr __un{
short
sun_family; /* Избранное
семейство
протоколов
— всегда AF_UNIX
*/ char
sun_path[108];
/* Имя файла типа «сокет» */
};
Выбранное
имя файла мы будем копировать внутрь
структуры, используя функциюstrcpy().
Фактическая
длина полного адреса сокета, хранящегося
в структуре с именем my_addr,
может
быть вычислена следующим образом:
sizeof(short)+strlen(my_addr.sun_path).
В Linux
для этих целей можно использовать
специальный макрос языка С:
SUN_LEN(struct
sokaddr_un*)
Ниже
приведены тексты переписанных подсемейство
UNIX
Domain
протоколов клиента и сервера для сервиса
echo
(программы
14— 15-5.c
и
14—15-6.с), общающиеся через датаграммы.
Клиент использует сокет с именем АААА
в
текущей директории, а сервер — сокет с
именем ВВВВ.
Как
следует из описания типа данных, эти
имена (полные или относительные) не
должны по длине превышать 107 символов.
Комментарии даны лишь для изменений по
сравнению с программами 14—15—l.c
и 14— 15-2.c.
/*
A simple echo UNIX Domain datagrairan server */ #include
<sys/types.h> #include <sys/socket.h>
#include
<sys/un.h> /* Новый
include-файл
вместо
netinet/in.h
и
arpa/inet.h */ #include <string.h> #include <stdio.h>
#include <errno.h> #include <unistd.h> int main() {
int
sockfd; int clilen, n; char line[1000];
struct
sockaddr_un servaddr, cliaddr; /* новый
тип
данных под адреса сокетов */ if((sockfd
= socket(AF_UNIX,
SOCK_DGRAM,
0)) < 0) /* Изменен тип семейства протоколов
*/
{
perror(NULL);
exit (1) ;
}
bzero(&servaddr,
sizeof(servaddr));
servaddr.sun_family
= AF_UNIX; /* Изменен
тип
семейства
протоколов и имя поля в структуре */
strcpy(servaddr.sun_path,»ВВВВ»);
/* Локальный
адрес
сокета сервера — ВВВВ
— в текущей
директории */ if(bind(sockfd,
(struct
sockaddr
*) &servaddr,
SUN_LEN(&servaddr))
< 0) /* Изменено вычисление
фактической
длины адреса */
{
perror(NULL);
close(sockfd);
exit(1);
}
while(l)
{
clilen
= sizeof(struct sockaddr_un); /* Изменено
вычисление
максимальной
длины
для
адреса
клиента
*/ if((n = recvfrom(sockfd, line, 999, 0, (struct sockaddr *)
&cliaddr, &clilen)) < 0){
perror(NULL);
close(sockfd)
;
exit(1);
}
if(sendto(sockfd,
line, strlen(line), 0, (struct sockaddr *) &cliaddr, clilen) <
0){
perror(NULL);
close(sockfd); exit(1);
}
}
return
0;
}
/*
A simple echo UNIX Domain datagram client */ #include <sys/types.h>
#include <sys/socket.h>
#include
<sys/un.h> /* Новый
include-файл
вместо
netinet/in.h
и
arpa/inet.h */ #include <string.h> #include <stdio.h>
#include <errno.h> #include <unistd.h>
int
main()
/* Аргументы командной строки не нужны,
так как сервис является локальным, и не
нужно
указывать,
к какой машине мы обращаемся с запросом
*/
{
int
sockfd; int n, len;
char
sendline[1000], recvline[1000];
struct
sockaddr_un servaddr, cliaddr; /* новый
тип
данных под адреса сокетов */
if((sockfd
= socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
/*
Изменен тип семейства протоколов */
{
perror(NULL);
exit(1);
}
bzero(kcliaddr,
sizeof(cliaddr));
cliaddr.sun_family=
AF_UNIX;
/* Изменен тип
семейства
протоколов и имя поля в структуре */
strcpy(cliaddr.sun_path,»АААА»);/*
Локальный адрес
сокета
клиента — АААА — в текущей директории */
if(bind(sockfd,
(struct
sockaddr
*) kcliaddr,
SUN_LEN(kcliaddr))
< 0) /* Изменено вычисление
фактической
длины адреса */
{
perror(NULL);
close(sockfd);
exit(1)
;
}
bzero(&servaddr,
si zeof(servaddr));
servaddr.sun_family
= AF_UNIX; /* Изменен
тип
семейства
протоколов и имя поля в структуре */
strcpy(servaddr.sun_path,»ВВВВ»);
/* Локальный адрес
сокета
сервера — ВВВВ — в текущей директории */
printf(«String
=> «); fgets(sendline,
1000,
stdin);
if(sendto(sockfd,
sendline,
strlen(sendline)+1,
0,
(struct
sockaddr
*) &servaddr,
SUN_LEN(&servaddr))
< 0)
/*
Изменено вычисление фактической длины
адреса */
{
perror(NULL);
close(sockfd); exit (1) ;
}
if((n
= recvfrom(sockfd, recvline, 1000,
0,
(struct
sockaddr *) NULL, NULL)) < 0){
perror(NULL);
close(sockfd);
exit(1)
;
}
recvline[n]
= 0
;
printf(«%s»,
recvline); close(sockfd); return 0;
}
Наберите
программы, откомпилируйте их и убедитесь
в их работоспособности.
Создание
потоковых клиента и сервера для стека
UNIX
Domain
протоколов
По
аналогии с программами в предыдущем
примере модифицируйте тексты программ
TCP
клиента и сервера для сервиса echo
(программы
14—15-3.с и 14— 15-4.с) для потокового общения
в семействе UNIX
Domain
протоколов. Откомпилируйте их и убедитесь
в их правильном функционировании.
Литература
[Bach,
1986] [СС1ТТ, 1991]
[Denning,
1996] [DoD, 1993] [DTI, 1991]
[Intel,
1989] [Linnaes,1789]
[Ritchie,1984]
[Silberschatz,
2002]
[Stevens,
1990]
[Axo,
2001]
[Баурн,1986]
[Беляков,
1991]
[Блэк,
2001] [Брамм, 1990] [Вахалия, 2003] [Дейтел, 1987]
[Дунаев, 1996]
Bach
M.J. The design of the UNIX Operating System.— Prentice-Hall, 1986.
Security
Architecture for Open Systems Interconnection for CCITT Applications.
Recommendations X.800. CCITT. -Geneva. 1991.
Peter
J. Denning. Before memory was virtual, Draft, June 6th 1996, at
http://cne.gmu.edu/pjd/PUBS/bvm.pdf.
Department of Defense. Trusted Computer System Evaluation Criteria. —
DoD 5200.28, STD. 1993. Department of Trade and Industry. Information
Technology Security Evaluation Criteria (ITSEC). Harmonized Criteria
of France — Germany — the Netherlands — the United Kingdom. —
London. 1991. i486™ Microprocessor, Intel Corporation, 1989.
Linnaeus. Karl, Systema naturae, 13lh
ed., t. 1-3 — Lugduni, 1789-96
Ritchie
D.M., The Evolution of the Unix Time-sharing System. // AT&T Bell
Laboratories Technical Journal 63
No.
6, Part 2, October 1984 — pp. 1577-93. Silberschatz A., P.B.Galvin,
Operating System Concepts, 6th edition. — John Willey & Sons,
2002.
Stevens
R. W,
Unix Network Programming — Prentice Hall, Inc., 1990, First
edition.
Axo
В., Хопкрофт Д., Ульман Д. Структуры данных
и алгоритмы. — М.: Вильяме, 2001. Баурн С.
Операционная система UNIX.
— М.: Мир. 1986. Беляков М.И., Рабовер Ю.И.,
Фридман А.Л. Мобильная операционная
система. — М.:, Радио и связь, 1991.
Блэк
У.
Интернет: протоколы безопасности.
Учебный курс. — Спб.: Издательский дом
Питер, 2001. Брамм П., Брамм Д. Микропроцессор
80386 и его применение. — М., Мир, 1990.
Вахалия
Ю. UNIX
изнутри. — Спб.: Издательский дом Питер,
2003.
Дейтел
Г.
Введение в операционные системы. — М.:
Мир, 1987.
Дунаев
С.
Unix. System V.
Release 4.2. — М.:
Диалог
МИФИ,
1996.
[Казаринов,
1990] Казаринов Ю.М., Номоконов В.М.,
Подклетнов Г.С., Филиппов Ф.М.
Микропроцессорный комплекс К1810. — М.:
Высшая школа, 1990.[Кастер, 1996] Кастер
Хелен. Основы Windows
NT
и NTFS.
— М.:
Русская
редакция.1996. [Керниган, 1992] Керниган Б.
В., Пайк P.
UNIX
— универсальная среда программирования.
— М.: Финансы и статистика. 1992. [Коффрон,
1983] Коффрон Дж. Технические средства
микропроцессорных систем. — М.: Мир,
1983.[Кузнецов] Кузнецов С.Д. Операционная
система UNIX.
— http://www.citforum.rU/operating_systems/unix/contents.s
html.
[Олифер,
2000] Олифер В.Г., Олифер Н.А. Новые технологии
и оборудование IP-сетей.
— Спб.: BHV,
2000. [Олифер, 2001] Олифер В.Г., Олифер Н.А.
Сетевые операционные системы. — Спб.:
Издательский дом Питер, 2001. [Олифер,
2002] Олифер В.Г., Олифер Н.А. Компьютерные
сети. Принципы, технологии, протоколы.
— Спб.: Издательский дом Питер, 2002.
[Снейдер, 2001] Снейдер Й. Эффективное
программирование TCP/IP. — Издательский
дом Питер, 2001. [Соломон, 2001] Соломон Д.,
Руссинович М. Внутреннее устройство
Microsoft
Windows
2000. — СПб: Издательский дом Питер,
М.: Русская редакция, 2001. [Стивене, 2002]
Стивене У.
UNIX:
Взаимодействие процессов. —
СПб:
Издательский дом Питер, 2002. [Стивене,
2003] Стивене У.
UNIX:
разработка сетевых приложений. —
СПб:
Издательский дом Питер, 2003. [Столлингс,
2001] Столлингс В., Операционные системы.
— М.:
Вильяме,
2001.
[Таненбаум,
2002] Таненбаум Э. Современные операционные
системы.
— СПб.: Издательский дом Питер, 2002.
[Таненбаум, 2003] Таненбаум Э. Компьютерные
сети. — СПб.:
Издательский
дом Питер, 2003. [Таненбаум II, 2003] Таненбаум
Э., Ван Стеен М. Распределенные системы.
Принципы и парадигмы. — СПб.:
Издательский
дом Питер, 2003. [Робачевский, 1999] Робачевский
А. Операционная система UNIX.
— Спб.: BHV,
1999.
[Цикритис,
1977] Цикритис Д., Бернстайн Ф. Операционные
системы. — М.: Мир. 1977.
Серия
«Основы информационных технологий»
Серия
учебных пособий «Основы информационных
технологий» открыта в издательстве
Интернет-Университета Информационных
Технологий в 2003 году и предполагает
издание более 100 книг. В настоящее время
в ее рамках вышли более 30
учебных пособий по самым разным
направлениям информационных и
коммуникационных технологий. Авторами
этой серии являются известные профессора
и преподаватели ведущих российских
вузов, а также представители компьютерного
бизнеса и академической среды. Ряд
учебных курсов создаются при активном
участии и поддержке ведущих отечественных
и иностранных компаний, а также
общественных организаций и коммерческих
ассоциаций в области информационных
технологий.
Книги
серии
1. Основы
Web-технологий,
П.Б.
Храмцови др., 2003, 512 с, ISBN
5-9556-0001-9.
2. Основы
сетей передачи данных,
В.Г.
Олифер, Н.А. Олифер, 2005, 176 с, ISBN
5-9556-0035-3.
4.5. Функция listen
Функция listen вызывается только сервером TCP и выполняет два действия.
1. Когда сокет создается с помощью функции socket, считается, что это активный сокет, то есть клиентский сокет, который запустит функцию connect. Функция listen преобразует неприсоединенный сокет в пассивный сокет, запросы на подключение к которому начинают приниматься ядром. В терминах диаграммы перехода между состояниями TCP (см. рис. 2.4) вызов функции listen переводит сокет из состояния CLOSED в состояние LISTEN.
2. Второй аргумент этой функции задает максимальное число соединений, которые ядро может помещать в очередь этого сокета.
#include <sys/socket.h>
int listen(int sockfd, int backlog);
Возвращает: 0 в случае успешного выполнения, -1 в случае ошибки
Эта функция обычно вызывается после функций socket и bind. Она должна вызываться перед вызовом функции accept.
Чтобы уяснить смысл аргумента backlog, необходимо понять, что для данного прослушиваемого сокета ядро поддерживает две очереди:
1. Очередь не полностью установленных соединений (incomplete connection queue), содержащую запись для каждого сегмента SYN, пришедшего от клиента, для которого сервер ждет завершения трехэтапного рукопожатия TCP. Эти сокеты находятся в состоянии SYN_RCVD (см. рис. 2.4).
2. Очередь полностью установленных соединений (complete connection queue), содержащую запись для каждого клиента, с которым завершилось трехэтапное рукопожатие TCP. Эти сокеты находятся в состоянии ESTABLISHED (см. рис. 2.4).
На рис. 4.2 представлены обе эти очереди для прослушиваемого сокета.
Рис. 4.2. Две очереди, поддерживаемые прослушиваемым сокетом TCP
Когда в очередь не полностью установленных соединений добавляется новая запись, параметры прослушиваемого сокета копируются на создаваемое соединение. Механизм создания соединения полностью автоматизирован, и процесс сервера в нем не участвует. На рис. 4.3 показан обмен пакетами во время установления соединения с использованием этих очередей.
Рис. 4.3. Обмен пакетами в процессе установления соединения с применением очередей
Когда от клиента приходит сегмент SYN, TCP создает новую запись в очереди не полностью установленных соединений, а затем отвечает вторым сегментом трехэтапного рукопожатия, посылая сегмент SYN вместе с сегментом ACK, подтверждающим прием клиентского сегмента SYN (см. раздел 2.6). Эта запись останется в очереди не полностью установленных соединений, пока не придет третий сегмент трехэтапного рукопожатия (клиентский сегмент ACK для сегмента сервера SYN) или пока не истечет время жизни этой записи. (В реализациях, происходящих от Беркли, время ожидания (тайм-аут) для элементов очереди не полностью установленных соединений равно 75 с.) Если трехэтапное рукопожатие завершается нормально, запись переходит из очереди не полностью установленных соединений в конец очереди полностью установленных соединений. Когда процесс вызывает функцию accept (о которой мы поговорим в следующем разделе), ему возвращается первая запись из очереди полностью установленных соединений, а если очередь пуста, процесс переходит в состояние ожидания до появления записи в ней.
Есть несколько важных моментов, которые нужно учитывать при работе с этими очередями.
? Аргумент backlog функции listen исторически задавал максимальное суммарное значение для обеих очередей.
? Беркли-реализации включают поправочный множитель для аргумента backlog, равный 1,5 [111, с. 257], [128, с. 462]. Например, при типичном значении аргумента backlog = 5 в таких системах допускается до восьми записей в очередях, как показано в табл. 4.6.
ПРИМЕЧАНИЕ
Формального определения аргумента backlog никогда не существовало. В руководстве 4.2BSD сказано, что «он определяет максимальную длину, до которой может вырасти очередь не полностью установленных соединений». Многие руководства и даже POSIX копируют это определение дословно, но в нем не говорится, в каком состоянии должно находится соединение — в состоянии SYN_RCVD, ESTABLISHED (до вызова accept), или же в любом из них. Определение, приведенное выше, относится к реализации Беркли 4.2BSD, и копируется многими другими реализациями.
ПРИМЕЧАНИЕ
Причина возникновения этого множителя теряется в истории [57]. Но если мы рассматриваем backlog как способ задания максимального числа установленных соединений, которые ядро помещает в очередь прослушиваемого сокета (об этом вскоре будет рассказано), этот множитель нужен для учета не полностью установленных соединений, находящихся в очереди [8].
? Не следует задавать нулевое значение аргументу backlog, поскольку различные реализации интерпретируют это по-разному (см. табл. 4.6). Некоторые реализации допускают помещение в очередь одного соединения, в то время как в других вообще невозможно помещать соединения в очередь. Если вы не хотите, чтобы клиенты соединялись с вашим прослушиваемым сокетом, просто закройте прослушиваемый сокет.
? Если трехэтапное рукопожатие завершается нормально (то есть без потерянных сегментов и повторных передач), запись остается в очереди не полностью установленных соединений на время одного периода обращения (round-trip time, RTT), какое бы значение ни имел этот параметр для конкретного соединения между клиентом и сервером. В разделе 14.4 [112] показано, что для одного веб-сервера средний период RTT оказался равен 187 мс. (Чтобы редкие большие числа не искажали картину, здесь использована медиана, а не обычное среднее арифметическое по всем клиентам.)
? Традиционно в примерах кода всегда используется значение backlog, равное 5, поскольку это было максимальное значение, которое поддерживалось в системе 4.2BSD. Это было актуально в 80-х, когда загруженные серверы могли обрабатывать только несколько сотен соединений в день. Но с ростом Сети (WWW), когда серверы обрабатывают миллионы соединений в день, столь малое число стало абсолютно неприемлемым [112, с. 187–192]. Серверам HTTP необходимо намного большее значение аргумента backlog, и новые ядра должны поддерживать такие значения.
ПРИМЕЧАНИЕ
В настоящее время многие системы позволяют администраторам изменять максимальное значение аргумента backlog.
? Возникает вопрос: какое значение аргумента backlog должно задавать приложение, если значение 5 часто является неадекватным? На этот вопрос нет простого ответа. Серверы HTTP сейчас задают большее значение, но если заданное значение является в исходном коде константой, то для увеличения константы требуется перекомпиляция сервера. Другой способ — принять некоторое значение по умолчанию и предоставить возможность изменять его с помощью параметра командной строки или переменной окружения. Всегда можно задавать значение больше того, которое поддерживается ядром, так как ядро должно обрезать значение до максимального, не возвращая при этом ошибку [128, с. 456].
Мы приводим простое решение этой проблемы, изменив нашу функцию-обертку для функции listen. В листинге 4.1[1] представлен действующий код. Переменная окружения LISTENQ позволяет переопределить значение по умолчанию.
Листинг 4.1. Функция-обертка для функции listen, позволяющая переменной окружения переопределить аргумент backlog
//lib/wrapsock.c
137 void
138 Listen(int fd, int backlog)
139 {
140 char *ptr;
141 /* может заменить второй аргумент на переменную окружения */
142 if ((ptr = getenv(«LISTENQ»)) != NULL)
143 backlog = atoi(ptr);
144 if (listen(fd, backlog) < 0)
145 err_sys(«listen error»);
146 }
? Традиционно в руководствах и книгах утверждалось, что помещение фиксированного числа соединений в очередь позволяет обрабатывать случай загруженного серверного процесса между последовательными вызовами функции accept. При этом подразумевается, что из двух очередей больше записей будет содержаться, вероятнее всего, в очереди полностью установленных соединений. Но оказалось, что для действительно загруженных веб-серверов это не так. Причина задания большего значения backlog в том, что очередь не полностью установленных соединений растет по мере поступления сегментов SYN от клиентов; элементы очереди находятся в состоянии ожидания завершения трехэтапного рукопожатия.
? Если очереди заполнены, когда приходит клиентский сегмент SYN, то TCP игнорирует приходящий сегмент SYN [128, с. 930–931] и не посылает RST. Это происходит потому, что состояние считается временным, и TCP клиента должен еще раз передать свой сегмент SYN, для которого в ближайшее время, вероятно, найдется место в очереди. Если бы TCP сервера послал RST, функция connect клиента сразу же возвратила бы ошибку, заставив приложение обработать это условие, вместо того чтобы позволить TCP выполнить повторную передачу. Кроме того, клиент не может увидеть разницу между сегментами RST в ответе на сегмент SYN, означающими, что на данном порте нет сервера либо на данном порте есть сервер, но его очереди заполнены.
ПРИМЕЧАНИЕ
Некоторые реализации отправляют сегмент RST в описанной выше ситуации, что некорректно по изложенным выше причинам. Если вы не пишете клиент специально для работы с подобным сервером, лучше всего игнорировать такую возможность. Ее учет при кодировании клиента снизит его устойчивость и увеличит нагрузку на сеть, если окажется, что порт действительно не прослушивается сервером.
? Данные, которые приходят после завершения трехэтапного рукопожатия, но до того, как сервер вызывает функцию accept, должны помещаться в очередь TCP-сервера, пока не будет заполнен приемный буфер.
В табл. 4.6 показано действительное число установленных в очередь соединений для различных значений аргумента backlog в операционных системах, показанных на рис. 1.7. Семь операционных систем помещены в пять колонок, что иллюстрирует многообразие значений аргумента backlog.
Таблица 4.6. Действительное количество соединений в очереди для различных значений аргумента backlog
backlog
MacOS 10.2.6 AIX 5.1
Linux 2.4.7
HP-UX 11.11
FreeBSD 4.8 FreeBSD 5.1
Solaris 2.9
0
1
3
1
1
1
1
2
4
1
2
2
2
4
5
3
3
4
3
5
6
4
4
5
4
7
7
6
5
6
5
8
8
7
6
8
6
10
9
9
7
10
7
И
10
10
8
11
8
13
11
12
9
13
9
14
12
13
10
14
10
16
13
15
11
16
11
17
14
16
12
17
12
19
15
18
13
19
13
20
16
19
14
20
14
22
17
21
15
22
Системы AIX, BSD/ОХ и SunOS реализуют традиционный алгоритм Беркли, хотя последний не допускает значения аргумента backlog больше пяти. В системах HP-UX и Solaris 2.6 используется другой поправочный множитель к аргументу backlog. Системы Digital Unix, Linux и UnixWare воспринимают этот аргумент буквально, то есть не используют поправочный множитель, а в Solaris 2.5.1 к аргументу backlog просто добавляется единица.
ПРИМЕЧАНИЕ
Программа для измерения этих значений представлена в решении упражнения 15.4.
Как мы отмечали, традиционно аргумент backlog задавал максимальное значение для суммы обеих очередей. В 1996 году была предпринята новая атака через Интернет, названная SYN flooding (лавинная адресация сегмента SYN). Написанная хакером программа отправляет жертве сегменты SYN с высокой частотой, заполняя очередь не полностью установленных соединений для одного или нескольких портов TCP. (Хакером мы называем атакующего, как сказано в предисловии к [20].) Кроме того, IP-адрес отправителя каждого сегмента SYN задается случайным числом — формируются вымышленные IP-адреса (IP spoofing), что ведет к получению доступа обманным путем. Таким образом, сегмент сервера SYN/ACK уходит в никуда. Это не позволяет серверу узнать реальный IP-адрес хакера. Очередь не полностью установленных соединений заполняется ложными сегментами SYN, в результате чего для подлинных сегментов SYN в ней не хватает места — происходит отказ в обслуживании (denial of service) нормальных клиентов. Существует два типичных способа противостояния этим атакам [8]. Но самое интересное в этом примечании — это еще одно обращение к вопросу о том, что на самом деле означает аргумент backlog функции listen. Он должен задавать максимальное число установленных соединений для данного сокета, которые ядро помещает в очередь. Ограничение количества установленных соединений имеет целью приостановить получение ядром новых запросов на соединение для данного сокета, когда их не принимает приложение (по любой причине). Если система реализует именно такую интерпретацию, как, например, BSD/OS 3.0, то приложению не нужно задавать большие значения аргумента backlog только потому, что сервер обрабатывает множество клиентских запросов (например, занятый веб-сервер), или для защиты от «наводнения» SYN (лавинной адресации сегмента SYN). Ядро обрабатывает множество не полностью установленных соединений вне зависимости от того, являются ли они законными или приходят от хакера. Но даже в такой интерпретации мы видим (см. табл. 4.6), что значения 5 тут явно недостаточно.
Данный текст является ознакомительным фрагментом.
Читайте также
Функция pthread_rwlock_rdlock
Функция pthread_rwlock_rdlock
Текст функции pthread_rwlock_rdlock приведен в листинге 8.4.Листинг 8.4. Функция pthread_rwlock_rdlock: получение блокировки на чтение//my_rwlock/pthread_rwlock_rdlock.с1 #include «unpipc.h»2 #include «pthread_rwlock.h»3 int4 pthread_rwlock_rdlock(pthread_rwlock_t *rw)5 {6 int result;7 if (rw->rw_magic != RW_MAGIC)8 return(EINVAL);9 if ((result =
Функция pthread_rwlock_tryrdlock
Функция pthread_rwlock_tryrdlock
В листинге 8.5 показана наша реализация функции pthread_rwlock_tryrdlock, которая не вызывает приостановления вызвавшего ее потока.Листинг 8.5. Функция pthread_rwlock_tryrdlock: попытка заблокировать ресурс для чтения//my_rwlock/pthread_rwlock_tryrdlock.с1 #include «unpipc.h»2 #include
Функция pthread_rwlock_wrlock
Функция pthread_rwlock_wrlock
Текст функции pthread_rwlock_wrlock приведен в листинге 8.6.11-17 Если ресурс заблокирован на считывание или запись (значение rw_refcount отлично от 0), мы приостанавливаем выполнение потока. Для этого мы увеличиваем rw_nwaitwriters и вызываем pthread_cond_wait с условной переменной
Функция pthread_rwlock_unlock
Функция pthread_rwlock_unlock
Последняя функция, pthread_rwlock_unlock, приведена в листинге 8.8.Листинг 8.8. Функция pthread_rwlock_unlock: разблокирование ресурса//my_rwlock/pthread_rwlock_unlock.c1 #include «unpipc.h»2 #include «pthread_rwlock.h»3 int4 pthread_rwlock_unlock(pthread_rwlock_t *rw)5 {6 int result;7 if (rw->rw_magic != RW_MAGIC)8 return(EINVAL);9 if ((result =
Функция sem_open
Функция sem_open
В листинге 10.22 приведен текст функции sem_open, которая создает новый семафор или открывает существующий.Листинг 10.22. Функция sem_open//my_pxsem_fifo/sem_open.с1 #include «unpipc.h»2 #include «semaphore.h»3 #include <stdarg.h> /* для произвольного списка аргументов */4 mysem_t *5 mysem_open(const char *pathname, int
Функция SUM
Функция SUM
Ваши возможности в подведении итогов не ограничены простым подсчетом записей. Используя функцию SUM, можно генерировать итоговые результаты для всех возвращаемых записей по любым числовым полям. Например, для создания запроса, который генерирует итоги по
Функция uni()
Функция uni()
Поиск/замена символа по его юникодному номеру также может быть сделана при помощи функции uni().Пример функции uni(): Boouni(107,32)Designer найдет слово Book
Функция uni()
Функция uni()
Поиск/замена символа по его юникодному номеру также может быть сделана при помощи функции uni().Пример функции uni(): Boouni(107,32)Designer найдет слово Book
Хэш-функция.
Хэш-функция.
Еще одно важное преимущество использования PGP состоит в том, что PGP применяет так называемую «хэш-функцию», которая действует таким образом, что в том случае какого-либо изменения информации, пусть даже на один бит, результат «хэш-функции» будет совершенно
Функция uni()
Функция uni()
Поиск/замена символа по его юникодному номеру также может быть сделана при помощи функции uni().Пример функции uni(): Boouni(107,32)Designer найдет слово Book
Хэш-функция
Хэш-функция
Однако описанная выше схема имеет ряд существенных недостатков. Она крайне медлительна и производит слишком большой объём данных — по меньшей мере вдвое больше объёма исходной информации. Улучшением такой схемы становится введение в процесс преобразования
Перевод сокета в пассивное состояние. Создание очереди соединений
listenQ
accept().
write()
close()
— |
Ожидание |
♦ |
|
г*- |
Ожидание
информации. |
Обработка |
|
Отправление |
|
Окончание |
|
connect()
Передача |
write() |
і |
|
Ожидание |
read() |
Окончание
соединения
Рис.
14-15.7.
Схема
взаимодействия клиента и сервера для
протокола TCP
Установление
логического соединения. Системный вызов
connectQ
Среди
системных вызовов со стороны клиента
появляется только один новый — connect
().
Системный вызов connect
()
при работе с ТСР-сокетами служит для
установления логического соединения
со стороны клиента. Вызов connect
()
скрывает внутри себя настройку сокета
на выбранный системой порт и произвольный
сетевой интерфейс (по сути дела, вызов
bind()
с нулевым номером порта и IP-адресом
INADDR_ANY).
Вызов
блокируется до тех пор, пока не будет
установлено логическое соединение, или
пока не пройдет определенный промежуток
времени, который может регулироваться
системным администратором.
Для
установления соединения необходимо
задать три параметра: дескриптор
активного сокета, через который будет
устанавливаться соединение, полный
адрес сокета сервера и его длину.
Системный
вызов connect()
Прототип
системного вызова
Mnclude
<sys/types.h> Mnclude <sys/socket.h>
int
connect(int sockd, struct sockaddr *servaddr, int addrlen);
Описание
системного вызова
Системный
вызов connect
служит
для организации связи клиента с сервером.
Чаще всего он используется для установления
логического соединения, хотя может быть
применен и при связи с помощью датаграмм
(connectionless).
Данное описание не является полным
описанием системного вызова, а
предназначено только для использования
в нашем курсе. Полную информацию можно
найти в UNIX
Manual.
Параметр
sockd
является
дескриптором созданного ранее
коммуникационного узла, т. е. значением,
которое вернул системный вызов socket
().
Параметр
servaddr
представляет
собой адрес структуры, содержащей
информацию о полном адресе сокета
сервера. Он имеет тип указателя на
структуру-шаблон struct
sockaddr,
которая
должна быть конкретизирована в зависимости
от используемого семейства протоколов
и заполнена перед вызовом.
Параметр
addrlen
должен
содержать фактическую длину структуры,
адрес которой передается в качестве
второго параметра. Эта длина меняется
в зависмости от семейства протоколов
и различается даже в пределах одного
семейства протоколов (например, для
UNIX
Domain).
При
установлении виртуального соединения
системный вызов не возвращается до его
установления или до истечения
установленного в системе времени —
timeout.
При использовании его в connectionless
связи вызов возвращается немедленно.
Возвращаемое
значение
Системный
вызов возвращает значение 0
при
нормальном завершении и отрицательное
значение, если в процессе его выполнения
возникла ошибка.
Пример
программы TCP-клиента
Рассмотрим
пример — программу 14—15-З.с. Это простой
ТСР-кли-ент, обращающийся к стандартному
системному сервису echo.
Стандартный
сервис принимает от клиента текстовую
датаграмму и, не изменяя ее, отправляет
обратно. За сервисом зарезервирован
номер порта 7. Заметим, что это порт 7 TCP
— не путать с портом 7 UDP
из примера в разделе «Пример программы
UDP-клиента»!
Для правильного запуска программы
необходимо указать символьный IP-адрес
сетевого интерфейса компьютера, к
сервису которого требуется обратиться,
в качестве аргумента командной строки,
например:
a.out
192.168.253.12
Для
того чтобы подчеркнуть, что после
установления логического соединения
клиент и сервер могут обмениваться
информацией неоднократно, клиент трижды
запрашивает текст с экрана, отсылает
его серверу и печатает полученный ответ.
Ниже представлен текст программы.
/*
Простой пример TCP-клиента
для сервиса echo
*/
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<string.h>
#include
<stdio.h>
#include
<errno.h>
#include
<unistd.h>
void
main(int argc, char **argv)
{
int
sockfd;
/* Дескриптор сокета */
int
n;
/* Количество переданных или прочитанных
символов
*/ int
i;
/* Счетчик цикла */
char
sendline[1000],recvline[1000]
; /* Массивы для отсылаемой и принятой
строки */ struct
sockaddr_in
servaddr;
/* Структура для
адреса
сервера */ /* Сначала проверяем наличие
второго аргумента в командной строке.
При его отсутствии прекращаем работу
*/ if(argc
!= 2){
printf(«Usage:
a.out
<IP
address>n»);
exit(l);
}
/*
Обнуляем символьные массивы */
bzero(sendline,1000);
bzero(recvline,1000);
/* Создаем ТСР-сокет */
if((sockfd
= socket(PF_INET, SOCK_STREAM, 0)) < 0){ perror(NULL); /* Печатаем
сообщение
об
ошибке
*/
exit
(1) ;
}
/*
Заполняем структуру для адреса сервера:
семейство протоколов TCP/IP, сетевой
интерфейс — из аргумента командной
строки, номер порта 7.
Поскольку в структуре содержится
дополнительное не нужное нам поле,
хоторое должно быть нулевым, перед
заполнением обнуляем ее всю */
bzero(&servaddr,
sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port =
htons(51000);
іf(inet_aton(argv[1],
kservaddr.sin_addr) == 0){ printf(«Invalid IP addressn»);
close(sockfd); exit(1);
}
/*
Устанавливаем логическое соединение
через созданный сокет с сокетом сервера,
адрес которого мы занесли в структуру
*/
if
(connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))
< 0){
perror(NULL);
close(sockfd);
exit(1);
}
/*
Три раза в цикле вводим строку с
клавиатуры, отправляем ее серверу и
читаем полученный ответ */ for(i=0;
i<3;
i++){
printf(«String
=> «); fflush(stdin);
fgets(sendline,
1000, stdin)• if( (n = write(sockfd, sendline, strlen(sendline)+1))
< 0){
perror(«Can’t
writen»);
close(sockfd);
exit(1)
;
}
if
( (n = read(sockfd,recvline, 999)) < 0){ perror(«Can1t
readn»); close(sockfd); exit(1) ;
)
printf(«%s»,
recvline);
}
/*
Завершаем
соединение
*/ close(sockfd);
}
Наберите
и откомпилируйте программу. Перед
запуском узнайте
у своего системного администратора,
запущен
ли в системе стандартный TCP-сервис
echo
и,
если нет, попросите это сделать. Запустите
программу с запросом к сервису своего
компьютера, к сервисам других компьютеров.
Если в качестве IP-адреса
указать несуществующий адрес или адрес
выключенной машины, то программа сообщит
об ошибке при работе вызова connect
()
(правда, возможно, придется подождать
окончания time-out’a).
При задании адреса компьютера, на котором
не работает сервис echo,
об
ошибке станет известно сразу же. Протокол
TCP
является надежным протоколом. Если
логическое соединение установить не
удалось, то отправитель будет знать об
этом.
Как
происходит установление виртуального
соединения
Протокол
TCP
является надежным дуплексным протоколом.
С точки зрения пользователя работа
через протокол TCP
выглядит как обмен информацией через
поток данных. Внутри сетевых частей
операционных систем поток данных
отправителя нарезается на пакеты данных,
которые, собственно, путешествуют по
сети и на машине-получателе вновь
собираются в выходной поток данных. В
лекции 4
речь
шла о том, каким образом может обеспечиваться
надежность передачи информации в
средствах связи, использующих в своей
основе передачу пакетов данных. В
протоколе TCP
используются приемы нумерации передаваемых
пакетов и контроля порядка их получения,
подтверждения о приеме пакета со стороны
получателя и насчет контрольных сумм
по передаваемой информации. Для
правильного порядка получения пакетов
получатель должен знать начальный номер
первого пакета отправителя. Поскольку
связь является дуплексной, и в роли
отправителя пакетов данных могут
выступать обе взаимодействующие стороны,
они до передачи пакетов данных должны
обменяться, по крайней мере, информацией
об их начальных номерах. Согласование
начальных номеров происходит по
инициативе клиента при выполнении
системного вызова connect
().
Для
такого согласования клиент посылает
серверу специальный пакет информации,
который принято называть SYN
(от слова synchronize
— синхронизировать). Он содержит, как
минимум, начальный номер для пакетов
данных, который будет использовать
клиент. Сервер должен подтвердить
получение пакета SYN
от клиента и отправить ему свой пакет
SYN
с начальным номером для пакетов данных,
в виде единого пакета с сегментами SYN
и АСК (от слова acknowledgement
— подтверждение). В ответ клиент пакетом
данных АСК должен подтвердить прием
пакета данных от сервера.
Описанная
выше процедура, получившая название
трехэтапного
рукопожатия (three—way
handshake),
схематично
изображена на рисунке 14-15.8. При приеме
на машине-сервере пакета SYN,
направленного на пассивный (слушающий)
сокет, сетевая часть создает операционной
системе копию этого сокета — присоединенный
сокет — для последующего общения,
отмечая его как сокет сне полностью
установленным соединением. После приема
от клиента пакета АСК этот сокет
переводится в состояние полностью
установленного соединения, и тогда он
готов к дальнейшей работе с использованием
вызовов read
()
и write
().
Системный
вызов listen()
Системный
вызов listen
()
является первым из еще неизвестных нам
вызовов, применяемым на TCP-сервере.
В его задачу входит перевод ТСР-сокета
в пассивное (слушающее) состояние и
создание очередей для порождаемых при
установлении соединения присоединенных
сокетов, находящихся в состоянии не
полностью установленного соединения
и полностью установленного соединения.
Для этого вызов имеет два параметра:
дескриптор ТСР-сокета и число, определяющее
глубину создаваемых очередей.
Клиент
Сервер
Начало
вызова connectQ
Создание
присоединенного сокета в очереди не
полностью установленных соединений
Перевод
присоединенного сокета в очередь
полностью установленных соединений
Рис.
14-15.8. Схема установления ТСР-соединения
Системный
вызов listen()
Прототип
системного вызова
#include
<sys/types.h>
#include
<sys/socket.h>
int
listen{int
sockd,
int
backlog);
Описание
системного вызова
Системный
вызов listen
используется
сервером, ориентированным на установление
связи путем виртуального соединения,
для перевода сокета в пассивный режим
и установления глубины очереди для
соединений.
Параметр
sockd
является
дескриптором созданного ранее сокета,
который должен быть переведен в пассивный
режим, т. е. значением, которое вернул
системный вызов socket
().
Системный
вызов 1
i
st
en
требует
предварительной настройки адреса сокета
с помощью системного вызова bind
().
Параметр
backlog
определяет
максимальный размер очередей для
сокетов, находящихся в состояниях
полностью и не полностью установленных
соединений.
Возвращаемое
значение
Системный
вызов возвращает значение 0
при
нормальном завершении и значение -1
при
возникновении ошибки.
Последний
параметр на разных UNIX-подобных
операционных системах и даже на разных
версиях одной и той же системы может
иметь различный смысл. Где-то это
суммарная длина обеих очередей, где-то
он относится к очереди не полностью
установленных соединений (например,
Linux
до версии ядра 2.2) где-то — к очереди
полностью установленных соединений
(например, Linux,
начиная с версии ядра 2.2), где-то — вообще
игнорируется.
Системный
вызов accept()
Системный
вызов accept
()
позволяет серверу получить информацию
о полностью установленных соединениях.
Если очередь полностью установленных
соединений не пуста, то он возвращает
дескриптор для первого присоединенного
сокета в этой очереди, одновременно
удаляя его из очереди. Если очередь
пуста, то вызов ожидает появления
полностью установленного соединения.
Системный вызов также позволяет серверу
узнать полный адрес клиента, установившего
соединение. У вызова есть три параметра:
дескриптор слушающего сокета, через
который ожидается установление
соединения; указатель на структуру, в
которую при необходимости будет занесен
полный адрес сокета клиента, установившего
соединение; указатель на целую переменную,
содержащую максимально допустимую
длину этого адреса. Как и в случае вызова
recvfromO,
последний
параметр является модернизируемым, а
если нас не интересует, кто с нами
соединился, то вместо второго и третьего
параметров можно указать значение NULL.
Системный
вызов accept()
Прототип
системного вызова
#include
<sys/types.h> #include <sys/socket.h>
int
accept(int sockd, struct sockaddr *cliaddr, int *clilen);
Описание
системного вызова
Системный
вызов accept
используется
сервером, ориентированным на установление
связи путем виртуального соединения,
для приема полностью установленного
соединения.
Параметр
sockd
является
дескриптором созданного и настроенного
сокета, предварительного переведенного
в пассивный (слушающий) режим с помощью
системного вызова listen
().
Системный
вызов accept
требует
предварительной настройки адреса сокета
с помощью системного вызова bind().
Параметр
cliaddr
служит
для получения адреса клиента, установившего
логическое соединение, и должен содержать
указатель на структуру, в которую будет
занесен этот адрес.
Параметр
с
1
i
1
en
содержит
указатель на целую переменную, которая
после возвращения из вызова будет
содержать фактическую длину адреса
клиента. Заметим,
что перед вызовом эта переменная должна
содержать максимально
допустимое значение такой длины. Если
параметр cliaddr
имеет
значение NULL,
то
и параметр clilen
может
иметь значение NULL.
Возвращаемое
значение
Системный
вызов возвращает при нормальном
завершении дескриптор присоединенного
сокега, созданного при установлении
соединения для последующего общения
клиента и сервера, и значение -1
при
возникновении ошибки.
Пример
простого ТСР-сервера
Рассмотрим
программу 14—15-4.С,
реализующую
простой ТСР-сер-вер для сервиса echo:
/*
Пример простого TCP-сервера
для сервиса echo
*/
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<string.h>
#include
<stdio.h>
#include
<errno.h>
#include
<unistd.h>
void
main()
{
int
sockfd, newsockfd; /* Дескрипторы
для
слушающего
и присоединенного сокетов */
int
clilen;
/* Длина адреса клиента */
int
п; /* Количество принятых символов */
char
line[1000];
/* Буфер для приема информации */
struct
sockaddr_in servaddr, cliaddr; /* Структуры
для
размещения полных адресов сервера и
клиента
*/ /* Создаем
ТСР-сокет
*/
if((sockfd
= socket(AF_INET, SOCK_STREAM, 0)) < 0){ perror(NULL); exit(1);
/*
Заполняем структуру для адреса сервера:
семейство протоколов TCP/IP, сетевой
интерфейс — любой, номер порта 51000.
Поскольку
в структуре содержится дополнительное
не нужное нам поле, которое должно быть
нулевым, обнуляем ее всю перед заполнением
*/ bzero(&servaddr,
sizeof(servaddr));
servaddr.sin_family=
AF_INET;
servaddr.sin_port=
htons(51000);
servaddr.sin_addr.s_addr
= htonl(INADDR_ANY);
/* Настраиваем адрес сокета */
іf(bind(sockfd,
(struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
perror(NULL);
close(sockfd);
exit
(1) ;
}
/*
Переводим
созданный
сокет в пассивное (слушающее) состояние.
Глубину очереди для установленных
соединений описываем значением 5 */ if
(listenfsockfd,
5) < 0){
perror(NULL);
close(sockfd);
exit
(1) ;
}
/*
Основной цикл сервера */ while(l){
/*
В переменную clilen
заносим максимальную длину ожидаемого
адреса клиента */ clilen
= sizeof(cliaddr);
/*
Ожидаем полностью установленного
соединения на слушающем сокете. При
нормальном завершении у нас в структуре
cliaddr
будет лежать полный адрес клиента,
установившего соединение, а в переменной
clilen
— его фактическая длина. Вызов же вернет
дескриптор присоединенного сокета,
через который будет происходить общение
с клиентом. Заметим, что информация о
клиенте у нас в дальнейшем никак не
используется, поэтому вместо второго
и третьего параметров можно было
поставить значения NULL.
*/
if((newsockfd = accept(sockfd,
(struct
sockaddr *) &cliaddr, &clilen)) < 0){
perror(NULL);
close(sockfd); exit(1);
}
/*
В цикле принимаем информацию от клиента
до тех пор, пока не произойдет ошибки
(вызов read()
вернет отрицательное значение) или
клиент не закроет соединение (вызов
read()
вернет значение 0). Максимальную длину
одной порции данных от клиента ограничим
999 символами. В
операциях чтения и записи пользуемся
дескриптором присоединенного сокета,
т. е. значением, которое вернул вызов
accept().*/
while((n
= read(newsockfd,
line,
999)) > 0){ /* Принятые данные отправляем
обратно */ if((n
= write(newsockfd,
line,
strlen(line)+1))
< 0){
perror(NULL);
closet
sockfd) ;
close(newsockfd);
exit(1)
;
}
}
/*
Если при чтении возникла ошибка —
завершаем работу */ if(п
< 0){
perror(NULL);
close(sockfd);
close(newsockfd);
exit(1);
}
/*
Закрываем дескриптор присоединенного
сокета и уходим ожидать нового соединения
*/ close(newsockfd);
}
}
Наберите
и откомпилируйте программу. Запустите
ее на выполнение. Модифицируйте текст
программы ТСР-клиента (программа 14—
15-З.с), заменив номер порта с 7 на 51000.
Запустите клиента с другого виртуального
терминала или с другого компьютера и
убедитесь, что клиент и сервер
взаимодействуют корректно.
Создание
программы с параллельной обработкой
запросов клиентов
В
приведенном выше примере сервер
осуществлял последовательную обработку
запросов от разных клиентов. При таком
подходе клиенты могут подолгу простаивать
после установления соединения, ожидая
обслуживания. Поэтому обычно применяется
схема псевдопараллельной обработки
запросов. После приема установленного
соединения сервер порождает процесс-ребенок,
которому и поручает дальнейшую работу
с клиентом. Процесс-родитель закрывает
присоединенный сокет и уходит на ожидание
нового соединения. Схематично организация
такого сервера изображена на рис.
14-15.9.
Напишите,
откомпилируйте и запустите такой
параллельный сервер. Убедитесь в его
работоспособности. Не забудьте о
необходимости удаления зомби-процессов.
Применение
интерфейса сетевых вызовов для других
семейств протоколов. UNIX
Domain
протоколы. Файлы типа «сокет»
Рассмотренный
нами интерфейс умеет работать не только
со стеком протоколов TCP/IP, но и с другими
семействами протоколов. При этом
требуется лишь незначительное изменение
написанных с его помощью программ.
Рассмотрим действия, которые необходимо
выполнить для модернизации написанных
для TCP/IP программ под другое семейство
протоколов:
-
Изменяется
тип сокета, поэтому для его точной
спецификации нужно задавать другие
параметры в системном вызове socket
(). -
В
различных семействах протоколов
применяются различные адресные
пространства для удаленных и локальных
адресов сокетов. Поэтому меняется
состав структуры для хранения полного
адреса сокета, название ее типа,
наименования полей и способ их заполнения. -
Описание
типов данных и предопределенных констант
будет находиться в других include-файлах,
поэтому потребуется заменить include-файлы
<netinet/in.h>
и
<агра/inet.h>
на
файлы, относящиеся к выбранному семейству
протоколов. -
Может
измениться способ вычисления фактической
длины полного адреса сокета и указания
его максимального размера.
И
все!!!
Давайте
подробнее рассмотрим эти изменения на
примере семейства UNIX
Domain
протоколов. Семейство UNIX
Domain
протоколов предназначено для общения
локальных процессов с использованием
интер фейса системных вызовов. Оно
содержит один потоковый и один
дата-граммный протокол. Никакой сетевой
интерфейс при этом не используется, а
вся передача информации реально
происходит через адресное пространство
ядра операционной системы. Многие
программы, взаимодействующие и с
локальными, и с удаленными процессами
(например, Х-ЭДпс!оу8),
для
локального общения используют этот
стек протоколов.
Процесс-ребенок
Закрытие
пассивного сокета
Ожидание
получения информации. Чтение информации
Обработка
информации
Отправление
ответа
Завершение
соединения. Закрытие присоединенного
сокета
closeQ
read
()
writeQ
cIose()
Закрытие
присоединенного сокета
closeQ
Окончание
работы
exit()
Рис.
14-15.9. Схема работы ТСР-сервера с
параллельной обработкой запросов
Поскольку
общение происходит в рамках одной
вычислительной системы, в полном адресе
сокета его удаленная часть отсутствует.
В качестве адресного пространства
портов — локальной части адреса —
выбрано адресное пространство, совпадающее
с множеством всех допустимых имен файлов
в файловой системе.
При
этом в качестве имени сокета требуется
задавать имя несуществующего еще файла
в директории, к которой у вас есть права
доступа как на запись, так и на чтение.
При настройке адреса (системный вызов
bind())
под этим именем будет создан файл типа
«сокет» — последний еще неизвестный
нам тип файла. Этот файл для сокетов
играет роль файла-метки типа FIFO
для именованных pip’oe.
Если на вашей машине функционируют
Х-Windows,
то вы сможете обнаружить такой файл в
директории с именем /стр/
. Xll-unix
—
это файл типа «сокет», служащий для
взаимодействия локальных процессов с
оконным сервером.
Для
хранения полного адреса сокета
используется структура следующего
вида, описанного в файле <sys/un.
h>:
struct
sockaddr __un{
short
sun_family; /* Избранное
семейство
протоколов
— всегда AF_UNIX
*/ char
sun_path[108];
/* Имя файла типа «сокет» */
};
Выбранное
имя файла мы будем копировать внутрь
структуры, используя функциюstrcpy().
Фактическая
длина полного адреса сокета, хранящегося
в структуре с именем my_addr,
может
быть вычислена следующим образом:
sizeof(short)+strlen(my_addr.sun_path).
В Linux
для этих целей можно использовать
специальный макрос языка С:
SUN_LEN(struct
sokaddr_un*)
Ниже
приведены тексты переписанных подсемейство
UNIX
Domain
протоколов клиента и сервера для сервиса
echo
(программы
14— 15-5.c
и
14—15-6.с), общающиеся через датаграммы.
Клиент использует сокет с именем АААА
в
текущей директории, а сервер — сокет с
именем ВВВВ.
Как
следует из описания типа данных, эти
имена (полные или относительные) не
должны по длине превышать 107 символов.
Комментарии даны лишь для изменений по
сравнению с программами 14—15—l.c
и 14— 15-2.c.
/*
A simple echo UNIX Domain datagrairan server */ #include
<sys/types.h> #include <sys/socket.h>
#include
<sys/un.h> /* Новый
include-файл
вместо
netinet/in.h
и
arpa/inet.h */ #include <string.h> #include <stdio.h>
#include <errno.h> #include <unistd.h> int main() {
int
sockfd; int clilen, n; char line[1000];
struct
sockaddr_un servaddr, cliaddr; /* новый
тип
данных под адреса сокетов */ if((sockfd
= socket(AF_UNIX,
SOCK_DGRAM,
0)) < 0) /* Изменен тип семейства протоколов
*/
{
perror(NULL);
exit (1) ;
}
bzero(&servaddr,
sizeof(servaddr));
servaddr.sun_family
= AF_UNIX; /* Изменен
тип
семейства
протоколов и имя поля в структуре */
strcpy(servaddr.sun_path,»ВВВВ»);
/* Локальный
адрес
сокета сервера — ВВВВ
— в текущей
директории */ if(bind(sockfd,
(struct
sockaddr
*) &servaddr,
SUN_LEN(&servaddr))
< 0) /* Изменено вычисление
фактической
длины адреса */
{
perror(NULL);
close(sockfd);
exit(1);
}
while(l)
{
clilen
= sizeof(struct sockaddr_un); /* Изменено
вычисление
максимальной
длины
для
адреса
клиента
*/ if((n = recvfrom(sockfd, line, 999, 0, (struct sockaddr *)
&cliaddr, &clilen)) < 0){
perror(NULL);
close(sockfd)
;
exit(1);
}
if(sendto(sockfd,
line, strlen(line), 0, (struct sockaddr *) &cliaddr, clilen) <
0){
perror(NULL);
close(sockfd); exit(1);
}
}
return
0;
}
/*
A simple echo UNIX Domain datagram client */ #include <sys/types.h>
#include <sys/socket.h>
#include
<sys/un.h> /* Новый
include-файл
вместо
netinet/in.h
и
arpa/inet.h */ #include <string.h> #include <stdio.h>
#include <errno.h> #include <unistd.h>
int
main()
/* Аргументы командной строки не нужны,
так как сервис является локальным, и не
нужно
указывать,
к какой машине мы обращаемся с запросом
*/
{
int
sockfd; int n, len;
char
sendline[1000], recvline[1000];
struct
sockaddr_un servaddr, cliaddr; /* новый
тип
данных под адреса сокетов */
if((sockfd
= socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
/*
Изменен тип семейства протоколов */
{
perror(NULL);
exit(1);
}
bzero(kcliaddr,
sizeof(cliaddr));
cliaddr.sun_family=
AF_UNIX;
/* Изменен тип
семейства
протоколов и имя поля в структуре */
strcpy(cliaddr.sun_path,»АААА»);/*
Локальный адрес
сокета
клиента — АААА — в текущей директории */
if(bind(sockfd,
(struct
sockaddr
*) kcliaddr,
SUN_LEN(kcliaddr))
< 0) /* Изменено вычисление
фактической
длины адреса */
{
perror(NULL);
close(sockfd);
exit(1)
;
}
bzero(&servaddr,
si zeof(servaddr));
servaddr.sun_family
= AF_UNIX; /* Изменен
тип
семейства
протоколов и имя поля в структуре */
strcpy(servaddr.sun_path,»ВВВВ»);
/* Локальный адрес
сокета
сервера — ВВВВ — в текущей директории */
printf(«String
=> «); fgets(sendline,
1000,
stdin);
if(sendto(sockfd,
sendline,
strlen(sendline)+1,
0,
(struct
sockaddr
*) &servaddr,
SUN_LEN(&servaddr))
< 0)
/*
Изменено вычисление фактической длины
адреса */
{
perror(NULL);
close(sockfd); exit (1) ;
}
if((n
= recvfrom(sockfd, recvline, 1000,
0,
(struct
sockaddr *) NULL, NULL)) < 0){
perror(NULL);
close(sockfd);
exit(1)
;
}
recvline[n]
= 0
;
printf(«%s»,
recvline); close(sockfd); return 0;
}
Наберите
программы, откомпилируйте их и убедитесь
в их работоспособности.
Создание
потоковых клиента и сервера для стека
UNIX
Domain
протоколов
По
аналогии с программами в предыдущем
примере модифицируйте тексты программ
TCP
клиента и сервера для сервиса echo
(программы
14—15-3.с и 14— 15-4.с) для потокового общения
в семействе UNIX
Domain
протоколов. Откомпилируйте их и убедитесь
в их правильном функционировании.
Литература
[Bach,
1986] [СС1ТТ, 1991]
[Denning,
1996] [DoD, 1993] [DTI, 1991]
[Intel,
1989] [Linnaes,1789]
[Ritchie,1984]
[Silberschatz,
2002]
[Stevens,
1990]
[Axo,
2001]
[Баурн,1986]
[Беляков,
1991]
[Блэк,
2001] [Брамм, 1990] [Вахалия, 2003] [Дейтел, 1987]
[Дунаев, 1996]
Bach
M.J. The design of the UNIX Operating System.— Prentice-Hall, 1986.
Security
Architecture for Open Systems Interconnection for CCITT Applications.
Recommendations X.800. CCITT. -Geneva. 1991.
Peter
J. Denning. Before memory was virtual, Draft, June 6th 1996, at
http://cne.gmu.edu/pjd/PUBS/bvm.pdf.
Department of Defense. Trusted Computer System Evaluation Criteria. —
DoD 5200.28, STD. 1993. Department of Trade and Industry. Information
Technology Security Evaluation Criteria (ITSEC). Harmonized Criteria
of France — Germany — the Netherlands — the United Kingdom. —
London. 1991. i486™ Microprocessor, Intel Corporation, 1989.
Linnaeus. Karl, Systema naturae, 13lh
ed., t. 1-3 — Lugduni, 1789-96
Ritchie
D.M., The Evolution of the Unix Time-sharing System. // AT&T Bell
Laboratories Technical Journal 63
No.
6, Part 2, October 1984 — pp. 1577-93. Silberschatz A., P.B.Galvin,
Operating System Concepts, 6th edition. — John Willey & Sons,
2002.
Stevens
R. W,
Unix Network Programming — Prentice Hall, Inc., 1990, First
edition.
Axo
В., Хопкрофт Д., Ульман Д. Структуры данных
и алгоритмы. — М.: Вильяме, 2001. Баурн С.
Операционная система UNIX.
— М.: Мир. 1986. Беляков М.И., Рабовер Ю.И.,
Фридман А.Л. Мобильная операционная
система. — М.:, Радио и связь, 1991.
Блэк
У.
Интернет: протоколы безопасности.
Учебный курс. — Спб.: Издательский дом
Питер, 2001. Брамм П., Брамм Д. Микропроцессор
80386 и его применение. — М., Мир, 1990.
Вахалия
Ю. UNIX
изнутри. — Спб.: Издательский дом Питер,
2003.
Дейтел
Г.
Введение в операционные системы. — М.:
Мир, 1987.
Дунаев
С.
Unix. System V.
Release 4.2. — М.:
Диалог
МИФИ,
1996.
[Казаринов,
1990] Казаринов Ю.М., Номоконов В.М.,
Подклетнов Г.С., Филиппов Ф.М.
Микропроцессорный комплекс К1810. — М.:
Высшая школа, 1990.[Кастер, 1996] Кастер
Хелен. Основы Windows
NT
и NTFS.
— М.:
Русская
редакция.1996. [Керниган, 1992] Керниган Б.
В., Пайк P.
UNIX
— универсальная среда программирования.
— М.: Финансы и статистика. 1992. [Коффрон,
1983] Коффрон Дж. Технические средства
микропроцессорных систем. — М.: Мир,
1983.[Кузнецов] Кузнецов С.Д. Операционная
система UNIX.
— http://www.citforum.rU/operating_systems/unix/contents.s
html.
[Олифер,
2000] Олифер В.Г., Олифер Н.А. Новые технологии
и оборудование IP-сетей.
— Спб.: BHV,
2000. [Олифер, 2001] Олифер В.Г., Олифер Н.А.
Сетевые операционные системы. — Спб.:
Издательский дом Питер, 2001. [Олифер,
2002] Олифер В.Г., Олифер Н.А. Компьютерные
сети. Принципы, технологии, протоколы.
— Спб.: Издательский дом Питер, 2002.
[Снейдер, 2001] Снейдер Й. Эффективное
программирование TCP/IP. — Издательский
дом Питер, 2001. [Соломон, 2001] Соломон Д.,
Руссинович М. Внутреннее устройство
Microsoft
Windows
2000. — СПб: Издательский дом Питер,
М.: Русская редакция, 2001. [Стивене, 2002]
Стивене У.
UNIX:
Взаимодействие процессов. —
СПб:
Издательский дом Питер, 2002. [Стивене,
2003] Стивене У.
UNIX:
разработка сетевых приложений. —
СПб:
Издательский дом Питер, 2003. [Столлингс,
2001] Столлингс В., Операционные системы.
— М.:
Вильяме,
2001.
[Таненбаум,
2002] Таненбаум Э. Современные операционные
системы.
— СПб.: Издательский дом Питер, 2002.
[Таненбаум, 2003] Таненбаум Э. Компьютерные
сети. — СПб.:
Издательский
дом Питер, 2003. [Таненбаум II, 2003] Таненбаум
Э., Ван Стеен М. Распределенные системы.
Принципы и парадигмы. — СПб.:
Издательский
дом Питер, 2003. [Робачевский, 1999] Робачевский
А. Операционная система UNIX.
— Спб.: BHV,
1999.
[Цикритис,
1977] Цикритис Д., Бернстайн Ф. Операционные
системы. — М.: Мир. 1977.
Серия
«Основы информационных технологий»
Серия
учебных пособий «Основы информационных
технологий» открыта в издательстве
Интернет-Университета Информационных
Технологий в 2003 году и предполагает
издание более 100 книг. В настоящее время
в ее рамках вышли более 30
учебных пособий по самым разным
направлениям информационных и
коммуникационных технологий. Авторами
этой серии являются известные профессора
и преподаватели ведущих российских
вузов, а также представители компьютерного
бизнеса и академической среды. Ряд
учебных курсов создаются при активном
участии и поддержке ведущих отечественных
и иностранных компаний, а также
общественных организаций и коммерческих
ассоциаций в области информационных
технологий.
Книги
серии
1. Основы
Web-технологий,
П.Б.
Храмцови др., 2003, 512 с, ISBN
5-9556-0001-9.
2. Основы
сетей передачи данных,
В.Г.
Олифер, Н.А. Олифер, 2005, 176 с, ISBN
5-9556-0035-3.
4.5. Функция listen
Функция listen вызывается только сервером TCP и выполняет два действия.
1. Когда сокет создается с помощью функции socket, считается, что это активный сокет, то есть клиентский сокет, который запустит функцию connect. Функция listen преобразует неприсоединенный сокет в пассивный сокет, запросы на подключение к которому начинают приниматься ядром. В терминах диаграммы перехода между состояниями TCP (см. рис. 2.4) вызов функции listen переводит сокет из состояния CLOSED в состояние LISTEN.
2. Второй аргумент этой функции задает максимальное число соединений, которые ядро может помещать в очередь этого сокета.
#include <sys/socket.h>
int listen(int sockfd, int backlog);
Возвращает: 0 в случае успешного выполнения, -1 в случае ошибки
Эта функция обычно вызывается после функций socket и bind. Она должна вызываться перед вызовом функции accept.
Чтобы уяснить смысл аргумента backlog, необходимо понять, что для данного прослушиваемого сокета ядро поддерживает две очереди:
1. Очередь не полностью установленных соединений (incomplete connection queue), содержащую запись для каждого сегмента SYN, пришедшего от клиента, для которого сервер ждет завершения трехэтапного рукопожатия TCP. Эти сокеты находятся в состоянии SYN_RCVD (см. рис. 2.4).
2. Очередь полностью установленных соединений (complete connection queue), содержащую запись для каждого клиента, с которым завершилось трехэтапное рукопожатие TCP. Эти сокеты находятся в состоянии ESTABLISHED (см. рис. 2.4).
На рис. 4.2 представлены обе эти очереди для прослушиваемого сокета.
Рис. 4.2. Две очереди, поддерживаемые прослушиваемым сокетом TCP
Когда в очередь не полностью установленных соединений добавляется новая запись, параметры прослушиваемого сокета копируются на создаваемое соединение. Механизм создания соединения полностью автоматизирован, и процесс сервера в нем не участвует. На рис. 4.3 показан обмен пакетами во время установления соединения с использованием этих очередей.
Рис. 4.3. Обмен пакетами в процессе установления соединения с применением очередей
Когда от клиента приходит сегмент SYN, TCP создает новую запись в очереди не полностью установленных соединений, а затем отвечает вторым сегментом трехэтапного рукопожатия, посылая сегмент SYN вместе с сегментом ACK, подтверждающим прием клиентского сегмента SYN (см. раздел 2.6). Эта запись останется в очереди не полностью установленных соединений, пока не придет третий сегмент трехэтапного рукопожатия (клиентский сегмент ACK для сегмента сервера SYN) или пока не истечет время жизни этой записи. (В реализациях, происходящих от Беркли, время ожидания (тайм-аут) для элементов очереди не полностью установленных соединений равно 75 с.) Если трехэтапное рукопожатие завершается нормально, запись переходит из очереди не полностью установленных соединений в конец очереди полностью установленных соединений. Когда процесс вызывает функцию accept (о которой мы поговорим в следующем разделе), ему возвращается первая запись из очереди полностью установленных соединений, а если очередь пуста, процесс переходит в состояние ожидания до появления записи в ней.
Есть несколько важных моментов, которые нужно учитывать при работе с этими очередями.
? Аргумент backlog функции listen исторически задавал максимальное суммарное значение для обеих очередей.
? Беркли-реализации включают поправочный множитель для аргумента backlog, равный 1,5 [111, с. 257], [128, с. 462]. Например, при типичном значении аргумента backlog = 5 в таких системах допускается до восьми записей в очередях, как показано в табл. 4.6.
ПРИМЕЧАНИЕ
Формального определения аргумента backlog никогда не существовало. В руководстве 4.2BSD сказано, что «он определяет максимальную длину, до которой может вырасти очередь не полностью установленных соединений». Многие руководства и даже POSIX копируют это определение дословно, но в нем не говорится, в каком состоянии должно находится соединение — в состоянии SYN_RCVD, ESTABLISHED (до вызова accept), или же в любом из них. Определение, приведенное выше, относится к реализации Беркли 4.2BSD, и копируется многими другими реализациями.
ПРИМЕЧАНИЕ
Причина возникновения этого множителя теряется в истории [57]. Но если мы рассматриваем backlog как способ задания максимального числа установленных соединений, которые ядро помещает в очередь прослушиваемого сокета (об этом вскоре будет рассказано), этот множитель нужен для учета не полностью установленных соединений, находящихся в очереди [8].
? Не следует задавать нулевое значение аргументу backlog, поскольку различные реализации интерпретируют это по-разному (см. табл. 4.6). Некоторые реализации допускают помещение в очередь одного соединения, в то время как в других вообще невозможно помещать соединения в очередь. Если вы не хотите, чтобы клиенты соединялись с вашим прослушиваемым сокетом, просто закройте прослушиваемый сокет.
? Если трехэтапное рукопожатие завершается нормально (то есть без потерянных сегментов и повторных передач), запись остается в очереди не полностью установленных соединений на время одного периода обращения (round-trip time, RTT), какое бы значение ни имел этот параметр для конкретного соединения между клиентом и сервером. В разделе 14.4 [112] показано, что для одного веб-сервера средний период RTT оказался равен 187 мс. (Чтобы редкие большие числа не искажали картину, здесь использована медиана, а не обычное среднее арифметическое по всем клиентам.)
? Традиционно в примерах кода всегда используется значение backlog, равное 5, поскольку это было максимальное значение, которое поддерживалось в системе 4.2BSD. Это было актуально в 80-х, когда загруженные серверы могли обрабатывать только несколько сотен соединений в день. Но с ростом Сети (WWW), когда серверы обрабатывают миллионы соединений в день, столь малое число стало абсолютно неприемлемым [112, с. 187–192]. Серверам HTTP необходимо намного большее значение аргумента backlog, и новые ядра должны поддерживать такие значения.
ПРИМЕЧАНИЕ
В настоящее время многие системы позволяют администраторам изменять максимальное значение аргумента backlog.
? Возникает вопрос: какое значение аргумента backlog должно задавать приложение, если значение 5 часто является неадекватным? На этот вопрос нет простого ответа. Серверы HTTP сейчас задают большее значение, но если заданное значение является в исходном коде константой, то для увеличения константы требуется перекомпиляция сервера. Другой способ — принять некоторое значение по умолчанию и предоставить возможность изменять его с помощью параметра командной строки или переменной окружения. Всегда можно задавать значение больше того, которое поддерживается ядром, так как ядро должно обрезать значение до максимального, не возвращая при этом ошибку [128, с. 456].
Мы приводим простое решение этой проблемы, изменив нашу функцию-обертку для функции listen. В листинге 4.1[1] представлен действующий код. Переменная окружения LISTENQ позволяет переопределить значение по умолчанию.
Листинг 4.1. Функция-обертка для функции listen, позволяющая переменной окружения переопределить аргумент backlog
//lib/wrapsock.c
137 void
138 Listen(int fd, int backlog)
139 {
140 char *ptr;
141 /* может заменить второй аргумент на переменную окружения */
142 if ((ptr = getenv(«LISTENQ»)) != NULL)
143 backlog = atoi(ptr);
144 if (listen(fd, backlog) < 0)
145 err_sys(«listen error»);
146 }
? Традиционно в руководствах и книгах утверждалось, что помещение фиксированного числа соединений в очередь позволяет обрабатывать случай загруженного серверного процесса между последовательными вызовами функции accept. При этом подразумевается, что из двух очередей больше записей будет содержаться, вероятнее всего, в очереди полностью установленных соединений. Но оказалось, что для действительно загруженных веб-серверов это не так. Причина задания большего значения backlog в том, что очередь не полностью установленных соединений растет по мере поступления сегментов SYN от клиентов; элементы очереди находятся в состоянии ожидания завершения трехэтапного рукопожатия.
? Если очереди заполнены, когда приходит клиентский сегмент SYN, то TCP игнорирует приходящий сегмент SYN [128, с. 930–931] и не посылает RST. Это происходит потому, что состояние считается временным, и TCP клиента должен еще раз передать свой сегмент SYN, для которого в ближайшее время, вероятно, найдется место в очереди. Если бы TCP сервера послал RST, функция connect клиента сразу же возвратила бы ошибку, заставив приложение обработать это условие, вместо того чтобы позволить TCP выполнить повторную передачу. Кроме того, клиент не может увидеть разницу между сегментами RST в ответе на сегмент SYN, означающими, что на данном порте нет сервера либо на данном порте есть сервер, но его очереди заполнены.
ПРИМЕЧАНИЕ
Некоторые реализации отправляют сегмент RST в описанной выше ситуации, что некорректно по изложенным выше причинам. Если вы не пишете клиент специально для работы с подобным сервером, лучше всего игнорировать такую возможность. Ее учет при кодировании клиента снизит его устойчивость и увеличит нагрузку на сеть, если окажется, что порт действительно не прослушивается сервером.
? Данные, которые приходят после завершения трехэтапного рукопожатия, но до того, как сервер вызывает функцию accept, должны помещаться в очередь TCP-сервера, пока не будет заполнен приемный буфер.
В табл. 4.6 показано действительное число установленных в очередь соединений для различных значений аргумента backlog в операционных системах, показанных на рис. 1.7. Семь операционных систем помещены в пять колонок, что иллюстрирует многообразие значений аргумента backlog.
Таблица 4.6. Действительное количество соединений в очереди для различных значений аргумента backlog
backlog
MacOS 10.2.6 AIX 5.1
Linux 2.4.7
HP-UX 11.11
FreeBSD 4.8 FreeBSD 5.1
Solaris 2.9
0
1
3
1
1
1
1
2
4
1
2
2
2
4
5
3
3
4
3
5
6
4
4
5
4
7
7
6
5
6
5
8
8
7
6
8
6
10
9
9
7
10
7
И
10
10
8
11
8
13
11
12
9
13
9
14
12
13
10
14
10
16
13
15
11
16
11
17
14
16
12
17
12
19
15
18
13
19
13
20
16
19
14
20
14
22
17
21
15
22
Системы AIX, BSD/ОХ и SunOS реализуют традиционный алгоритм Беркли, хотя последний не допускает значения аргумента backlog больше пяти. В системах HP-UX и Solaris 2.6 используется другой поправочный множитель к аргументу backlog. Системы Digital Unix, Linux и UnixWare воспринимают этот аргумент буквально, то есть не используют поправочный множитель, а в Solaris 2.5.1 к аргументу backlog просто добавляется единица.
ПРИМЕЧАНИЕ
Программа для измерения этих значений представлена в решении упражнения 15.4.
Как мы отмечали, традиционно аргумент backlog задавал максимальное значение для суммы обеих очередей. В 1996 году была предпринята новая атака через Интернет, названная SYN flooding (лавинная адресация сегмента SYN). Написанная хакером программа отправляет жертве сегменты SYN с высокой частотой, заполняя очередь не полностью установленных соединений для одного или нескольких портов TCP. (Хакером мы называем атакующего, как сказано в предисловии к [20].) Кроме того, IP-адрес отправителя каждого сегмента SYN задается случайным числом — формируются вымышленные IP-адреса (IP spoofing), что ведет к получению доступа обманным путем. Таким образом, сегмент сервера SYN/ACK уходит в никуда. Это не позволяет серверу узнать реальный IP-адрес хакера. Очередь не полностью установленных соединений заполняется ложными сегментами SYN, в результате чего для подлинных сегментов SYN в ней не хватает места — происходит отказ в обслуживании (denial of service) нормальных клиентов. Существует два типичных способа противостояния этим атакам [8]. Но самое интересное в этом примечании — это еще одно обращение к вопросу о том, что на самом деле означает аргумент backlog функции listen. Он должен задавать максимальное число установленных соединений для данного сокета, которые ядро помещает в очередь. Ограничение количества установленных соединений имеет целью приостановить получение ядром новых запросов на соединение для данного сокета, когда их не принимает приложение (по любой причине). Если система реализует именно такую интерпретацию, как, например, BSD/OS 3.0, то приложению не нужно задавать большие значения аргумента backlog только потому, что сервер обрабатывает множество клиентских запросов (например, занятый веб-сервер), или для защиты от «наводнения» SYN (лавинной адресации сегмента SYN). Ядро обрабатывает множество не полностью установленных соединений вне зависимости от того, являются ли они законными или приходят от хакера. Но даже в такой интерпретации мы видим (см. табл. 4.6), что значения 5 тут явно недостаточно.
Данный текст является ознакомительным фрагментом.
Читайте также
Функция pthread_rwlock_rdlock
Функция pthread_rwlock_rdlock
Текст функции pthread_rwlock_rdlock приведен в листинге 8.4.Листинг 8.4. Функция pthread_rwlock_rdlock: получение блокировки на чтение//my_rwlock/pthread_rwlock_rdlock.с1 #include «unpipc.h»2 #include «pthread_rwlock.h»3 int4 pthread_rwlock_rdlock(pthread_rwlock_t *rw)5 {6 int result;7 if (rw->rw_magic != RW_MAGIC)8 return(EINVAL);9 if ((result =
Функция pthread_rwlock_tryrdlock
Функция pthread_rwlock_tryrdlock
В листинге 8.5 показана наша реализация функции pthread_rwlock_tryrdlock, которая не вызывает приостановления вызвавшего ее потока.Листинг 8.5. Функция pthread_rwlock_tryrdlock: попытка заблокировать ресурс для чтения//my_rwlock/pthread_rwlock_tryrdlock.с1 #include «unpipc.h»2 #include
Функция pthread_rwlock_wrlock
Функция pthread_rwlock_wrlock
Текст функции pthread_rwlock_wrlock приведен в листинге 8.6.11-17 Если ресурс заблокирован на считывание или запись (значение rw_refcount отлично от 0), мы приостанавливаем выполнение потока. Для этого мы увеличиваем rw_nwaitwriters и вызываем pthread_cond_wait с условной переменной
Функция pthread_rwlock_unlock
Функция pthread_rwlock_unlock
Последняя функция, pthread_rwlock_unlock, приведена в листинге 8.8.Листинг 8.8. Функция pthread_rwlock_unlock: разблокирование ресурса//my_rwlock/pthread_rwlock_unlock.c1 #include «unpipc.h»2 #include «pthread_rwlock.h»3 int4 pthread_rwlock_unlock(pthread_rwlock_t *rw)5 {6 int result;7 if (rw->rw_magic != RW_MAGIC)8 return(EINVAL);9 if ((result =
Функция sem_open
Функция sem_open
В листинге 10.22 приведен текст функции sem_open, которая создает новый семафор или открывает существующий.Листинг 10.22. Функция sem_open//my_pxsem_fifo/sem_open.с1 #include «unpipc.h»2 #include «semaphore.h»3 #include <stdarg.h> /* для произвольного списка аргументов */4 mysem_t *5 mysem_open(const char *pathname, int
Функция SUM
Функция SUM
Ваши возможности в подведении итогов не ограничены простым подсчетом записей. Используя функцию SUM, можно генерировать итоговые результаты для всех возвращаемых записей по любым числовым полям. Например, для создания запроса, который генерирует итоги по
Функция uni()
Функция uni()
Поиск/замена символа по его юникодному номеру также может быть сделана при помощи функции uni().Пример функции uni(): Boouni(107,32)Designer найдет слово Book
Функция uni()
Функция uni()
Поиск/замена символа по его юникодному номеру также может быть сделана при помощи функции uni().Пример функции uni(): Boouni(107,32)Designer найдет слово Book
Хэш-функция.
Хэш-функция.
Еще одно важное преимущество использования PGP состоит в том, что PGP применяет так называемую «хэш-функцию», которая действует таким образом, что в том случае какого-либо изменения информации, пусть даже на один бит, результат «хэш-функции» будет совершенно
Функция uni()
Функция uni()
Поиск/замена символа по его юникодному номеру также может быть сделана при помощи функции uni().Пример функции uni(): Boouni(107,32)Designer найдет слово Book
Хэш-функция
Хэш-функция
Однако описанная выше схема имеет ряд существенных недостатков. Она крайне медлительна и производит слишком большой объём данных — по меньшей мере вдвое больше объёма исходной информации. Улучшением такой схемы становится введение в процесс преобразования