Книга: UNIX: разработка сетевых приложений
Глава 7
Глава 7
7.2. Решение упражнения приведено в листинге Д.2. Вывод строки данных, возвращаемых сервером, был удален, поскольку это значение нам не нужно.
Листинг Д.2. Вывод размера приемного буфера сокета и MSS до и после установления соединения
//sockopt/rcvbuf.c
1 #include "urp.h"
2 #include <netinet/tcp.h> /* для TCP_MAXSEG */
3 int
4 main(int argc, char **argv)
5 {
6 int sockfd, rcvbuf, mss;
7 socklen_t len;
8 struct sockaddr_in servaddr;
9 if (argc != 2)
10 err_quit("usage: rcvbuf <Ipaddress>");
11 sockfd = Socket(AF_INET, SOCK_STREAM, 0);
12 len = sizeof(rcvbuf);
13 Getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len);
14 len = sizeof(mss);
15 Getsockopt(sockfd, IPPROTO_TCP, TCP_MAXSEG, &mss, &len);
16 printf("defaults: SO_RCVBUF = %d. MSS = %dn", rcvbuf, mss);
17 bzero(&servaddr, sizeof(servaddr));
18 servaddr.sin_family = AF_INET;
19 servaddr.sin_port = htons(13); /* сервер времени и даты */
20 Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
21 Connect(sockfd, (SA*)&servaddr, sizeof(servaddr));
22 len = sizeof(rcvbuf);
23 Getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len);
24 len = sizeof(mss);
25 Getsockopt(sockfd, IPPROTO_TCP, TCP_MAXSEG, &mss, &len);
26 printf("after connect: SO_RCVBUF = %d, MSS = %dn", rcvbuf, mss);
27 exit(0);
28 }
He существует какого-то одного «правильного» вывода для данной программы. Результаты зависят от системы. Некоторые системы (в особенности Solaris 2.5.1 и более ранние версии) всегда возвращают нулевой размер буфера сокета, не давая нам возможности увидеть, что происходит с этим значением в процессе соединения.
До вызова функции connect
выводится значение MSS по умолчанию (часто 536 или 512), а значение, выводимое после вызова функции connect, зависит от возможных параметров MSS, полученных от собеседника. Например, в локальной сети Ethernet после выполнения функции connect MSS может иметь значение 1460. Однако после соединения (connect
) с сервером в удаленной сети значение MSS может быть равно значению по умолчанию, если только ваша система не поддерживает обнаружение транспортной MTU. Если это возможно, запустите во время работы вашей программы программу tcpdump
или подобную ей (см. раздел В.5), чтобы увидеть фактическое значение параметра MSS в сегменте SYN, полученном от собеседника.
Многие реализации после установления соединения округляют размер приемного буфера сокета в большую сторону, чтобы он было кратным MSS. Чтобы узнать размер приемного буфера сокета после установления соединения, можно исследовать пакеты с помощью программы типа tcpdump
и посмотреть, каков размер объявленного окна TCP.
7.3. Разместите в памяти структуру linger
по имени ling
и проинициализируйте ее следующим образом:
str_cli(stdin, sockfd);
ling.l_onoff = 1;
ling.l_linger = 0;
Setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
exit(0);
Это заставит TCP на стороне клиента прекратить работу путем отправки сегмента RST вместо нормального обмена четырьмя сегментами. Дочерний процесс сервера вызывает функцию readline
, возвращает ошибку ECONNRESET
и выводит следующее сообщение:
readline error: Connection reset by peer
Клиентский сокет не должен проходить через состояние ожидания TIME_WAIT, даже если клиент выполняет активное закрытие.
7.4. Первый клиент вызывает функции setsockopt
, bind
и connect
. Но если второй клиент вызовет функцию bind
между вызовами функций bind
и connect
первого клиента, возвращается ошибка EADDRINUSE
. Но как только первый клиент установит соединение с собеседником, вызов функции bind
второго клиента будет работать, поскольку сокет первого клиента уже присоединен. В случае возвращения ошибки EADDRINUSE
второму клиенту следует вызывать bind
несколько раз, а не останавливаться при появлении первой ошибки — это единственный способ справиться с данной ситуацией.
7.5. Запускаем программу на узле без поддержки многоадресной передачи (MacOS X 10.2.6).
macosx % sock -s 9999 & запускаем первый сервер с универсальным адресом
[1] 29697
macosx % sock -s 172.24.37.78 9999 пробуем второй сервер, но без -А
can't bind local address: Address already in use
macosx % sock -s -A 172.24.37.78 9999 & пробуем опять с -A: работает
[2] 29699
macosx % sock -s -A 127.0.0.1 9999 & третий сервер с -A; работает
[3] 29700
macosx % netstat -na | grep 9999
tcp4 0 0 127.0.0.1.9999 *.* LISTEN
tcp4 0 0 206.62.226.37.9999 *.* LISTEN
tcp4 0 0 *.9999 *.* LISTEN
7.6. Теперь попробуем проделать то же на узле с поддержкой многоадресной передачи, но без поддержки параметра SO_REUSEADDR
(Solaris 9).
solaris % sock -s -u 8888 & запускаем первый
[1] 24051
solaris % sock -s -u 8888
can't bind local address: Address already in use
solaris % sock -s -u -A 8888 & снова пробуем запустить второй с -A:
работает
solaris % netstat -na | grep 8888 мы видим дублированное связывание
*.8888 Idle
* 8888 Idle
В этой системе задавать параметр SO_REUSEADDR
было необходимо только для второго связывания. Наконец, запускаем сценарий в MacOS X 10.2.6, где поддерживается как многоадресная передача, так и параметр SO_REUSEPORT
. Сначала пробуем использовать SO_REUSEADDR
для обоих серверов, но это не работает.
macosx % sock -u -s -A 7777 &
[1] 17610
macosx % sock -u -s -A 7777
can't bind local address: Address already in use
Тогда пробуем использовать параметр SO_REUSEPORT
только для второго сервера. Это также не работает, так как полностью дублированное связывание требует включения данного параметра для всех сокетов, совместно использующих соединение.
macosx % sock -u -s 8888 &
[1] 17612
macosx % sock -u -s -T 8888
can't bind local address: Address already in use
Наконец, задаем параметр SO_REUSEPORT
для обоих серверов, и этот вариант работает.
macosx % sock -u -s -Т 9999 &
[1] 17614
macosx % sock -u -s -T 9999 &
[2] 17615
macosx % netstat -na | grep 9999
udp4 0 0 *.9999 *.*
udp4 0 0 *.9999 *.*
7.7. Этот параметр (-d
) не делает ничего, поскольку программа ping
использует ICMP-сокет, а параметр сокета SO_DEBUG
влияет только на TCP-сокеты. Описание параметра сокета SO_DEBUG
всегда было довольно расплывчатым, наподобие «этот параметр допускает отладку на соответствующем уровне протокола», и единственный уровень протокола, где реализуется данный параметр — это TCP.
7.8. Временная диаграмма приведена на рис. Д.4.
Рис. Д.4. Взаимодействие алгоритма Нагла с задержанными сегментами ACK
7.9. Установка параметра сокета TCP_NODELAY
приводит к немедленной отправке данных из второй функции write
, даже если имеется еще один небольшой пакет, ожидающий отправки. Это показано на рис. Д.5. Полное время в данном примере превышает 150 мс.
Рис Д.5. Предотвращение алгоритма Нагла путем установки параметра TCP_NODELAY
7.10. Как показано на рис. Д.6, преимущество данного решения состоит в уменьшении числа пакетов.
Рис. Д.6. Использование функции writev вместо параметра сокета TCP_NODELAY
7.11. В разделе 4.2.3.2 говорится: «задержка ДОЛЖНА быть меньше 0,5 с, а в потоке полноразмерных сегментов СЛЕДУЕТ использовать сегмент ACK по крайней мере для каждого второго сегмента». Беркли-реализации задерживают сегмент ACK не более, чем на 200 мс [128, с. 821].
7.12. Родительский процесс сервера в листинге 5.1 большую часть времени блокирован в вызове функции accept
, а дочерний процесс в листинге 5.2 большую часть времени блокирован в вызове функции read
, который содержится в функции readline
. Проверка работоспособности с помощью параметра SO_KEEPALIVE
не влияет на прослушиваемый сокет, поэтому в случае, если клиентский узел выйдет из строя, родительский процесс не пострадает. Функция read дочернего процесса возвратит ошибку ETIMEDOUT
примерно через 2 ч после последнего обмена данными через соединение.
7.13. Клиент, приведенный в листинге 5.4, большую часть времени блокирован вызовом функции fgets
, который, в свою очередь, блокирован операцией чтения из стандартной библиотеки ввода-вывода на стандартном устройстве ввода. Когда примерно через 2 ч после последнего обмена данными через соединение истечет время таймера проверки работоспособности и проверочные сообщения не выявят работоспособности сервера, ошибка сокета, ожидающая обработки, примет значение ETIMEDOUT
. Но клиент блокирован вызовом функции fgets
, поэтому он не увидит этой ошибки, пока не осуществит чтение или запись на сокете. Это одна из причин, по которой в главе 6 листинг 5.4 был изменен таким образом, чтобы использовать функцию select
.
7.14. Этот клиент большую часть времени блокирован вызовом функции select
, которая сообщит, что сокет готов для чтения, как только ожидающая обработки ошибка будет установлена в ETIMEDOUT
(как показано в предыдущем решении).
7.15. Происходит обмен только двумя сегментами, а не четырьмя. Вероятность того, что таймеры двух систем будут строго синхронизированы, очень мала, следовательно, на одном конце соединения таймер проверки работоспособности сработает немного раньше, чем на другом. Первый из сработавших таймеров посылает проверочное сообщение, заставляя другой конец послать в ответ сегмент ACK. Но получение проверочного сообщения приводит к тому, что таймеру проверки работоспособности с более медленными часами будет присвоено новое значение — он сдвинется на 2 ч вперед.
7.16 Изначально в API сокетов не было функции listen
. Вместо этого четвертый аргумент функции socket
содержал параметр сокета, а параметр SO_ACCEPTCONN
использовался для задания прослушиваемого сокета. Когда добавилась функция listen
, флаг остался, но теперь его может устанавливать только ядро [128, с. 456].
- Слова благодарности
- 1.4.2.2. OSDL: Mobile Linux Initiative
- Состав сайта
- Предложения, которым недостает прямоты
- Поляризующий блок
- Другие интерфейсы программирования для Windows
- Законность спама
- 11.5. ПАО «ЛУКОЙЛ»
- Выводы для розничных магазинов
- Изучение среды международного маркетинга
- Приложение Словарь компьютерных терминов
- Немного о себе