МИНИСТЕРСТВО ОБРАЗОВАНИЯ РЕСПУБЛИКИ БЕЛАРУСЬ
УО ПОЛОЦКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ
МЕТОДИЧЕСКИЕ УКАЗАНИЯ И ЗАДАНИЯ
ДЛЯ ПОДГОТОВКИ И ПРАКТИЧЕСКИХ ЗАНЯТИЙ
по дисциплине
«ГЛОБАЛЬНЫЕ ВЫЧИСЛИТЕЛЬНЫЕ СЕТИ»
для студентов-заочников специальностей
1-40 02 01 «Вычислительные машины, системы и сети»
Составитель: Бровко Н.В.
2013 Содержание 1. ФИО и контактная информация о преподавателе
2. Структура дисциплины
3. Примеры программ
4. Вопросы к зачету
5. Список литературы
1. ФИО и контактная информация о преподавателе Бровко Надежда Валерьевна – ассистент кафедры «Вычислительные системы и сети», ауд. 156B к/к., e-mail: [email protected] 2. Структура дисциплины Целью изучения дисциплины является приобретение современных систематизированных знаний по архитектуре и принципам работы глобальных вычислительных сетей, основам их проектирования, методам анализа и синтеза, особенностям программного обеспечения.
Главной задачей курса является приобретение студентами знаний о видах, организации, основах функционирования и применении глобальных вычислительных сетей, а также способностей к самостоятельной разработке сетевых приложений, взаимодействующих при помощи соответствующих сетевых протоколов.
В результате изучения дисциплины студенты должны знать:
основные виды архитектур вычислительных сетей различного назначения;
принципы построения, работу и взаимодействие основных устройств названных систем в процессе их функционирования;
назначение и принципы функционирования основных сетевых протоколов передачи данных;
Основой для изучения курса являются дисциплины: «Системное программное обеспечение», «Локальные вычислительные сети».
3. Примеры программ В ходе самостоятельной работы необходимо научиться разрабатывать следующие приложения: клиент-серверное приложение, реализующее TCP взаимодействие с помощью механизма сокетов; клиент-серверное приложение, реализующее широковещательную рассылку UDP пакетов с помощью механизма сокетов.
Библиотеки, среды и языки программирования: Winsock2, MS Visual Studio и др., С/С++, С#.
Пример 1: клиент-серверное приложение, реализующее TCP взаимодействие с помощью механизма сокетов.
Протокол TCP (Transmission Control Protocol, Протокол контроля передачи) обеспечивает сквозную доставку данных между прикладными процессами, запущенными на узлах, взаимодействующих по сети.
TCP - надежный байт-ориентированный (byte-stream) протокол с установлением соединения. TCP находится на транспортном уровне стека TCP/IP, между протоколом IP и собственно приложением. Протокол IP занимается пересылкой дейтаграмм по сети, никак не гарантируя доставку, целостность, порядок прибытия информации и готовность получателя к приему данных; все эти задачи возложены на протокол TCP.
При получении дейтаграммы, в поле Protocol которой указан код протокола TCP, модуль IP передает данные этой дейтаграммы модулю TCP. Эти данные представляют собой TCPсегмент, содержащий TCP-заголовок и данные пользователя (прикладного процесса).
Модуль TCP анализирует служебную информацию заголовка, определяет, какому именно процессу предназначены данные пользователя, проверяет целостность и порядок прихода данных и подтверждает их прием другой стороне. По мере получения правильной последовательности неискаженных данных пользователя они передаются прикладному процессу.
Socket (гнездо, разъем) - абстрактное программное понятие, используемое для обозначения в прикладной программе конечной точки канала связи с коммуникационной средой, образованной вычислительной сетью. При использовании протоколов TCP/IP можно говорить, что socket является средством подключения прикладной программы к порту локального узла сети. Socket-интерфейс представляет собой просто набор системных вызовов и/или библиотечных функций языка программирования СИ. Ниже рассматривается подмножество функций socket-интерфейса, достаточное для написания сетевых приложений, реализующих модель "клиент-сервер" в режиме с установлением соединения. Все функции сокетов содержаться в wsock32.dll перед тем как написать программу с использование функций сокетов в VisualC++ необходимо задать компилятору чтобы он включил в программу файл wsock32.lib. В меню Рroject выберите пункт settings а там укажите раздел Link, wsock32.lib можно ввести в поле Library modules или project options. Не забудьте поставить #include "Winsock2.h".
Создание сервера Прежде чем воспользоваться функцией socket необходимо проинициализировать процесс библиотеки wsock32.dll вызвав функцию WSAStartup например:
WSADATA WsaData;
int err = WSAStartup (0x0101, &WsaData);
if (err == SOCKET_ERROR) printf ("WSAStartup() failed: %ld\n", GetLastError ());
Теперь объявление переменную типа SOCKET например s :
s = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
Создание socket'а осуществляется следующим системным вызовом Аргумент domain задает используемый для взаимодействия набор протоколов (вид коммуникационной области), для стека протоколов TCP/IP он должен иметь символьное значение AF_INET.
Аргумент type задает режим взаимодействия:
SOCK_STREAM - с установлением соединения;
SOCK_DGRAM - без установления соединения.
Аргумент protocolзадает конкретный протокол транспортного уровня (из нескольких возможных в стеке протоколов). Если этот аргумент задан равным 0, то будет использован протокол "по умолчанию" (TCP для SOCK_STREAM и UDP для SOCK_DGRAM при использовании комплекта протоколов TCP/IP).
При удачном завершении своей работы данная функция возвращает дескриптор socket'а целое неотрицательное число, однозначно его идентифицирующее. Дескриптор socket'а аналогичен дескриптору файла ОС UNIX.
При обнаружении ошибки в ходе своей работы функция возвращает число "-1".
Далее мы задаем параметры для сокета (сервера) для этого нам необходимо объявить структуру SOCKADDR_IN sin далее заполняем параметры для сервера:
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(80);
sin.sin_addr.s_addr = INADDR_ANY;
Структура 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 другие (не интересующие нас) #define s_addr S_un.S_addr Структура SOCKADDR_IN должна быть полностью заполнена перед выдачей системного вызова bind. При этом, если поле sin_addr.s_addr имеет значение INADDR_ANY, то системный вызов будет привязывать к socket'у номер (адрес) локального узла сети.
Для подключения socket'а к коммуникационной среде, образованной вычислительной сетью, необходимо выполнить системный вызов bind, определяющий в принятом для сети формате локальный адрес канала связи со средой. В сетях TCP/IP socket связывается с локальным портом. Системный вызов bind имеет следующий синтаксис:
int bind (s, addr, addrlen) struct SOCKADDR_IN *addr;
int addrlen;
Пример:
err = bind( s, (LPSOCKADDR)&sin, sizeof(sin) );
Аргумент s задает дескриптор связываемого socket'а.
Аргумент addr в общем случае должен указывать на структуру данных, содержащую локальный адрес, приписываемый socket'у. Для сетей TCP/IP такой структурой является SOCKADDR_IN.
Аргумент addrlen задает размер (в байтах) структуры данных, указываемой аргументом addr.
В случае успеха bind возвращает 0, в противном случае - "-1".
Для установления связи "клиент-сервер" используются системные вызовы listen и accept (на стороне сервера), а также connect (на стороне клиента). Для заполнения полей структуры socaddr_in, используемой в вызове connect, обычно используется библиотечная функция gethostbyname, транслирующая символическое имя узла сети в его номер (адрес).
Системный вызов listen выражает желание выдавшей его программы-сервера ожидать запросы к ней от программ-клиентов и имеет следующий вид:
int s;
int n;
Пример:
err = listen( s, SOMAXCONN);
Аргумент s задает дескриптор socket'а, через который программа будет ожидать запросы к ней от клиентов. Socket должен быть предварительно создан системным вызовом socket и обеспечен адресом с помощью системного вызова bind.
Аргумент n определяет максимальную длину очереди входящих запросов на установление связи. Если какой-либо клиент выдаст запрос на установление связи при полной очереди, то этот запрос будет отвергнут.
Признаком удачного завершения системного вызова listen служит нулевой код возврата.
Перед тем как воспользоваться функцией accept сначала объявите ещ одну переменную типа SOCKET, например s1.
SOCKADDR_IN from;
int fromlen=sizeof(from);
s1 = accept(s,(struct sockaddr*)&from, &fromlen);
Это cделано для того что бы узнать IP адрес и порт удаленного компьютера. Что бы вывести на экран IP адрес и порт удаленного компьютера можно воспользоваться следующим кодом:
printf("accepted connection from %s, port %d\n", inet_ntoa(from.sin_addr), htons(from.sin_port)) ;
Для приема запросов от программ-клиентов на установление связи в программах-серверах используется системный вызов accept, имеющий следующий вид:
int accept (s, addr, p_addrlen) struct sockaddr_in *addr;
int *p_addrlen;
Аргумент s задает дескриптор socket'а, через который программа-сервер получила запрос на соединение (посредством системного запроса listen ).
Аргумент addr должен указывать на область памяти, размер которой позволял бы разместить в ней структуру данных, содержащую адрес socket'а программы-клиента, сделавшей запрос на соединение. Никакой инициализации этой области не требуется.
Аргумент p_addrlen должен указывать на область памяти в виде целого числа, задающего размер (в байтах) области памяти, указываемой аргументом addr.
Системный вызов accept извлекает из очереди, организованной системным вызовом listen, первый запрос на соединение и возвращает дескриптор нового (автоматически созданного) socket'а с теми же свойствами, что и socket, задаваемый аргументом s. Этот новый дескриптор необходимо использовать во всех последующих операциях обмена данными.
Кроме того после удачного завершения accept:
область памяти, указываемая аргументом addr, будет содержать структуру данных (для сетей TCP/IP это sockaddr_in), описывающую адрес socket'а программы-клиента, через который она сделала свой запрос на соединение;
целое число, на которое указывает аргумент p_addrlen, будет равно размеру этой структуры данных.
Если очередь запросов на момент выполнения accept пуста, то программа переходит в состояние ожидания поступления запросов от клиентов на неопределенное время (хотя такое поведение accept можно и изменить).
Признаком неудачного завершения accept служит отрицательное возвращенное значение (дескриптор socket'а отрицательным быть не может).
Примечание. Системный вызов accept используется в программах-серверах, функционирующих только в режиме с установлением соединения.
После соединения с клиентом для передачи информации используются команды send и:
BYTE RecvBuffer[1];
while(recv(s1,RecvBuffer,sizeof(RecvBuffer),0)!=SOCKET_ERROR) printf("%c",RecvBuffer[0]);
send(s1,MsgText,sizeof(MsgText),MSG_DONTROUTE);
Цикл while будет выполнятся пока клиент не отключится Для получения данных от партнера по сетевому взаимодействию используется системный вызов recv, имеющий следующий вид int recv (s, buf, len, flags) char *buf;
int len;
int flags;
Аргумент s задает дескриптор socket'а, через который принимаются данные.
Аргумент buf указывает на область памяти, предназначенную для размещения принимаемых данных.
Аргумент len задает длину (в байтах) этой области.
Аргумент flags модифицирует исполнение системного вызова recv. При нулевом значении этого аргумента вызов recv полностью аналогичен системному вызову read.
При успешном завершении recv возвращает количество принятых в область, указанную аргументом buf, байт данных. Если канал данных, определяемый дескриптором s, оказывается "пустым", то recv переводит программу в состояние ожидания до момента появления в нем данных.
Для посылки данных партнеру по сетевому взаимодействию используется системный вызов send, имеющий следующий вид 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 переводит программу в состояние ожидания до момента его освобождения.
Для закрытия ранее созданного socket'а используется обычный системный вызов closesocket, применяемый в ОС UNIX для закрытия ранее открытых файлов и имеющий следующий вид int closesocket(s) int s;
Аргумент s задает дескриптор ранее созданного socket'а.
Однако в режиме с установлением логического соединения (обеспечивающем, как правило, надежную доставку данных) внутрисистемные механизмы обмена будут пытаться передать/принять данные, оставшиеся в канале передачи на момент закрытия socket'а. На это может потребоваться значительный интервал времени, неприемлемый для некоторых приложений. В такой ситуации необходимо использовать описываемый далее системный вызов shutdown.
Создание клиента Программа клиента делается аналогично до момента создания сокетов. Cоздайте сокет так как описано выше, но не пользуйтесь командой bind:
SOCKADDR_IN anAddr;
anAddr.sin_family = AF_INET;
anAddr.sin_port = htons(80);
anAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
Заполнение структуры производится почти также но нужно указать теперь IP адрес сервера (пример 127.0.0.1 ).Дальше сразу можно соединятся:
connect(s, (struct sockaddr *)&anAddr, sizeof(struct sockaddr));
Для обращения программы-клиента к серверу с запросом на установление логической соединения используется системный вызов connect, имеющий следующий вид 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,то такая привязка будет выполнена автоматически.
Вот наконец установлена долгожданная связь c сервером( не забывайте проверять ошибки). Дальше воспользуемся функциями send и recv по своему усмотрению.
Приложение Для получения адреса узла сети TCP/IP по его символическому имени используется библиотечная функция struct hostent *gethostbyname (name) char *name;
Аргумент name задает адрес последовательности литер, образующих символическое имя узла сети.
При успешном завершении функция возвращает указатель на структуру hostent, определенную в include-файле netdb.h и имеющую следующий вид struct hostent char *h_name;
char **h_aliases;
int h_addrtype;
int h_lenght;
char *h_addr;
Поле h_name указывает на официальное (основное) имя узла.
Поле h_aliases указывает на список дополнительных имен узла (синонимов), если они есть.
Поле h_addrtype содержит идентификатор используемого набора протоколов, для сетей TCP/IP это поле будет иметь значение AF_INET.
Поле h_length содержит длину адреса узла.
Поле h_addr указывает на область памяти, содержащую адрес узла в том виде, в котором его используют системные вызовы и функции socket-интерфейса.
Для "экстренного" закрытия связи с партнером (путем "сброса" еще не переданных данных) используется системный вызов shutdown, выполняемый перед close и имеющий следующий вид int shutdown (s, how) Аргумент s задает дескриптор ранее созданного socket'а.
Аргумент how задает действия, выполняемые при очистке системных буферов socket'а:
0 - сбросить и далее не принимать данные для чтения из socket'а;
1 - сбросить и далее не отправлять данные для посылки через socket;
2 - сбросить все данные, передаваемые через socket в любом направлении.
Пример 2: клиент-серверное приложение, реализующее широковещательную рассылку UDP пакетов с помощью механизма сокетов.
Датаграмные сокеты используют UDP, ненадежный протокол без установления соединения. UDP сервер не должен прослушивать (listen) и принимать (accept) клиентские соединения, а UDP клиент не должен устанавливать соединение с сервером (connect). На рисунке 1 показано взаимодействие UDP сервера и UDP клиента.
Рисунок 1 - Схема взаимодействия UDP сервера и UDP клиента Создание сервера на базе UDP дататаграммного сокета 2. Используйте AF_INET в качестве формата адреса (address format parameter) и SOCK_DGRAM для параметра типа (type parameter).
int WinSocket;
if((WinSocket=socket(AF_INET,SOCK_DGRAM,0))==INVALID_SOCKET) printf("Allocating socket failed. Error: %d", Именуйте сокет функцией bind, используя структуру SOCKADDR_IN для параметра адреса (address parameter). Следующий пример реализует инициализацию (именование) сокета.
#define DEST_PORT SOCKADDR_IN local_sin, recv_sin;
// Fill out the local socket’s address information.
local_sin.sin_family = AF_INET;
local_sin.sin_port = htons (DEST_PORT);
local_sin.sin_addr.s_addr = htonl (INADDR_ANY);
// Associate the local address with WinSocket.
if (bind (WinSocket,(struct sockaddr *)&local_sin, sizeof (local_sin)) == SOCKET_ERROR) printf ("Binding socket failed. Error: %d", WSAGetLastError ());
closesocket (WinSocket);
4. Обмен данными с клиентом осуществляется функциями sendto и recvfrom. При использовании функции sendto успешная отправка пакета не означает его успешную доставку.
char szMessageA [100];
TCHAR szMessageW [100];
int iRecvLen, index;
iRecvLen = sizeof (recv_sin);
// Получение данных от клиента.
printf("Wait message from client.");
if(recvfrom(WinSocket,szMessageA,sizeof(szMessageA),0, (struct sockaddr *) &recv_sin, &iRecvLen) == SOCKET_ERROR) printf("recvfrom faild! Error: %d", WSAGetLastError ());
closesocket (WinSocket);
return -1;
else // Convert the ASCII string to Unicode.