Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Материал этих методических указаний предпологает использование языка CИ, возможностей ОС Linux, cистемных вызовов этой операционной системы, и ее библиотек.



СЕТЕВОЕ ПРОГРАММИРОВАНИЕ

Методические указания

 

Cанкт - Петербург 2017

 

Введение

 

Материал этих методических указаний предпологает использование языка CИ, возможностей ОС Linux, cистемных вызовов этой операционной системы, и ее библиотек.

 

Цель цикла лабораторных работ.

Получение навыков разработки сетевых приложений, с использованием библиотеки Sockets API.

 

Краткие теоретические сведения.

Socket (гнездо, разъем) - абстрактное программное понятие, используемое для обозначения в прикладной программе конечной точки канала связи с коммуникационной средой, образованной вычислительной сетью. При использовании протоколов TCP/IP можно говорить, что socket является средством подключения прикладной программы к порту локального узла сети.

Socket-интерфейс представляет собой просто набор системных вызовов и/или библиотечных функций языка программирования СИ, разделенных на четыре группы:

1. локального управления;

2. установления связи;

3. обмена данными (ввода/вывода);

4. закрытия связи.

Ниже рассматривается подмножество функций socket-интерфейса, достаточное для написания сетевых приложений, реализующих модель " клиент-сервер" в режиме с установлением соединения.

Функции локального управления используются, главным образом, для выполнения подготовительных действий, необходимых для организации взаимодействия двух программ-партнеров. Эти функции носят такое название, поскольку их выполнение носит локальный для программы характер.

 

Создание socket'а

 

Создание socket'а осуществляется следующим системным вызовом

#include < sys/socket.h>

int socket (domain, type, protocol)

int domain;

int type;

int protocol;

Аргумент domain задает используемый для взаимодействия набор протоколов (вид коммуникационной области), для стека протоколов TCP/IP он должен иметь символьное значение AF_INET (определено в sys/socket.h).

Аргумент type задает режим взаимодействия:

• SOCK_STREAM - с установлением соединения;

• SOCK_DGRAM - без установления соединения.

Аргумент protocolзадает конкретный протокол транспортного уровня (из нескольких возможных в стеке протоколов). Если этот аргумент задан равным 0, то будет использован протокол " по умолчанию" (TCP для SOCK_STREAM и UDP для SOCK_DGRAM при использовании комплекта протоколов TCP/IP).

При удачном завершении своей работы данная функция возвращает дескриптор socket'а - целое неотрицательное число, однозначно его идентифицирующее. Дескриптор socket'а аналогичен дескриптору файла ОС UNIX. При обнаружении ошибки в ходе своей работы функция возвращает число " -1".

 

Связывание socket'а

 

Для подключения socket'а к коммуникационной среде, образованной сетью, необходимо выполнить системный вызов bind, определяющий в принятом для сети формате локальный адрес канала связи со средой. В сетях TCP/IP socket связывается с локальным портом. Системный вызов bind имеет следующий синтаксис:

#include < sys/types.h>

#include < sys/socket.h>

#include < netinet/in.h>

int bind (s, addr, addrlen)

int s;

struct sockaddr *addr;

int addrlen;

Аргумент s задает дескриптор связываемого socket'а.

Аргумент addr должен указывать на структуру данных, содержащую локальный адрес, приписываемый socket'у. Для сетей TCP/IP такой структурой является sockaddr_in.

Аргумент addrlen задает размер (в байтах) структуры данных, указываемой аргументом

addr.

Структура sockaddr_in используется несколькими системными вызовами и функциями socket-интерфейса и определена в include-файле in.h следующим образом:

struct sockaddr_in {

short sin_family;

u_short sin_port;

struct in_addr sin_addr;

char sin_zero[8];

};

Поле sin_family определяет используемый формат адреса (набор протоколов), в нашем случае (для TCP/IP) оно должно иметь значение AF_INET.

Поле sin_addr содержит адрес (номер) узла сети.

Поле sin_port содержит номер порта на узле сети.

Поле sin_zero не используется.

Определение структуры in_addr (из того же include-файла) таково:

struct in_addr {

union {

u_long S_addr;

/*

другие (не интересующие нас)

члены объединения

*/

} S_un;

#define s_addr S_un.S_addr

};

Структура sockaddr_in должна быть полностью заполнена перед выдачей системного вызова bind. При этом, если поле sin_addr.s_addr имеет значение INADDR_ANY, то системный вызов будет привязывать к socket'у номер (адрес) локального узла сети. В случае успеха bind возвращает 0, в противном случае - " -1".

 

Ожидание установления связи

 

Для установления связи " клиент-сервер" используются системные вызовы listen и accept (на стороне сервера), а также connect (на стороне клиента). Для заполнения полей структуры socaddr_in, используемой в вызове connect, обычно используется библиотечная функция gethostbyname, транслирующая символическое имя узла сети в его номер (адрес).

Системный вызов listen выражает желание выдавшей его программысервера ожидать запросы к ней от программ-клиентов и имеет следующий вид:

#include < sys/socket.h>

int listen (s, n)

int s;

int n;

Аргумент s задает дескриптор socket'а, через который программа будет ожидать запросы к ней от клиентов. Socket должен быть предварительно создан системным вызовом socket и обеспечен адресом с помощью системного вызова bind. Аргумент n определяет максимальную длину очереди входящих запросов на установление связи. Если какой-либо клиент выдаст запрос на установление связи при полной очереди, то этот запрос будет отвергнут. Признаком удачного завершения системного вызова listen служит нулевой код возврата.

 

Запрос на установление соединения

Для обращения программы-клиента к серверу с запросом на установление логической соединения используется системный вызов connect, имеющий следующий вид.

#include < sys/types.h>

#include < sys/socket.h>

#include < netinet/in.h>

int connect (s, addr, addrlen)

int s;

struct sockaddr_in *addr;

int addrlen;

Аргумент s задает дескриптор socket'а, через который программа обращается к серверу с запросом на соединение. Socket должен быть предварительно создан системным вызовом socket и обеспечен адресом с помощью системного вызова bind.

Аргумент addr должен указывать на структуру данных, содержащую адрес, приписанный socket'у программы-сервера, к которой делается запрос на соединение. Для сетей TCP/IP такой структурой является sockaddr_in. Для формирования значений полей структуры sockaddr_in удобно использовать функцию gethostbyname.

Аргумент addrlen задает размер (в байтах) структуры данных, указываемой аргументом addr. Для того, чтобы запрос на соединение был успешным, необходимо, по крайней мере, чтобы программа-сервер выполнила к этому моменту системный вызов listen для socket'а с указанным адресом.

При успешном выполнении запроса системный вызов connect возвращает 0, в противном случае - " -1" (устанавливая код причины неуспеха в глобальной переменной errno). Если к моменту выполнения connect используемый им socket не был привязан к адресу посредством bind, то такая привязка будет выполнена автоматически. В режиме взаимодействия без установления соединения необходимости выполнении системного вызова connect нет. Однако, его выполнение в таком режиме не является ошибкой - просто меняется смысл выполняемых при этом действий: устанавливается адрес " по умолчанию" для всех последующих посылок дейтаграмм.

 

Функции обмена данными

В режиме с установлением логического соединения после удачного выполнения пары взаимосвязанных системных вызовов connect (в клиенте) и accept (в сервере) становится возможным обмен данными.

Этот обмен может быть реализован обычными системными вызовами read и write, используемыми для работы с файлами (при этом вместо дескрипторов файлов в них задаются дескрипторы socket'ов).

Кроме того могут быть дополнительно использованы системные вызовы send и recv, ориентированные специально на работу с socket'ами.

Для обмена данными в режиме без установления логического соединения

используются, как правило, системные вызовы sendto и recvfrom. Sendto позволяет специфицировать вместе с передаваемыми данными (составляющими дейтаграмму) адрес их получателя. Recvfrom одновременно с доставкой данных получателю информирует его и об адресе отправителя.

 

Посылка данных

Для посылки данных партнеру по сетевому взаимодействию используется системный вызов send, имеющий следующий вид

#include < sys/types.h>

#include < sys/socket.h>

int send (s, buf, len, flags)

int s;

char *buf;

int len;

int flags;

Аргумент s задает дескриптор socket'а, через который посылаются данные.

Аргумент buf указывает на область памяти, содержащую передаваемые данные.

Аргумент len задает длину (в байтах) передаваемых данных.

Аргумент flags модифицирует исполнение системного вызова send. При нулевом значении этого аргумента вызов send полностью аналогичен системному вызову write. При успешном завершении send возвращает количество переданных из области, указанной аргументом buf, байт данных. Если канал данных, определяемый дескриптором s, оказывается " переполненным", то send переводит программу в состояние ожидания до момента его освобождения.

 

Получение данных

 

Для получения данных от партнера по сетевому взаимодействию используется системный вызов recv, имеющий следующий вид

#include < sys/types.h>

#include < sys/socket.h>

int recv (s, buf, len, flags)

int s;

char *buf;

int len;

int flags;

Аргумент s задает дескриптор socket'а, через который принимаются данные.

Аргумент buf указывает на область памяти, предназначенную для размещения

принимаемых данных. Аргумент len задает длину (в байтах) этой области. Аргумент flags модифицирует исполнение системного вызова recv. При нулевом значении этого аргумента вызов recv полностью аналогичен системному вызову read. При успешном завершении recv возвращает количество принятых в область, указанную аргументом buf, байт данных. Если канал данных, определяемый дескриптором s, оказывается " пустым", то recv переводит программу в состояние ожидания до момента появления в нем данных.

 

Функции закрытия связи

 

Для закрытия связи с партнером по сетевому взаимодействию используются системные

вызовы close и shutdown.

Системный вызов close

Для закрытия ранее созданного socket'а используется обычный системный вызов close, применяемый в ОС UNIX для закрытия ранее открытых файлов и имеющий следующий вид.

int close (s)

int s;

Аргумент s задает дескриптор ранее созданного socket'а.

Однако в режиме с установлением логического соединения (обеспечивающем, как правило, надежную доставку данных) внутрисистемные механизмы обмена будут пытаться передать/принять данные, оставшиеся в канале передачи на момент закрытия socket'а. На это может потребоваться значительный интервал времени, неприемлемый для некоторых приложений. В такой ситуации необходимо использовать описываемый далее

системный вызов shutdown.

Sockets API»

Разработать сетевое приложение в соответствии с вариантом задания.

Примеры использования socket-интерфейса для организации клиент – серверного взаимодействия

 

В данном разделе рассматривается использование socket-интерфейса в режиме

взаимодействия с установлением логического соединения на простых примерах взаимодействия двух программ (сервера и клиента), функционирующих на разных узлах сети TCP/IP.

Содержательная часть первой программы примитивна:

1. сервер, приняв запрос на соединение, передает клиенту вопрос " Who are you? ";

2. клиент, получив вопрос, выводит его в стандартный вывод и направляет серверу ответ " I am your client" и завершает на этом свою работу;

3. сервер выводит в стандартный вывод ответ клиента, закрывает с ним связь и

переходит в состояние ожидания следующего запроса к нему.

Тексты первых двух пар программ (клиент и север) предназначены только для иллюстрации логики взаимодействия программ через сеть, поэтому в них отсутствуют такие атрибуты программ, предназначенных для практического применения, как обработка кодов возврата системных вызовов и функций, анализ кодов ошибок в глобальной переменной errno, реакция на асинхронные события и т.п. В последнем примере программы HttpClient эти атрибуты реализованы.

 

 

Программа-сервер

Текст программы-сервера на языке программирования СИ выглядит следующим образом

1 #include < sys/types.h>

2 #include < sys/socket.h>

3 #include < netinet/in.h>

4 #include < netdb.h>

5 #include < memory.h>

6 #define SRV_PORT 1234

7 #define BUF_SIZE 64

8 #define TXT_QUEST " Who are you? \n"

9 main () {

10 int s, s_new;

11 int from_len;

12 char buf[BUF_SIZE];

13 struct sockaddr_in sin, from_sin;

14 s = socket (AF_INET, SOCK_STREAM, 0);

15 memset ((char *)& sin, '\0', sizeof(sin));

16 sin.sin_family = AF_INET;

17 sin.sin_addr.s_addr = INADDR_ANY;

18 sin.sin_port = SRV_PORT;

19 bind (s, (struct sockaddr *)& sin, sizeof(sin));

20 listen (s, 3);

21 while (1) {

22 from_len = sizeof(from_sin);

23 s_new = accept (s, & from_sin, & from_len);

24 write (s_new, TXT_QUEST, sizeof(TXT_QUEST));

25 from_len = read (s_new, buf, BUF_SIZE);

26 write (1, buf, from_len);

27 shutdown (s_new, 0);

28 close (s_new);

29 };

30 }

 

Строки 1...5 описывают включаемые файлы, содержащие определения для всех необходимых структур данных и символических констант.

Строка 6 приписывает целочисленной константе 1234 символическое имя SRV_PORT. В дальнейшем эта константа будет использована в качестве номера порта сервера. Значение этой константы должно быть известно и программе-клиенту.

Строка 7 приписывает целочисленной константе 64 символическое имя BUF_SIZE. Эта константа будет определять размер буфера, используемого для размещения принимаемых от клиента данных.

Строка 8 приписывает последовательности символов, составляющих текст вопроса клиенту, символическое имя TXT_QUEST. Последним символом в последовательности является символ перехода на новую строку '\n'. Сделано это для упрощения вывода текста вопроса на стороне клиента.

В строке 14 создается (открывается) socket для организации режима взаимодействия с установлением логического соединения (SOCK_STREAM) в сети TCP/IP (AF_INET), при выборе протокола транспортного уровня используется протокол " по умолчанию" (0).

В строках 15...18 сначала обнуляется структура данных sin, а затем заполняются ее отдельные поля. Использование константы INADDR_ANY упрощает текст программы, избавляя от необходимости использовать функцию gethostbyname для получения адреса локального узла, на котором запускается сервер.

Строка 19 посредством системного вызова bind привязывает socket, задаваемый дескриптором s, к порту с номером SRV_PORT на локальном узле. Bind завершится успешно при условии, что в момент его выполнения на том же узле уже не функционирует программа, использующая этот номер порта.

Строка 20 посредством системного вызова listen организует очередь на три входящих к серверу запроса на соединение.

Строка 21 служит заголовком бесконечного цикла обслуживания запросов от клиентов.

На строке 23, содержащей системный вызов accept, выполнение программы

приостанавливается на неопределенное время, если очередь запросов к серверу на установление связи оказывается пуста. При появлении такого запроса accept успешно завершается, возвращая в переменной s_new дескриптор socket'а для обмена информацией с клиентом.

В строке 24 сервер с помощью системного вызова write отправляет клиенту вопрос.

В строке 25 с помощью системного вызова read читается ответ клиента.

В строке 26 ответ направляется в стандартный вывод, имеющий дескриптор файла номер

Так как строка ответа содержит в себе символ перехода на новую строку, то текст ответа будет размещен на отдельной строке дисплея.

Строка 27 содержит системный вывод shutdown, обеспечивающий очистку системных буферов socket'а, содержащих данные для чтения (" лишние" данные могут там оказаться в результате неверной работы клиента).

В строке 28 закрывается (удаляется) socket, использованный для обмена данными с очередным клиентом.

Данная программа (как и большинство реальных программ-серверов)

самостоятельно своей работы не завершает, находясь в бесконечном цикле обработки запросов клиентов. Ее выполнение может быть прервано только извне путем посылки ейсигналов (прерываний) завершения. Правильно разработанная программа-сервер должно брабатывать такие сигналы, корректно завершая работу (закрывая, в частности, посредством close socket с дескриптором s).

 

Программа-клиент

Текст программы-клиента на языке программирования СИ выглядит следующим образом

 

1 #include < sys/types.h>

2 #include < sys/socket.h>

3 #include < netinet/in.h>

4 #include < netdb.h>

5 #include < memory.h>

6 #define SRV_HOST " delta"

7 #define SRV_PORT 1234

8 #define CLNT_PORT 1235

9 #define BUF_SIZE 64

10 #define TXT_ANSW " I am your client\n"

11 main () {

12 int s;

13 int from_len;

14 char buf[BUF_SIZE];

15 struct hostent *hp;

16 struct sockaddr_in clnt_sin, srv_sin;

17 s = socket (AF_INET, SOCK_STREAM, 0);

18 memset ((char *)& clnt_sin, '\0', sizeof(clnt_sin));

19 clnt_sin.sin_family = AF_INET;

20 clnt_sin.sin_addr.s_addr = INADDR_ANY;

21 clnt_sin.sin_port = CLNT_PORT;

22 bind (s, (struct sockaddr *)& clnt_sin, sizeof(clnt_sin));

23 memset ((char *)& srv_sin, '\0', sizeof(srv_sin));

24 hp = gethostbyname (SRV_HOST);

25 srv_sin.sin_family = AF_INET;

26 memcpy ((char *)& srv_sin.sin_addr, hp-> h_addr, hp-> h_length);

27 srv_sin.sin_port = SRV_PORT;

28 connect (s, & srv_sin, sizeof(srv_sin));

29 from_len = recv (s, buf, BUF_SIZE, 0);

30 write (1, buf, from_len);

31 send (s, TXT_ANSW, sizeof(TXT_ANSW), 0);

32 close (s);

33 exit (0);

34 }

В строках 6 и 7 описываются константы SRV_HOST и SRV_PORT, определяющие имя удаленного узла, на котором функционирует программа-сервер, и номер порта, к которому привязан socket сервера.

Строка 8 приписывает целочисленной константе 1235 символическое имя CLNT_PORT. В дальнейшем эта константа будет использована в качестве номера порта клиента.

В строках 17...22 создается привязанный к порту на локальном узле socket.

В строке 24 посредством библиотечной функции gethostbyname транслируется символическое имя удаленного узла (в данном случае " delta" ), на котором должен функционировать сервер, в адрес этого узла, размещенный в структуре типа hostent.

В строке 26 адрес удаленного узла копируется из структуры типа hostent в

соответствующее поле структуры srv_sin, которая позже (в строке 28) используется в системном вызове connect для идентификации программы-сервера.

В строках 29...31 осуществляется обмен данными с сервером и вывод вопроса,

поступившего от сервера, в стандартный вывод.

 

Программа-сервер

Текст программы-сервера на языке программирования СИ выглядит следующим образом

 

1 #include < tiuser.h>

2 #include < fcntl.h>

3 #include < stdio.h>

4 #include < sys/socket.h>

5 #include < netinet/in.h>

6 #include < netdb.h>

7 #include < memory.h>

8 #include < sys/time.h>

9 #define SRV_PORT 1234

10 #define CONT_TXT " Continue\n"

1 #define CANC_TXT " Cancel\n"

2 main () {

3 int fd;

4 int flags;

5 time_t secs;

6 struct t_bind *bind;

7 struct t_unitdata *ud;

8 struct sockaddr_in *p_addr;

9 extern int t_errno;

10 fd = t_open(" /dev/udp", O_RDWR, NULL);

11 bind = (struct t_bind *) t_alloc (fd, T_BIND, T_ADDR);

12 memset (bind-> addr.buf, '\0', bind-> addr.maxlen);

13 p_addr = (struct sockaddr_in *) bind-> addr.buf;

14 p_addr-> sin_family = AF_INET;

15 p_addr-> sin_addr.s_addr = INADDR_ANY;

16 p_addr-> sin_port = SRV_PORT;

17 bind-> addr.len = sizeof(struct sockaddr_in);

18 bind-> qlen = 0;

19 t_bind (fd, bind, bind);

20 ud = (struct t_unitdata *)t_alloc(fd, T_UNITDATA, T_ALL);

21 while (1) {

22 t_rcvudata (fd, ud, & flags);

23 write (1, ud-> udata.buf, ud-> udata.len);

24 secs = time (NULL);

25 if (secs % 3) {

26 strcpy (ud-> udata.buf, CONT_TXT);

27 ud-> udata.len = sizeof(CONT_TXT); 38 }

39 else {

40 strcpy (ud-> udata.buf, CANC_TXT);

41 ud-> udata.len = sizeof(CANC_TXT); 42 };

43 t_sndudata (fd, ud);

44 };

45 }

Строки 1...8 описывают включаемые файлы, содержащие определения для всех необходимых структур данных и символических констант. Строка 9 приписывает целочисленной константе 1234 символическое имя SRV_PORT. В дальнейшем эта константа будет использована в качестве номера порта сервера. Значение этой константы должно быть известно и программе-клиенту. Строки 10 и 11 приписывают последовательностям символов, составляющих тексты возможных ответов клиенту, символические имена CONT_TXT и CANC_TXT. Последним символом в последовательностях является символ перехода на новую строку '\n'. Сделано это для упрощения вывода текста ответа на стороне клиента. В строке 20 создается (открывается) транспортная точка для организации режима взаимодействия без установления логического соединения с помощью протокола транспортного уровня UDP в сети TCP/IP (/dev/udp). Транспортная точка будет использоваться для двустороннего обмена информацией (O_RDWR). Третий аргумент функции t_open задан как NULL, поскольку данную программу особые характеристики поставщика транспортных услуг не интересуют. В строке 21 выделяется оперативная память под структуру данных типа struct t_bind и под буфер данных, определяемый полем addr этой структуры (размер памяти, выделяемой под этот буфер, функция t_alloc вычисляет самостоятельно на основе информации о конкретном поставщике транспортных услуг). Поле addr этой структуры в своем буфере будет содержать транспортный адрес транспортной точки, который для поставщиков транспортных услуг UDP и TCP имеет тот же формат, что и адрес socket'а.

В строках 22...27 сначала обнуляется структура данных типа struct sockaddr_in, на которую указывает bind-> addr.buf а затем заполняются ее отдельные поля. Использование константы INADDR_ANY упрощает текст программы, избавляя от необходимости использовать функцию gethostbyname для получения адреса локального узла, на котором запускается сервер. В строке 28 переменной bind-> qlen присваивается значение 0, поскольку наш сервер предназначен для работы в режиме без установления соединения. Строка 29 посредством функции t_bind привязывает к транспортной точке транспортный адрес, описанный в структуре, на которую указывает bind. T_bind завершится успешно при условии, что в момент его выполнения на том же узле уже не функционирует программа, использующая этот же транспортный адрес. Строка 31 служит заголовком бесконечного цикла обслуживания запросов от клиентов. В строке 32 с помощью функции t_rcvudata читается запрос клиента. В строке 33 текст запроса направляется в стандартный вывод, имеющий дескриптор файла номер 1. Так как строка ответа содержит в себе символ перехода на новую строку, то текст ответа будет размещен на отдельной строке дисплея. Строка 34 содержит обращение к функции time, возвращающей количество секунд времени, прошедших с 1 января 1970 г. до текущего момента. Это значение используется в программе-сервере для выбора варианта ответа клиенту. В строке 43 сервер с помощью функции t_sndudata отправляет клиенту ответ на его запрос, выбранный из двух возможных вариантов псевдослучайным образом.

 

Примечание. Данная программа (как и большинство реальных программ-серверов) самостоятельно своей работы не завершает, находясь в бесконечном цикле обработки запросов клиентов. Ее выполнение может быть прервано только извне путем посылки ей сигналов (прерываний) завершения. Правильно разработанная программа-сервер должна обрабатывать такие сигналы, корректно завершая работу.

Программа-клиент

Текст программы-клиента на языке программирования СИ выглядит следующим образом

 

1 #include < tiuser.h>

2 #include < fcntl.h>

3 #include < stdio.h>

4 #include < sys/socket.h>

5 #include < netinet/in.h>

6 #include < netdb.h>

7 #include < memory.h>

8 #define SRV_HOST " delta"

9 #define SRV_PORT 1234

10 #define ASK_TXT " What must I do? \n"

11 #define CONT_TXT " Continue\n"

12 #define CANC_TXT " Cancel\n"

13 main () {

14 int fd;

15 int flags;

16 struct t_unitdata *ud;

17 struct sockaddr_in *p_addr;

18 struct hostent *hp;

19 extern int t_errno;

20 fd = t_open (" /dev/udp", O_RDWR, NULL);

21 t_bind (fd, NULL, NULL);

22 ud = (struct t_unitdata *) t_alloc (fd, T_UNITDATA, T_ALL);

23 memset (ud-> addr.buf, '\0', ud-> addr.maxlen);

24 p_addr = (struct sockaddr_in *) ud-> addr.buf;

25 hp = gethostbyname (SRV_HOST);

26 p_addr-> sin_family = AF_INET;

27 memcpy((char *)& (p_addr-> sin_addr), hp-> h_addr, hp-> h_length);

28 p_addr-> sin_port = SRV_PORT;

29 ud-> addr.len = sizeof(struct sockaddr_in);

30 while (1) {

31 strcpy (ud-> udata.buf, ASK_TXT);

32 ud-> udata.len = sizeof(ASK_TXT);

33 t_sndudata (fd, ud);

34 t_rcvudata (fd, ud, & flags);

35 write (1, ud-> udata.buf, ud-> udata.len);

36 if ( strcmp(ud-> udata.buf, CONT_TXT) )

37 break;

38 };

39 t_free ((char *) ud, T_UNITDATA);

40 t_close (fd);

41 exit (0);

42 }

В строках 8 и 9 описываются константы SRV_HOST и SRV_PORT, определяющие имя удаленного узла, на котором функционирует программа-сервер, и номер порта, к которому привязана транспортная точка сервера. В строках 20 и 21 создается транспортная точка, имеющая не интересующий нас в этой программе транспортный адрес.

В строке 22 выделяется оперативная память под структуру данных типа struct t_unitdata и под три буфера, определяемых полями addr, opt и udata этой структуры (размер памяти, выделяемой под эти буфера, функция t_alloc вычисляет самостоятельно на основе информации о конкретном поставщике транспортных услуг). В строке 25 посредством библиотечной функции gethostbyname транслируется символическое имя удаленного узла (в данном случае " delta" ), на котором должен функционировать сервер, в адрес этого узла, размещенный в структуре типа hostent. В строке 27 адрес удаленного узла копируется из структуры типа struct hostent в соответствующее поле структуры типа struct sockaddr_in, которая размещена в буфере ud- > addr. В строках 31 и 32 заполняются поля структуры ud-> udata передаваемой серверу информацией и длиной этой информации. В строке 33 с помощью функции t_sndudata посылается запрос серверу. В строке 34 с помощью функции t_rcvudata принимается ответ от сервера. При этом транспортный адрес транспортной точки отправителя ответа (сервера) размещается функцией в ud-> addr, а сами данные, составляющие ответ, - в ud-> udata. В строке 39 освобождается оперативная память, занимавшаяся структурой типа struct t_unitdata. Строка 40 посредством функции t_close закрывает (удаляет) транспортную точку.

 

Программа HttpClient

 

 

#include < stdlib.h> // stdlib functions

#include < unistd.h> // getopt

#include < stdio.h> // printf, etc

#include < string.h> // strncpy, etc

#include < sys/socket.h> // sockets API

#include < netdb.h> // gethostbyname

#include < errno.h> // errno

// Необходимо для getopt

extern char *optarg;

// Функция main, входная точка программы

int main(int argc /*количество аргументов*/, char* argv[]/*массив аргументов*/)

{

// Текст помощи

const char* usageInfo =

" Использование: ./HttpClient [-u http: //host[: port][/page]] [-h]\n"

" \t-u - URL для получения страницы.\n"

" \t-h - эта справка.\n";

// Буффер для строчки с URL

static const int MAX_URL_LEN = 1024;

char urlBuffer[MAX_URL_LEN];

memset(urlBuffer, 0, MAX_URL_LEN);

// Чтение аргументов командной строки в стиле Си

int opt;

while ((opt = getopt(argc, argv, " hu: " ))! = -1)

{

switch (opt)

{

case 'u':

{

// Использование strncpy вместо strcpy дает защиту от переполнения буфера

strncpy(urlBuffer, optarg, MAX_URL_LEN);

break;

}

case 'h':

default:

{

// Напечатать помощь и выйти

printf(usageInfo);

exit(EXIT_FAILURE);

}

}

}

// если конфигурация после прочтения всех опций неполна - напечатать помощь и выйти

if (strnlen(urlBuffer, MAX_URL_LEN) == 0)

{

printf(usageInfo);

return EXIT_FAILURE;

}

// Данные для разбора URL в стиле Си

char urlHost[MAX_URL_LEN]; // Хост

memset(urlHost, 0, MAX_URL_LEN);

char urlPage[MAX_URL_LEN]; // Адрес страницы

memset(urlPage, 0, MAX_URL_LEN);

strncpy(urlPage, " index.html", MAX_URL_LEN); // Предопределенное значение адреса страницы index.html

int urlPort = 80; // Порт, предопределенное значение 80

// Пропустить http: //

char* pBuffer = strstr(urlBuffer, " http: //" );

if (pBuffer! = 0)

pBuffer = urlBuffer + strnlen(" http: //", MAX_URL_LEN);

else

pBuffer = urlBuffer;

// Разбиение строки в разных кобминациях имени хоста, порта и адреса страницы на сервере

if (sscanf(pBuffer, " %99[^: ]: %i/%199[^\n]", urlHost, & urlPort, urlPage) == 3) {}

else if (sscanf(pBuffer, " %99[^/]/%199[^\n]", urlHost, urlPage) == 2) {}

else if (sscanf(pBuffer, " %99[^: ]: %i[^\n]", urlHost, & urlPort) == 2) {}

else if (sscanf(pBuffer, " %99[^\n]", urlHost) == 1) {}

else

{

printf(" Не удалось разобрать строчку URL" );

printf(usageInfo);

return EXIT_FAILURE;

}

// Данные необходимые для соединения и обмена данными

// Структуры для адресов

struct addrinfo hints;

struct addrinfo *result;

struct addrinfo *rp;

// Сокет для обмена данными

int sfd; // socket file descriptor

// код ошибки

int s;

static const int BUF_SIZE = 256*1024;

// Буфер памяти для сетевого обмена, фиксированной длинны на стеке

char buf[BUF_SIZE];

// Инициализация структур для получения ip адреса по имени хоста

memset(& hints, 0, sizeof(struct addrinfo));

hints.ai_family = AF_UNSPEC;

hints.ai_socktype = SOCK_STREAM;

snprintf(buf, BUF_SIZE, " %d", urlPort);

// Получение набора адресов соответствующих имени хоста urlHost

s = getaddrinfo(urlHost, buf, & hints, & result);

if (s! = 0)

{

fprintf(stderr, " Ошибка getaddrinfo: %s\n", gai_strerror(s));

perror(strerror(errno));

return EXIT_FAILURE;

}

// getaddrinfo() Возвращает набор структур с адресами

// Будем пробовать все подряд пока не соединимся

for (rp = result; rp! = NULL; rp = rp-> ai_next)

{

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

sfd = socket(rp-> ai_family, rp-> ai_socktype, rp-> ai_protocol);

if (sfd == -1)

continue;

// Попытка соединения, выход из цикла если удачно

if (connect(sfd, rp-> ai_addr, rp-> ai_addrlen)! = -1)

break;

// Закрытие сокета

close(sfd);

}

// Если ни один адрес не подошел

if (rp == NULL)

{

fprintf(stderr, " Неудается установить соединение\n" );

return EXIT_FAILURE;

}

// Освобождение структур содержащих разрезолвленные адреса

freeaddrinfo(result);

// Подготовка простейшего HTTP запроса

char requestPattern[] = " GET /%s HTTP/1.1\r\n"

" Host: %s\r\n"

" Connection: close\r\n"

" Accept: text/html; q=1.0\r\n"

" Accept-Language: en-US, en; q=1.0\r\n\r\n";

snprintf(buf, BUF_SIZE, requestPattern, urlPage, urlHost);

// Отправка запроса

// total - количество байт к отправке

int total = strnlen(buf, BUF_SIZE);

// sent - общее количество отправленных байт

int sent = 0;

// bytes - количество байт отправленных за последний заход

int bytes;

do

{

// попытка записи в сокет с текущего места и до конца

bytes = write(sfd, buf + sent, total - sent);

if (bytes < 0)

{

printf(" Ошибка при записи в сокет" );

perror(strerror(errno));

return EXIT_FAILURE;

}

if (bytes == 0)

break;

sent += bytes;

} while (sent < total); // Отсылка данных пока есть, что посылать

// Очистка буфера сетевого обмена

memset(buf, 0, BUF_SIZE);

// Максимальный размер получаемых данных равен размеру буфера

total = BUF_SIZE;

// Общее количество полученных байт

int received = 0;

do

{

// Чтение в буфер поэтапно

bytes = read(sfd, buf + received, total - received);

if (bytes < 0)

{

printf(" Ошибка при чтении из сокета" );

perror(strerror(errno));

return EXIT_FAILURE;

}

// Получение ноля байт говорит о том, что противополжная сторона закрыла соединение

if (bytes == 0)

break;

received += bytes;

} while (received < total); // Получение данных пока есть место в буфере

// Если буфер закончился и нехватает места на терминирующий ноль - случилось переполнение

if (received == total)

{

printf(" Ошибка при чтении из сокета - буфер переполнен" );

exit(EXIT_FAILURE);

}

// Нультерминируем буфер после чтения

buf[received] = 0;

// Закрываем сокет

close(sfd);

// Выводим ответ сервера

printf(" Ответ: \n%s\n", buf);

// Успешное завершение

return EXIT_SUCCESS;

}

Примечание. Данная клиентская программа по заданному URL определяет у Web-сервера его IP-адреса и по первому адресу, содержащемуся в полученной структуре, запрашивает у сервера его главную страницу. В программе используется функция perror() которая записывает в переменную errno код ошибки.


 

Приложение А.

Accept()

Принимает входные подключения на слушающем сокете.

Прототип

 

#include < sys/types.h>

#include < sys/socket.h>

int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

Описание

accept() вызываетcя чтобы получить новый дескриптор сокета для последующего общения с только что подключённым клиентом.

 

s addr
Дескриптор слушаемого сокета. Заполняется адресом подключающегося сайта.
addrlen Заполняется размером (sizeof())структуры, возвращённой в параметре addr. Его можно игнорировать, если полагаете, что получили назад struct sockaddr_in, потому что именно такой тип был передан в параметре addr.

Возвращаемый accept() -ом дескриптор определяет уже открытый и подключённый к

.

Возвращаемое значение

accept() возвращает дескриптор только что подключённого сокета, или -1 при ошибке, при этом соответствующим образом установив errno.

 

Пример

 

struct sockaddr_storage their_addr; socklen_t addr_size;

struct addrinfo hints, *res; int sockfd, new_fd;

// сначала заполняем адресные структуры с помощью getaddrinfo(): memset(& hints, 0, sizeof hints);

hints.ai_family = AF_UNSPEC; // использовать либо IPv4 либо IPv6

hints.ai_socktype = SOCK_STREAM;

hints.ai_flags = AI_PASSIVE; // заполнить мой IP для меня getaddrinfo(NULL, MYPORT, & hints, & res);

// создать сокет, связать и слушать:

sockfd = socket(res-> ai_family, res-> ai_socktype, res-> ai_protocol); bind(sockfd, res-> ai_addr, res-> ai_addrlen);

listen(sockfd, BACKLOG);

// теперь принять входящие подключения: addr_size = sizeof their_addr;

new_fd = accept(sockfd, (struct sockaddr *)& their_addr, & addr_size);

// можно беседовать по дескриптору сокета new_fd!

 

Bind()

Связывает сокет с IP адресом и номером порта.

 

Прототип

 

#include < sys/types.h>

#include < sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

Описание

Когда удалённая машина хочет связаться с вашей серверной программой, ей для этого нужны IP адрес и номер порта. Это можно сделать вызов bind().

Сначала нужно вызывать getaddrinfo(), заполнить struct sockaddr адресом назначения и информацией порта. Затем вызывается socket() чтобы получить дескриптор сокета и передаёте сокет и адрес в bind(), и вот IP адрес привязан к сокету!

Если свой IP адреса неизвестен или известно, что у вашей машины только один IP адрес, или вам безразлично, какие IP адреса используются, нужно установить флаг AI_PASSIVE в параметре hints при вызове getaddrinfo(). При этом в часть IP адреса в struct sockaddr записывается специальное значение, которое указывает bind(), что ей нужно автоматически заполнить этот IP адрес хоста.

Это специальное значение записывается в IP адрес struct sockaddr чтобы автоматически установить адрес текущего хоста. Но нужно помнить, что это происходит только при заполнении struct sockaddr вручную, иначе необходимо воспользоваться результатом getaddrinfo(), как указано выше. В IPv4, поле sin_addr.s_addr структуры struct sockaddr_in устанавливается INADDR_ANY. В IPv6, в поле sin6_addr структуры sockaddr_in6 записывается значение глобальной переменной in6addr_any. Или, если объявлена новая struct in6_addr, можно инициализировать её IN6ADDR_ANY_INIT.

Наконец, параметр addrlen должен быть установлен в sizeof my_addr.

Возвращаемое значение

Возвращает 0 при успехе или -1 в случае ошибки ( errno устанавливается соответственно).

Пример

 

// современный способ работы с getaddrinfo()

struct addrinfo hints, *res; int sockfd;

// сначала заполняем адресные структуры с помощью getaddrinfo(): memset(& hints, 0, sizeof hints);

hints.ai_family = AF_UNSPEC; // использовать либо IPv4 либо IPv6

hints.ai_socktype = SOCK_STREAM;

hints.ai_flags = AI_PASSIVE; // заполнить мой IP для меня getaddrinfo(NULL, " 3490", & hints, & res);

// создать сокет:

// (вам нужно прогуляться по связанному списку " res" и проверить на ошибки! )

sockfd = socket(res-> ai_family, res-> ai_socktype, res-> ai_protocol);

// связать с портом, переданным getaddrinfo(): bind(sockfd, res-> ai_addr, res-> ai_addrlen);

// пример упаковки структуры вручную, IPv4

struct sockaddr_in myaddr; int s;

myaddr.sin_family = AF_INET; myaddr.sin_port = htons(3490);

// можете указать IP адрес:

inet_pton(AF_INET, " 63.161.169.137", & (myaddr.sin_addr));

// или позволить выбрать его автоматически: myaddr.sin_addr.s_addr = INADDR_ANY;


Поделиться:



Последнее изменение этой страницы: 2017-04-12; Просмотров: 295; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.45 с.)
Главная | Случайная страница | Обратная связь