1.2. Простой клиент времени и даты

1.2. Простой клиент времени и даты

Рассмотрим конкретный пример, на котором мы введем многие понятия и термины, используемые в этой книге. В листинге 1.1[1] представлена реализация TCP-клиента времени и даты. Этот клиент устанавливает TCP-соединение с сервером, а сервер просто посылает клиенту время и дату в текстовом формате.

Листинг 1.1. Клиент TCP для определения времени и даты

//intro/daytimetcpcli.с

 1 #include "unp.h"

 2 int

 3 main(int argc, char **argv)

 4 {

 5  int sockfd, n;

 6  char recvline[MAXLINE + 1];

 7  struct sockaddr_in servaddr;

 8  if (argc != 2)

 9   err_quit("usage: a.out <Ipaddress>");

10  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)

11   err_sys("socket error");

12  bzero(&servaddr, sizeof(servaddr));

13  servaddr.sin_family = AF_INET;

14  servaddr.sin_port = htons(13); /* сервер времени и даты */

15  if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 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 }

ПРИМЕЧАНИЕ

Это формат, который мы используем для всего исходного кода в тексте. Каждая непустая строка пронумерована. Абзац, описывающий некоторую часть кода, начинается с двух номеров — начального и конечного номеров тех строк, о которых идет речь в данном абзаце. Как правило, абзацу предшествует короткий заголовок, в котором кратко резюмируется содержание описываемого кода.

В начале фрагмента кода указано имя файла исходного кода: в данном примере это файл daytimetcpcli.c в каталоге intro. Поскольку исходный код всех примеров этой книги можно свободно скачать из Сети (см. предисловие), вы можете найти соответствующие исходные файлы по их названиям. Наилучший способ изучить концепции сетевого программирования — компилировать, запускать и особенно модифицировать эти программы в ходе изучения книги.

ПРИМЕЧАНИЕ

Мы будем использовать примечания наподобие этого для описания особенностей реализации и исторических справок.

Если мы откомпилируем эту программу в определенный по умолчанию файл a.out и выполним ее, на выходе мы получим следующее:

solaris % a.out 206.168.112.96 наш ввод

Mon May 26 20:58:40 2003 вывод программы

ПРИМЕЧАНИЕ

Отображая интерактивный ввод и вывод, мы показываем то, что мы вводим, полужирным шрифтом; вывод же компьютера показываем моноширинным шрифтом. Мы всегда указываем название системы как часть приглашения интерпретатора (в данном примере solaris), чтобы показать, на каком узле выполняется команда. Системы, используемые для выполнения большинства примеров этой книги, показаны на рис. 1.7. Имена узлов обычно соответствуют операционным системам.

В этой программе, состоящей из 27 строк, есть много важных особенностей, нуждающихся в обсуждении. Мы кратко рассмотрим их на тот случай, если это первая сетевая программа, с которой вы встретились, а более подробные сведения по соответствующим вопросам вы сможете получить в других главах.

Подключение собственного заголовочного файла

1 Мы подключаем наш собственный заголовочный файл, unp.h, текст которого приведен в разделе Г.1. Этот заголовочный файл, в свою очередь, подключает различные системные заголовочные файлы, которые необходимы большинству сетевых программ, и определяет используемые нами константы (например, MAXLINE).

Аргументы командной строки

2-3 Определение функции main вместе с аргументами командной строки. Везде в данной книге при написании кода подразумевалось, что для его компиляции должен использоваться компилятор ANSI С (American National Standards Institute — Национальный институт стандартизации США), который также называют ISO С.

Создание сокета TCP

10-11 Функция socket создает потоковый сокет (SOCK_STREAM) Интернета (AF_INET) — это красивое название для обычного TCP-сокета). Функция возвращает дескриптор (небольшое целое число), который мы используем для идентификации сокета во всех последующих вызовах (например, connect и read).

ПРИМЕЧАНИЕ

Оператор if содержит вызов функции socket, присваивание возвращаемого значения переменной sockfd и последующую проверку, является ли это присвоенное значение меньшим нуля. Мы могли разбить этот оператор на два оператора С следующим образом:

sockfd = socket(AF_INET, SOCK_STREAM, 0);

 if (sockfd < 0)

но использованная в листинге 1.1 запись является типичным для языка С способом объединения двух строк. Поскольку в языке С оператор «меньше» (<) имеет более высокий приоритет, чем оператор присваивания, необходимо заключить в скобки операции присваивания и вызова функции (как это и сделано в листинге 1.1, в строке 10). Между двумя открывающими скобками мы всегда вставляем пробел как указание на то, что левая часть операции сравнения содержит также операцию присваивания. (Этот стиль позаимствован из исходного кода Minix [120].) Мы используем этот же прием в операторе while дальше в нашей программе.

Мы будем встречать множество различных вариантов использования термина сокет (socket). Во-первых, используемый нами API называется API сокетов. Во-вторых, в предыдущем абзаце мы упоминали функцию socket, которая входит в API сокетов. В-третьих, там же мы ссылались и на «сокет TCP», который является синонимом конечной точки TCP (TCP endpoint).

Если вызов функции socket оказывается неудачным, мы прерываем выполнение программы с помощью вызова функции err_sys. Она выдает сообщение об ошибке с ее описанием (например, «Протокол не поддерживается» — одна из возможных ошибок функции socket) и прерывает выполнение процесса. Эта функция создана нами, как и некоторые другие, начинающиеся с err_. Мы будем широко использовать их в примерах в последующих главах. Описание функций приводится в разделе Г.4.

Задание IP-адреса и порта сервера

12-16 Мы заполняем структуру адреса сокета Интернета (структура типа sockaddr_in с именем servaddr) IP-адресом и номером порта сервера. Сначала мы инициализируем всю структуру нулями, используя функцию bzero, затем устанавливаем номер порта в 13 (который является номером заранее известного порта (well-known port) сервера времени и даты на любом узле TCP/IP, поддерживающем соответствующую службу — см. табл. 2.1), после чего устанавливаем IP-адрес равным значению, определенному первым аргументом командной строки (argv[1]). В этой структуре поля IP-адреса и номера порта должны иметь определенный формат: мы вызываем библиотечную функцию htons (host to network short), чтобы преобразовать двоичный номер порта в требуемый формат, и вызываем библиотечную функцию inet_pton (presentation to numeric), чтобы преобразовать аргумент командной строки в символах ASCII (например, 206.168.112.96 при выполнении данного примера) в двоичный формат.

ПРИМЕЧАНИЕ

Функция bzero не является функцией ANSI С. Она происходит от более раннего кода сетевого программирования Беркли. Тем не менее мы используем именно ее, а не функцию ANSI С memset, потому что с функцией bzero работать проще: она вызывается с двумя аргументами, a memset — с тремя. Почти каждый производитель, поддерживающий API сокетов, также реализует и функцию bzero, а если и не реализует, мы определяем ее через макрос в нашем заголовочном файле unp.h.

Автор [112] в первом издании сделал десять ошибок, поменяв местами аргументы memset. Компилятор С не может распознать эту ошибку, поскольку оба аргумента принадлежат одному типу. В действительности второй аргумент принадлежит типу int, а третий — size_t — обычно имеет тип unsigned int (то есть целое без знака), но заданные значения, соответственно, 0 и 16, являются допустимыми для обоих типов аргумента. Вызов функции memset все равно осуществлялся, но реально функция ничего не делала, поскольку задавалось нулевое число инициализируемых байтов. Программа работала, потому что только некоторые функции сокетов действительно требуют, чтобы последние 8 байт структуры адреса сокета Интернета были установлены в 0. Тем не менее это ошибка, и ее можно избежать при использовании функции bzero, поскольку перестановка двух аргументов функции bzero всегда будет выявлена компилятором С, если используются прототипы функций.

Возможно, вы впервые встречаете функцию inet_pton. Она появилась вместе с протоколом IPv6 (о котором более подробно мы поговорим в приложении А). В старых программах для преобразования точечно-десятичной записи (dotted-decimal string) ASCII в необходимый формат использовалась функция inet_addr, но у нее есть ряд ограничений, которых не имеет функция inet_pton. Не беспокойтесь, если ваша система (еще) не поддерживает эту функцию; реализация ее приведена в разделе 3.7.

Установка соединения с сервером

17-18 Функция connect, применяемая к сокету TCP, устанавливает соединение по протоколу TCP с сервером, адрес сокета которого содержится в структуре, на которую указывает второй аргумент. Мы также должны задать длину структуры адреса сокета в качестве третьего аргумента функции connect, а для структур адреса интернет-сокета мы всегда предоставляем вычисление длины компилятору, используя оператор С sizeof.

ПРИМЕЧАНИЕ

В заголовочном файле unp.h мы используем директиву #define SA, чтобы определить SA как struct sockaddr, что соответствует общей структуре адреса сокета. Каждый раз, когда одна из функций сокетов требует указателя на структуру адреса сокета, этот указатель должен быть преобразован к указателю на общую структуру адреса сокета. Это происходит потому, что функции сокетов появились раньше, чем стандарт ANSI С. Соответственно, тип указателя void* не был доступен в начале 80-х, когда эти функции были разработаны. Проблема состоит в том, что "struct sockaddr" занимает 15 символов и часто заставляет выходить строку исходного кода за правую границу экрана (или за страницу книги), поэтому мы сократили ее до SA. Более подробно мы исследуем общие структуры адресов сокетов на примере листинга 3.2.

Чтение и отображение ответа сервера

19-25 Мы читаем ответ сервера и отображаем результат, используя стандартную функцию ввода-вывода fputs. Нужно быть внимательным при использовании TCP, поскольку это потоковый (byte-stream) протокол без границ записей. Обычно ответом сервера является 26-байтовая строка следующей формы:

Fri Jan 12 14:27:52 1996

где — это возврат каретки, а — перевод строки (в символах ASCII). В случае потокового протокола эти 26 байт можно получить в нескольких вариантах: в виде отдельного сегмента TCP, содержащего все 26 байт данных, либо в виде 26 сегментов, каждый из которых содержит по одному байту данных, или в виде любой другой комбинации, в сумме дающей 26 байт. Обычно возвращается один сегмент, содержащий все 26 байт, но при больших объемах данных нельзя рассчитывать, что ответ сервера будет получен с помощью одного вызова read. Следовательно, при чтении из сокета TCP нужно всегда вызывать функцию read циклически и прерывать цикл либо когда функция возвращает 0 (например, соединение было разорвано другой стороной), либо когда возвращенное значение оказывается меньше нуля (ошибка).

В приведенном примере конец записи обозначается сервером, закрывающим соединение. Эта технология используется также версией 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 Функция exit завершает программу. Unix всегда закрывает все открытые дескрипторы при завершении процесса, поэтому теперь наш сокет TCP закрыт.

Как уже говорилось, пока мы лишь выделили наиболее важные моменты, детальным исследованием которых займемся в дальнейшем.

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

Настройка даты и времени

Из книги Работа на ноутбуке автора Садовский Алексей

Настройка даты и времени Переходим к настройке даты и времени. Пожалуй, эта операция одна из самых простых среди изменений параметров работы Windows. Произвести ее можно, выбрав значок Дата и время из подраздела Дата, время, язык и региональные настройки на Панели управления.


Настройка даты и времени

Из книги Компьютер на 100. Начинаем с Windows Vista автора Зозуля Юрий

Настройка даты и времени Для настройки даты и времени щелкните правой кнопкой мыши на значке часов в области уведомлений и выберите пункт Настройка даты/времени. Чтобы изменить показания часов или текущую дату, вам понадобятся полномочия администратора компьютера.


7.10. Контроль даты и времени

Из книги Программирование на языке Ruby [Идеология языка, теория и практика применения] автора Фултон Хэл

7.10. Контроль даты и времени В разделе 7.5 было показано, что стандартные функции не проверяют корректность даты, а «переносят» ее вперед, если необходимо. Например, 31 ноября становится 1 декабря.Иногда такое поведение даже желательно. А если нет, то спешу обрадовать:


Функции даты и времени

Из книги Справочник по PHP автора

Функции даты и времени checkdateПроверяет правильность даты/времени.Синтаксис:int checkdate(int month, int day, int year);Функция checkdate() проверяет правильность даты, заданной в ее аргументах.Возвращает true если дата, указанная как "month, day, year" (месяц, число, год), правильна, иначе false. Дата считается


Средства для форматирования даты и времени

Из книги Технология XSLT автора Валиков Алексей Николаевич

Средства для форматирования даты и времени В первых версиях XSLT элемент xsl:decimal-format и функция format-number обеспечивали форматирование чисел при их текстовом отображении. К сожалению, подобных инструментов для форматирования даты предусмотрено не было.Поскольку связка


18.1. Вывод времени и даты

Из книги Разработка приложений в среде Linux. Второе издание автора Джонсон Майкл К.

18.1. Вывод времени и даты 18.1.1. Представление времени В системах Unix и Linux время отслеживается в секундах до или после начала эпохи, которое определяется как полночь 1 января 1970 года по UTC[148]. Положительные значения времени относятся к периоду после начала эпохи;


3.4. Функции даты и времени

Из книги MySQL 5.0. Библиотека программиста автора Гольцман Виктор Иосифович

3.4. Функции даты и времени В данном разделе мы рассмотрим некоторые полезные функции, выполняющие различные операции с датами: получение текущей даты и/или времени, получение отдельных компонентов даты и/или времени, арифметические операции с датами (сложение, вычитание)


ГЛАВА 10. Типы даты и времени.

Из книги Firebird РУКОВОДСТВО РАЗРАБОТЧИКА БАЗ ДАННЫХ автора Борри Хелен

ГЛАВА 10. Типы даты и времени. Firebird поддерживает в диалекте 3 типы данных DATE, TIME и TIMESTAMP. В диалекте 1 поддерживается только один тип данных, подобный TIMESTAMP, который, хотя и называется DATE, не является взаимозаменяемым с типом DATE диалекта 3. DATE В диалекте 3 DATE хранит одну дату без


6.1.3. Форматирование даты и времени

Из книги автора

6.1.3. Форматирование даты и времени Примеры в предыдущем разделе показали, как поля в struct tm могли бы быть использованы в качестве индексов символьных строк для вывода информативных значений даты и времени. Хотя можно было бы написать собственный код, использующий такие


1.5. Простой сервер времени и даты

Из книги автора

1.5. Простой сервер времени и даты Мы можем написать простую версию сервера TCP для определения времени и даты, который будет работать с клиентом, описанным в разделе 1.2. Мы используем функции-обертки, описанные в предыдущем разделе. Код сервера приведен в листинге 1.5.Листинг


Пример: клиент времени и даты

Из книги автора

Пример: клиент времени и даты В листинге 11.5 показан наш клиент времени и даты из листинга 1.1, переписанный с использованием функции tcp_connect.Листинг 11.5. Клиент времени и даты, переписанный с использованием функции tcp_connect//names/daytimetcpcli.c 1 #include "unp.h" 2 int 3 main(int argc, char **argv) 4 { 5  int


Пример: сервер времени и даты

Из книги автора

Пример: сервер времени и даты В листинге 11.7 показан наш сервер времени и даты из листинга 4.2, переписанный с использованием функции tcp_listen.Листинг 11.7. Сервер времени и даты, переписанный с использованием функции tcp_listen//names/daytimetcpsrv1.c 1 #include "unp.h" 2 #include <time.h> 3 int 4 main(int argc, char


Пример: не зависящий от протокола UDP-клиент времени и даты

Из книги автора

Пример: не зависящий от протокола UDP-клиент времени и даты Теперь мы перепишем наш клиент времени и даты, показанный в листинге 11.3, так, чтобы в нем использовалась наша функция udp_client. В листинге 11.10 представлен не зависящий от протокола исходный код.Листинг 11.10. UDP-клиент


16.4. Неблокируемая функция connect: клиент времени и даты

Из книги автора

16.4. Неблокируемая функция connect: клиент времени и даты В листинге 16.7 показана наша функция connect_nonb, вызывающая неблокируемую функцию connect. Мы заменяем вызов функции connect, имеющийся в листинге 1.1, следующим фрагментом кода:if (connect_nonb(sockfd, (SA*)&servaddr, sizeof(servaddr), 0) < 0)err_sys("connect