UNIX: разработка сетевых приложений - Уильям Стивенс
Шрифт:
Интервал:
Закладка:
8.6. Запустим программу sock с параметром -u (использовать UDP) и параметром -l (определяет локальный адрес и порт) на многоинтерфейсном узле freebsd.
freebsd % sock -u -l 12.106.32.254.4444 192.168.42.2 8888
hello
Локальный IP-адрес подключен к Интернету (см. рис. 1.7), но чтобы достичь получателя, дейтаграмма должна выйти через другой интерфейс. Наблюдая за сетью с помощью программы tcpdump, мы увидим, что IP-адрес отправителя, связанный с клиентом, не является адресом исходящего интерфейса.
14:28:29.614846 12.106.32.254.444 > 192.168.42.2.8888. udp 6
14:28:29.615255 192.168.42.2 > 12 106.32.254: icmp: 192.168 42.2
udp port 8888 unreachable
8.7. Использование функции printf на стороне клиента приведет к возникновению задержки между отправками дейтаграмм, что позволит серверу получать большее количество дейтаграмм. Использование функции printf на стороне сервера приведет к тому, что сервер будет терять большее количество дейтаграмм.
8.8. Наибольший размер IPv4-дейтаграммы составляет 65 535 байт и ограничивается 16-разрядным полем полной длины, показанным на рис. А.1. IP-заголовок требует 20 байт, UDP-заголовок — 8 байт, и для пользовательских данных остается не более 65 507 байт. В IPv6 (без поддержки джумбограмм) размер IP-заголовка составляет 40 байт, и под пользовательские данные отводится 65 487 байт.
В листинге Д.3 приведена новая версия dg_cli. Если забыть установить размер буфера отправки, Беркли-ядра возвратят из функции sendto ошибку EMSGSIZE, поскольку размер буфера отправки сокета обычно меньше, чем максимально возможный размер UDP-дейтаграммы (чтобы убедиться в этом, выполните упражнение 7.1).
Листинг Д.3. Запись дейтаграммы UDP/IPv4 максимального размера
//udpcliserv/dgclibig.c
1 #include "unp.h"
2 #undef MAXLINE
3 #define MAXLINE 65507
4 void
5 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
6 {
7 int size;
8 char sendline[MAXLINE], recvline[MAXLINE + 1];
9 ssize_t n;
10 size = 70000;
11 Setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
12 Setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
13 Sendto(sockfd, sendline, MAXLINE, 0, pservaddr, servlen);
14 n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
15 printf("received %d bytesn", n);
16 }
Но если установить размеры буферов сокета клиента, как показано в листинге Д.3, и запустить программу, сервер ничего не возвратит. С помощью программы tcpdump можно убедиться, что клиентская дейтаграмма отправляется серверу, но если в сервер поместить функцию printf, вызов функции recvfrom не возвратит дейтаграмму. Проблема заключается в том, что приемный буфер UDP-сокета сервера меньше, чем посланная нами дейтаграмма, поэтому дейтаграмма отбрасывается и не доставляется на сокет. В системах BSD/OS это можно проверить, запустив программу netstat -s и проверив счетчик, указывающий количество дейтаграмм, отброшенных из-за переполнения буферов сокета (dropped due to full socket buffers), до и после получения нашей длинной дейтаграммы. Решением является модификация сервера путем задания размеров буферов приема и отправки сокета.
В большинстве сетей дейтаграмма длиной 65 535 байт фрагментируется. Как отмечалось в разделе 2.9, IP-уровнем должен поддерживаться размер буфера для сборки фрагментов, равный всего лишь 576 байт. Поэтому некоторые узлы не получат дейтаграмму максимального размера, посылаемую в данном упражнении. Кроме того, во многих Беркли-реализациях, включая 4.4BSD-Lite2, имеется ошибка, связанная со знаковыми типами данных, которая не позволяет UDP принимать дейтаграммы больше, чем 32 767 байт (см. строка 95, с. 770 [128]).
Глава 9
9.1. В некоторых ситуациях функция sctp_peeloff может оказаться очень полезной. Примером приложения, которому может понадобиться эта функция, является традиционный сервер дейтаграмм, обрабатывающий небольшие транзакции, которому периодически приходится устанавливать долговременные соединения. Чаще всего сервер передает одно-два коротких сообщения, но время от времени поступает запрос на проверку базы сервера, и тогда ему приходится передавать большие объемы данных. В такой ситуации имеет смысл отделить ассоциацию, по которой передаются проверочные данные, для обработки ее отдельным процессом или потоком.
9.2. На стороне сервера выполняется автоматическое закрытие после закрытия ассоциации клиентом. SCTP не поддерживает состояние неполного закрытия, поэтому когда клиент вызывает close, все подготовленные сервером данные сбрасываются и ассоциация закрывается.
9.3. Сокет типа «один-к-одному» требует вызова connect, поэтому когда собеседнику отсылается сегмент COOKIE, никаких данных в буфере отправки быть еще не может. Сокет типа «один-ко-многим» допускает отправку данных с одновременной установкой соединения. Поэтому сегмент COOKIE в этом случае может быть совмещен с сегментом DATA.
9.4. Собеседник, с которым устанавливается ассоциация, может прислать данные только в том случае, если у него будет готов сегмент DATA до того, как соединение будет установлено, то есть если на обеих сторонах используются сокеты типа «один-ко-многим» и каждая сторона выполняет операцию send с неявной установкой соединения. Такой процесс установки ассоциации называется коллизией пакетов INIT и подробно описывается в главе 4 [117].
9.5. В некоторых случаях не все связанные адреса могут быть переданы собеседнику. В частности, если приложение связало с сокетом как частные, так и общие IP-адреса, собеседник получит информацию только об общих IP-адресах. Еще одним примером являются локальные в рамках канала адреса IPv6, которые не обязательно сообщаются собеседнику.
Глава 10
10.1 Если функция sctp_sendmsg возвращает ошибку, сообщение не будет отправлено, а приложение вызовет функцию sctp_recvmsg и заблокируется в ней навсегда, ожидая ответного сообщения, которое никогда не придет.
Чтобы избежать этой неприятности, нужно проверять коды возврата. Если при отправке возникла ошибка, клиент не должен пытаться получить ответ. Ему следует просто сообщить о возникшей ошибке.
Если функция sctp_recvmsg вернет ошибку, никаких сообщений получено не будет, но сервер все равно попытается отправить сообщение, что может привести к установлению ассоциации. Для предотвращения этого следует проверять код ошибки и, в зависимости от его значения, сообщать об ошибке и закрывать сокет (при этой операции также может быть возвращена ошибка) или повторно вызывать sctp_recvmsg.
10.2. Если сервер получает запрос и завершает работу, клиент (в его нынешней форме) зависает навечно в ожидании ответа сервера. Клиенту следует включить доставку уведомлений о событиях для данной ассоциации. Когда сервер завершит работу, клиент получит соответствующее сообщение и сможет принять какие-либо меры, например связаться с другим сервером. Альтернативным решением может быть установка таймера и завершение работы по истечении времени ожидания.
10.3. Чтобы каждая порция данных была помещена в свой пакет, мы установили размер сообщения 800 байт. Более правильным решением будет получение значения параметра сокета SCTP_MAXSEG для определения размера данных, помещающихся в один пакет.
10.4. Алгоритм Нагла (управляемый параметром сокета SCTP_NODELAY, см. раздел 7.10) вызывает проблемы только при передаче данных небольших объемов. Если данные передаются порциями такого размера, что SCTP вынужден передавать их немедленно, никакого замедления быть не может. Установка небольшого размера out_sz исказит результаты, потому что в некоторых случаях передача будет задерживаться до получения выборочных уведомлений от собеседника. Поэтому при передаче данных небольшого размера алгоритм Нагла следует отключать.
10.5. Если приложение устанавливает ассоциацию и изменяет количество потоков, количество потоков в данной ассоциации не меняется. Количество потоков может быть задано только для новых ассоциаций, но не для существующих.
Сокет типа «один-ко-многим» позволяет устанавливать ассоциации неявно. Для изменения параметров ассоциации необходимо вызвать sendmsg со вспомогательными данными. Фактически при этом обязательно использовать неявное установление ассоциации.
Глава 11
11.1. В листинге Д.4 приведена программа, вызывающая функцию gethostbyaddr.
Листинг Д.4. Изменение листинга 11.1 для вызова функции gethostbyaddr
//names/hostent2.c
1 #include "unp.h"
2 int
3 main(int argc, char **argv)
4 {
5 char *ptr, **pptr;
6 char str[INET6_ADDRSTRLEN];
7 struct hostent *hptr;