KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программное обеспечение » Уильям Стивенс - UNIX: разработка сетевых приложений

Уильям Стивенс - UNIX: разработка сетевых приложений

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Уильям Стивенс, "UNIX: разработка сетевых приложений" бесплатно, без регистрации.
Перейти на страницу:

 2 void

 3 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)

 4 {

 5  int n;

 6  char sendline[MAXLINE], recvline[MAXLINE + 1];

 7  Connect(sockfd, (SA*)pservaddr, servlen);


 8  while (Fgets(sendline, MAXLINE, fp) != NULL) {


 9   Write(sockfd, sendline, strlen(sendline));


10   n = Read(sockfd, recvline, MAXLINE);


11   recvline[n] = 0; /* завершающий нуль */

12   Fputs(recvline, stdout);

13  }

14 }

Изменения по сравнению с предыдущей версией — это добавление вызова функции connect и замена вызовов функций sendto и recvfrom вызовами функций write и read. Функция dg_cli остается не зависящей от протокола, поскольку она не вникает в структуру адреса сокета, передаваемую функции connect. Наша функция main клиента, показанная в листинге 8.3, остается той же.

Если мы запустим программу на узле macosx, задав IP-адрес узла freebsd4 (который не запускает наш сервер на порте 9877), мы получим следующий вывод:

macosx % udpcli04 172.24.37.94

hello, world

read error: Connection refused

Первое, что мы замечаем, — мы не получаем ошибку, когда запускаем процесс клиента. Ошибка происходит только после того, как мы отправляем серверу первую дейтаграмму. Именно отправка этой дейтаграммы вызывает ошибку ICMP от узла сервера. Но когда клиент TCP вызывает функцию connect, задавая узел сервера, на котором не запущен процесс сервера, функция connect возвращает ошибку, поскольку вызов функции connect вызывает отправку первого пакета трехэтапного рукопожатия TCP, и именно этот пакет вызывает получение сегмента RST от собеседника (см. раздел 4.3).

В листинге 8.8 показан вывод программы tcpdump.

Листинг 8.8. Вывод программы tcpdump при запуске функции dg_cli

macosx % tcpdump

01 0.0            macosx.51139 > freebsd4 9877:udp 13

02 0.006180 ( 0.0062) freebsd4 > macosx: icmp: freebsd4 udp port 9877 unreachable

В табл. A.5 мы также видим, что возникшую ошибку ICMP ядро сопоставляет ошибке ECONNREFUSED, которая соответствует выводу строки сообщения Connection refused (В соединении отказано) функцией err_sys.

ПРИМЕЧАНИЕ

К сожалению, не все ядра возвращают сообщения ICMP присоединенному сокету UDP, как мы показали в этом разделе. Обычно ядра реализаций, происходящих от Беркли, возвращают эту ошибку, а ядра System V — не возвращают. Например, если мы запустим тот же клиент на узле Solaris 2.4 и с помощью функции connect соединимся с узлом, на котором не запущен наш сервер, то с помощью программы tcpdump мы сможем убедиться, что ошибка ICMP о недоступности порта возвращается узлом сервера, но вызванная клиентом функция read никогда не завершается. Эта ситуация была исправлена в Solaris 2.5. UnixWare не возвращает ошибку, в то время как AIX, Digital Unix, HP-UX и Linux возвращают.

8.13. Отсутствие управления потоком в UDP

Теперь мы проверим, как влияет на работу приложения отсутствие какого-либо управления потоком в UDP. Сначала мы изменим нашу функцию dg_cli так, чтобы она отправляла фиксированное число дейтаграмм. Она больше не будет читать из стандартного потока ввода. В листинге 8.9 показана новая версия функции. Эта функция отправляет серверу 2000 дейтаграмм UDP по 1400 байт каждая.

Листинг 8.9. Функция dg_cli, отсылающая фиксированное число дейтаграмм серверу

//udpcliserv/dgcliloop1.c

 1 #include "unp.h"


 2 #define NDG 2000 /* количество дейтаграмм для отправки */

 3 #define DGLEN 1400 /* длина каждой дейтаграммы */


 4 void

 5 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)

 6 {

 7  int i;

 8  char sendline[DGLEN];


 9  for (i = 0; i < NDG; i++) {

10   Sendto(sockfd, sendline, DGLEN, 0, pservaddr, servlen);

11  }

12 }

Затем мы изменяем сервер так, чтобы он получал дейтаграммы и считал число полученных дейтаграмм. Сервер больше не отражает дейтаграммы обратно клиенту. В листинге 8.10 показана новая функция dg_echo. Когда мы завершаем процесс сервера нажатием клавиши прерывания на терминале (что приводит к отправке сигнала SIGINT процессу), сервер выводит число полученных дейтаграмм и завершается.

Листинг 8.10. Функция dg_echo, считающая полученные дейтаграммы

//udpcliserv/dgecholoop1.c

 1 #include "unp.h"


 2 static void recvfrom_int(int);

 3 static int count;


 4 void

 5 dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)

 6 {

 7  socklen_t len;

 8  char mesg[MAXLINE];


 9  Signal (SIGINT, recvfrom_int);


10  for (;;) {

11   len = clilen;

12   Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);


13   count++;

14  }

15 }


16 static void

17 recvfrom_int(int signo)

18 {

19  printf("nreceived %d datagramsn", count);

20  exit(0);

21 }

Теперь мы запускаем сервер на узле freebsd, который представляет собой медленный компьютер SPARCStation. Клиент мы запускаем в значительно более быстрой системе RS/6000 с операционной системой aix. Они соединены друг с другом напрямую каналом Ethernet на 100 Мбит/с. Кроме того, мы запускаем программу netstat -s на узле сервера и до, и после запуска клиента и сервера, поскольку выводимая статистика покажет, сколько дейтаграмм мы потеряли. В листинге 8.11 показан вывод сервера.

Листинг 8.11. Вывод на узле сервера

freebsd % netstat -s -p udp

udp:

 71208 datagrams received

 0 with incomplete header

 0 with bad data length field

 0 with bad checksum

 0 with no checksum

 832 dropped due to no socket

 16 broadcast/multicast datagrams dropped due to no socket

 1971 dropped due to full socket buffers

 0 not for hashed pcb

 68389 delivered

 137685 datagrams output

freebsd % udpserv06 запускаем наш сервер

                    клиент посылает дейтаграммы

^C для окончания работы клиента вводим наш символ прерывания

freebsd % netstat -s -р udp

udp

 73208 datagrams received

 0 with incomplete header

 0 with bad data length field

 0 with bad checksum

 0 with no checksum

 832 dropped due to no socket

 16 broadcast/multicast datagrams dropped due to no socket

 3941 dropped due to full socket buffers

 0 not for hashed pcb

 68419 delivered

 137685 datagrams output

Клиент отправил 2000 дейтаграмм, но приложение-сервер получило только 30 из них, что означает уровень потерь 98%. Ни сервер, ни клиент не получают сообщения о том, что эти дейтаграммы потеряны. Как мы и говорили, UDP не имеет возможности управления потоком — он ненадежен. Как мы показали, для отправителя UDP не составляет труда переполнить буфер получателя.

Если мы посмотрим на вывод программы netstat, то увидим, что общее число дейтаграмм, полученных узлом сервера (не приложением-сервером) равно 2000 (73 208 – 71 208). Счетчик dropped due to full socket buffers (отброшено из-за переполнения буферов сокета) показывает, сколько дейтаграмм было получено UDP и проигнорировано из-за того, что приемный буфер принимающего сокета был полон [128, с. 775]. Это значение равно 1970 (3941 – 1971), что при добавлении к выводу счетчика дейтаграмм, полученных приложением (30), дает 2000 дейтаграмм, полученных узлом. К сожалению, счетчик дейтаграмм, отброшенных из-за заполненного буфера, в программе netstat распространяется на всю систему. Не существует способа определить, на какие приложения (например, какие порты UDP) это влияет.

Число дейтаграмм, полученных сервером в этом примере, недетерминировано. Оно зависит от многих факторов, таких как нагрузка сети, загруженность узла клиента и узла сервера.

Если мы запустим тот же клиент и тот же сервер, но на этот раз клиент на медленной системе Sun, а сервер на быстрой системе RS/6000, никакие дейтаграммы не теряются.

aix % udpserv06

^? после окончания работы клиента вводим наш символ прерывания

received 2000 datagrams

Приемный буфер сокета UDP

Число дейтаграмм UDP, установленных в очередь UDP, для данного сокета ограничено размером его приемного буфера. Мы можем изменить его с помощью параметра сокета SO_RCVBUF, как мы показали в разделе 7.5. В FreeBSD по умолчанию размер приемного буфера сокета UDP равен 42 080 байт, что допускает возможность хранения только 30 из наших 1400-байтовых дейтаграмм. Если мы увеличим размер приемного буфера сокета, то можем рассчитывать, что сервер получит дополнительные дейтаграммы. В листинге 8.12 представлена измененная функция dg_echo из листинга 8.10, которая увеличивает размер приемного буфера сокета до 240 Кбайт. Если мы запустим этот сервер в системе Sun, а клиент — в системе RS/6000, то счетчик полученных дейтаграмм будет иметь значение 103. Поскольку это лишь немногим лучше, чем в предыдущем примере с размером буфера, заданным по умолчанию, ясно, что мы пока не получили решения проблемы.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*