Лабораторная работа № 9. Использование механизма сокетов с сетевыми протоколами, не ориентированными на установление соединения

 

Цель: Изучение механизма сокетов в операционной системе Windows.

Задачи:

1. Изучение теоретического материала по сокетам с сетевыми протоколами, не ориентированными на установление соединения.

2. Составление алгоритма программы.

3. Программная реализация.

Ход работы:

1. Ознакомиться с описанием функций WinSock API, которые служат для работы с протоколами, которые не устанавливают соединение.

2. Модифицировать обе программы, написанные в ходе выполнения лабораторной работы №8, используя сетевые протоколы без установления логического соединения (UDP).

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

Ход защиты:

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

2. Пояснить программный код разработанных приложений.

 

Основная цель разработки спецификации WinSock 2 – создание независимого от протоколов транспортного интерфейса.

Перед использованием сокетов необходимо загрузить корректную версию библиотеки WinSock с помощью функции инициализации

int WSAStartup (

WORD Version, // номер версии

LPWSADATA Data); // информация о версии

На современных платформах Win 32 используется версия 2.2. Чтобы
загрузить библиотеку этой версии нужно вызвать WSAStartUp следующим образом.

 

#include <winsock2.h>

#include <stdio.h>

#include <stdlib.h>

# pragma comment (lib, “ws2_32.lib”)

void main(void)

{

WSADATA wsData;

if (WSAStartup (0x0202, &wsData)!= 0) {

    printf ("Невозможно загрузить Winsock 2.2\n");

    exit (1);

    }

printf("Библиотека инициализирована.\n");

WSACleanup ();

}

 

Для завершения работы с WinSock необходимо вызвать WSACleanup, которая освобождает занятые ресурсов.

void WSACleanup (void);

 

 

Связь без установления соединения выполняется при помощи пользовательских дейтаграмм протокола UDP. Он не гарантирует надежности,
однако может осуществлять передачу данных нескольким адресатам и принимать их от нескольких источников. В частности, данные, отправляемые клиентом, передаются на сервер немедленно, независимо от готовности (или неготовности) сервера. При получении данных сервер не подтверждает их прием. Данные передаются порциями, называемыми дейтаграммами.

При использовании протокола IP устройствам назначается 32-разрядный IP -адрес версии 4. Для взаимодействия с сервером по TCP клиент должен указать IP -адрес сервера и номер порта службы. Чтобы прослушивать входящие запросы клиента, сервер тоже должен указать IP -адрес и номер порта. В Winsock IP -адрес и порт службы указываются в структуре SOCKADDR_IN:

struct sockaddr_in {

short sin_family;     // для IP должен использоваться AF_INET

u_short sin_port;

// любой свободный порт из диапазона (1024, 65535)

struct in_addr sin_addr; // IP-адрес в 4-байтном виде

char sin_zero[8];    // заполнитель нулями

};

 

Вспомогательная функция inet_addr преобразует IP -адрес из точечной нотации в беззнаковое длинное целое число, в котором байты следуют в соответствии с сетевым порядком следования:

unsigned long inet_addr (const char FAR * cp);

// строка с адресом в десятично-точечной нотации

 

Существуют два специальных адреса INADDR_ANY, который позволяет серверу слушать клиента через любой сетевой интерфейс на несущем компьютере, и INADDR_BROADCAST, позволяющие широковещательно рассылать дейтаграммы по сети.

Несколько замечаний относительно порядка следования байтов. В процессорах x 86 и Pentium представляют многобайтные числа от менее значимого байта к более значимому. В частности, IP -адрес и номер порта хранятся в памяти именно так. Это так называемый системный порядок. При передаче их по сети стандарты требуют, чтобы многобайтные значения представлялись от старшего байта к младшему, что называется сетевым порядком.
Две следующие функции позволяют преобразовывать, соответственно, 4- и 2-байтовые числа из системного порядка в сетевой.

u_long htonl(u_long hostlong);

u_short htons (u_short hostshort);

 

Следующие две функции решают обратную задачу, т. е. переставляют байты из сетевого порядка в системный:

u_long ntohl(u_long netlong);

u_short ntohs (u_short netshort);

Следующий фрагмент демонстрирует, как создается структура с адресом и номером порта при помощи описанных функций.

SOCKADDR_IN InetAddr;

 

INT PortNum = 5101;

 

InetAddr.sin_family = AF_INET;

InetAddr.sin_addr.s_addr = inet_addr ("192.168.123.45");

InetAddr.sin_port.htons (PortNum);

 

Процесс получения данных на сокете, не требующем соединения, прост. Сокет создается функцией

SOCKET socket (

int af, // семейство адресов протокола

          // (для IP надо указать AF_INET)

int type, // тип сокета (для UDP надо указать SOCK_DGRAM)

int namelen); // транспорт (для UDP - IPPROTO_UDP)

 

Открыть IP -сокет при помощи протокола UDP можно следующим
образом.

sock_udp = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);

 

Затем осуществляется привязка сокета к интерфейсу, на котором будут приниматься данные, с помощью функции bind (аналогично протоколам, ориентированным на соединения). После этого нужно просто ожидать
приема входящих данных. Соединения нет, а значит, сокет-приемник получает дейтаграммы от любой станции в сети. Итак, после создания сокета конкретного протокола его надо связать со стандартным адресом при помощи
функции

int bind (

SOCKET s, // сокет, на котором ожидается прием данных

const struct sockaddr FAR* name, // универсальный буфер

int namelen); // размер буфера

 

При возникновении ошибки функция bind возвращает SOCKET_ERROR.

if (bind (sock1, (struct sockaddr *) addr, sizeof (addr)) ==

SOCKET_ERROR) {

printf (“bind () с ошибкой\n”);

}

 

В сетевом программировании главное - отправлять и принимать данные. Для пересылки используют функцию sendto, а для приема - recvfrom. Первая из них определена так:

int sendto (

SOCKET s, // сокет для отправки данных

const char FAR* buf, // буфер для отправляемых данных

int len,  // число отправляемых байтов или размер буфера

int flags, // флаги

struct sockaddr FAR* to, // адрес станции-приемника

int tolen); // длина адреса применика

 

При успешном выполнении sendto вернет количество переданных байтов, иначе – ошибку SOCKET_ERROR. Возвращаемое количество байтов может отличаться от значения параметра len.

 

Функция приема аналогична отправке, она возвращает количество полученных байтов. Если сокет к этому времени был уже закрыт, то возвращаемым значением является 0. Если же возникла ошибка – SOCKET_ERROR.

int recvfrom (

SOCKET s, // сокет для приема данных

char FAR* buf, // буфер с данными для получаемых данных

int len,  // число принимаемых байтов или размер буфера

int flags, // флаги

struct sockaddr FAR* from, // адрес станции-отправителя

int FAR* fromlen); // длина адреса отправителя

 

По окончании работы с сокетом необходимо закрыть соединение и освободить все ресурсы, связанные с дескриптором сокета, с помошью функции closesocket.

int closesocket (SOCKET s); // закрытие сокета

 

Достаточно легко можно организовать прием данных.

#include <windows.h>

#include <winsock2.h>

# pragma comment (lib, “ws2_32.lib”)

...

SOCKET sock1;

int retcode, SendSize;

SOCKADDR_IN sender;

DWORD Length = 4096;

char *recvbuffer = NULL;

 

// выделение буфера приема

recvbuffer = (char*) GlobalAlloc (GMEM_FIXED, Length);

if (!recvbuffer) {

printf (“GlobalAlloc () с ошибкой\n”);

exit (1);

}

for (int i = 0; i < 30; i ++) {

SendSize = sizeof (sender);

retcode = recvfrom (sock1, recvbuffer, Length, 0,

                  (SOCKADDR *)&sender, &SendSize);

if (retcode == SOCKET_ERROR) {

printf (“recvfrom () с ошибкой\n”);

break;

}

  

else if (retcode == 0) {

break;

}

else {

recvbuffer[retcode] = ‘\0’;

printf (“Строку %s прислал %s\n”,

         inet_ntoa(sender.sin_addr), recvbuffer);

}

}

GlobalFree (recvbuffer);

...

 

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

#include <windows.h>

#include <winsock2.h>

# pragma comment (lib, “ws2_32.lib”)

...

SOCKET sock1;

int retcode;

SOCKADDR_IN receiver;

DWORD Length = 64;

char *sendbuffer = NULL;

 

// выделение буфера отправки

sendbuffer = (char*)GlobalAlloc (GMEM_FIXED, Length);

if (!sendbuffer) {

printf (“GlobalAlloc () с ошибкой\n”);

exit (1);

}

 

// Заполнение буфер символами

memset (sendbuffer, ‘+’, Length);

 

for (int i = 0; i < 30; i ++) {

retcode = sendto (sock1, sendbuffer, Length, 0,

               (SOCKADDR *)&receiver, sizeof (receiver));

if (retcode == SOCKET_ERROR) {

printf (“sendto () с ошибкой\n”);

break;

}

else if (retcode == 0) {

break;

}

}

GlobalFree (sendbuffer);

...

 








Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: