UNIX: разработка сетевых приложений
Fri Jan 12 14:27:52 1996\r\nгде
— это возврат каретки, а\r— перевод строки (в символах ASCII). В случае потокового протокола эти 26 байт можно получить в нескольких вариантах: в виде отдельного сегмента TCP, содержащего все 26 байт данных, либо в виде 26 сегментов, каждый из которых содержит по одному байту данных, или в виде любой другой комбинации, в сумме дающей 26 байт. Обычно возвращается один сегмент, содержащий все 26 байт, но при больших объемах данных нельзя рассчитывать, что ответ сервера будет получен с помощью одного вызова\n. Следовательно, при чтении из сокета TCP нужно всегда вызывать функциюreadциклически и прерывать цикл либо когда функция возвращает 0 (например, соединение было разорвано другой стороной), либо когда возвращенное значение оказывается меньше нуля (ошибка).readВ приведенном примере конец записи обозначается сервером, закрывающим соединение. Эта технология используется также версией 1.0 протокола передачи гипертекста (Hypertext Transfer Protocol, HTTP). Существуют и другие способы обозначения конца записи. Например, протокол передачи файлов (File Transfer Protocol, FTP) и простой протокол передачи почты (Simple Mail Transfer Protocol, SMTP) обозначают конец записи 2-байтовой последовательностью, состоящей из символов ASCII возврата каретки и перевода строки. Служба вызова удаленных процедур (Remote Procedure Call, RPC) и система именования доменов (Domain Name System, DNS) помещают перед каждой записью, отсылаемой по протоколу TCP, двоичное число, соответствующее длине этой записи. Здесь важно осознать, что протокол TCP сам по себе не предоставляет никаких меток записей: если приложение хочет отделять записи одну от другой, оно должно делать это самостоятельно, и для этого имеются стандартные методы.
Завершение программыФункция26завершает программу. Unix всегда закрывает все открытые дескрипторы при завершении процесса, поэтому теперь наш сокет TCP закрыт.exitКак уже говорилось, пока мы лишь выделили наиболее важные моменты, детальным исследованием которых займемся в дальнейшем.
1.3. Независимость от протокола
Наша программа, представленная в листинге 1.1, является зависимой от протокола (protocol dependent) IPv4. Мы выделяем и инициализируем структуру
, определяем адрес как относящийся к семейству AF_INET и устанавливаем первый аргумент функцииsockaddr_inравнымsocket.AF_INETЕсли мы хотим изменить программу так, чтобы она работала по протоколу IPv6, мы должны изменить код. В листинге 1.2 показана новая версия программы с соответствующими изменениями, отмеченными полужирным шрифтом.
Листинг 1.2. Версия листинга 1.1 для IPv6
//intro/daytimetcpcliv6.с1 #include "unp.h"2 int3 main(int argc, char **argv)4 {5 int sockfd, n;6 char recvline[MAXLINE + 1];7 struct <b>sockaddr_in6</b> servaddr;8 if (argc != 2)9 err_quit("usage: a.out <Ipaddress>");10 if ((sockfd = socket(<b>AF_INET6</b>, SOCK_STREAM, 0)) < 0)11 err_sys("socket error");12 bzero(&servaddr, sizeof(servaddr));13 servaddr.<b>sin6_family</b> = AF_INET6;14 servaddr.<b>sin6_port</b> = htons(13); /* сервер времени и даты */15 if (inet_pton(<b>AF_INET6</b>, argv[1], &servaddr.<b>sin6_addr</b>) <= 0)16 err_quit("inet_pton error for %s", argv[1]);17 if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) < 0)18 err_sys("connect error");19 while ((n = read(sockfd, recvline, MAXLINE)) > 0) {20 recvline[n] = 0; /* символ конца строки */21 if (fputs(recvline, stdout) == EOF)22 err_sys("fputs error");23 }24 if (n < 0)25 err_sys("read error");26 exit(0);27 }Изменились только пять строк, но в результате мы все равно получили программу, зависимую от протокола, в данном случае — от протокола IPv6. Лучше сделать программу независимой от протокола (protocol independent). В листинге 11.3 представлена независимая от протокола версия этого клиента, основанная на вызове
изgetaddrinfo.tcp_connectДругим недостатком наших программ является то, что пользователь должен вводить IP-адрес сервера в точечно-десятичной записи (например, 206.168.112.219 для версии IPv4). Людям проще работать с именами, чем с числами (например,
). В главе 11 мы обсудим функции, обеспечивающие преобразование имен узлов в IP-адреса и имен служб в порты. Мы специально откладываем описание этих функций, продолжая использовать IP-адреса и номера портов, чтобы иметь ясное представление о том, что именно входит в структуры адресов сокетов, которые мы должны заполнить и проверить. Это также упрощает наши объяснения сетевого программирования, снимая необходимость описывать в подробностях еще один набор функций.www.unpbook.com1.4. Обработка ошибок: функции-обертки
В любой реальной программе существенным моментом является проверка каждого вызова функции на предмет возвращаемой ошибки. В листинге 1.1 мы проводим поиск ошибок в вызовах функций
,socket,inet_pton,connectиread, и когда ошибка случается, мы вызываем свои собственные функцииfputsиerr_quitдля печати сообщения об ошибке и для прерывания выполнения программы. В отдельных случаях, когда функция возвращает ошибку, бывает нужно сделать еще что-либо помимо прерывания программы, как показано в листинге 5.9, когда мы должны проверить прерванный системный вызов.err_sys