
- •Введение
- •Лабораторная работа № 1. 1. Основные теоретические положения (интерфейс windows sockets)
- •1.1. Сокеты, датаграммы и каналы связи
- •1.1.1. Инициализация приложения и завершение его работы
- •1.1.2. Создание и инициализация сокета
- •1.3. Индивидуальные задания на работу
- •Лабораторная работа № 2. 2. Создание канала связи
- •2.1. Сторона сервера
- •2.2. Сторона клиента
- •2.3. Передача и прием данных
- •2.4. Приложение server
- •2.5. Приложение client
- •2.6. Индивидуальные задания на работу
- •Лабораторная работа № 3. 3. Датаграммный протокол
- •3.1. Приложение serverd
- •3.2. Приложение clientd
- •3.3. Индивидуальные задания на работу
- •Литература
1.1.2. Создание и инициализация сокета
После инициализации интерфейса WindowsSockets приложение должно создать один или несколько сокетов, которые будут использованы для передачи данных.
1.1.2.1. Создание сокета
Сокет создается с помощью функции socket, имеющей следующий прототип:
SOCKET socket(int af, int type, int protocol);
Параметр af определяет формат адреса. Для этого параметра следует указывать значение AF_INET, что соответствует формату адреса, принятому в Internet.
Параметры type и protocol определяют соответственно тип сокета и протокол, который будет использован для данного сокета.
Можно указывать сокеты следующих двух типов:
Тип сокета Описание
SOCK_STREAM Сокет будет использован для передачи данных через канал связи с использованием протокола TCP
SOCK_DGRAM Передача данных будет выполняться без создания каналов связи через датаграммный протокол UDP
Что же касается параметра protocol, то можно указать для него нулевое значение. В случае успеха функция socket возвращает дескриптор, который нужно использовать для выполнения всех операций над данным сокетом. Если же произошла ошибка, эта функция возвращает значение INVALID_SOCKET. Для анализа причины ошибки можно вызвать функцию WSAGetLastError, которая в данном случае может вернуть один из следующих кодов ошибки:
Код ошибки Описание
WSANOTINIT1ALISED Интерфейс WindowsSockets не был проинициализирован функцией WSAStanup
WSAENETDOWN Сбой сетевого программного обеспечения
WSAEAFNOSUPPORT Указан неправильный тип адреса
WSAEINPROGRESS Выполняется блокирующая функция интерфейса WindowsSockets
WSAEMFILE Израсходован весь запас свободных дескрипторов
WSAENOBUFS Нет памяти для создания буфера
WSAEPROTONOSUPPORT Указан неправильный протокол
WSAEPROTOTYPE Указанный протокол несовместим с данным типом сокета
WSAESOCKTNOSUPPORT Указанный тип сокета несовместим с данным типом адреса
Ниже приведен фрагмент кода, в котором создается сокет для передачи данных с использованием протокола TCP:
srv_socket = socket(AF_INET, SOCK_STREAM, 0);
if(srv_socket == INVALID_SOCKET)
{
MessageBox(NULL, "socket Error", "Error", MB_OK);
return;
}
1.1.2.2. Удаление сокета
Для освобождения ресурсов приложение должно закрывать сокеты, которые ему больше не нужны, вызывая функцию closesocket:
int closesocket(SOCKET sock);
Ниже перечислены коды ошибок для этой функции:
Код ошибки Описание
WSANOTINIT1ALISED Перед использованием функции closesocket необходимо вызвать функцию WSAStartup
WSAENETDOWN Сбой в сети
WSAENOTSOCK Указанный в параметре дескриптор не является сокетом
WSAEINPROGRESS Выполняется блокирующая функция интерфейса WindowsSockets
WSAEINTR Работа функции была отменена при помощи функции WSACancelBlockingCall
1.1.2.3. Параметры сокета
Перед использованием необходимо задать параметры сокета.
Для этого надо подготовить структуру типа sockaddr, определение которой показано ниже:
struct sockaddr
{
u_short sa_family;
char sa_data[14];
};
typedef struct sockaddr SOCKADDR;
typedef struct sockaddr *PSOCKADDR;
typedef struct sockaddr FAR *LPSOCKADDR;
Для работы с адресами в формате Internet используется другой вариант этой структуры, в котором детализируется формат поля sa_data:
struct sockaddr_in
{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr_in *PSOCKADDR_IN;
typedef struct sockaddr_in FAR *LPSOCKADDR_IN;
Поле sin_family определяет тип адреса. Необходимо записать в это поле значение AF_lNET, которое соответствует типу адреса, принятому в Internet:
srv_address.sin_family = AF_INET;
Поле sin_port определяет номер порта, который будет использоваться для передачи данных. Порт - это просто идентификатор программы, выполняющей обмен в сети. На одном узле может одновременно работать несколько программ, использующих разные порты.
Особенностью поля sin_port является использование так называемого сетевого формата данных. Этот формат отличается от того, что принят в процессорах с архитектурой Intel, а именно младшие байты данных хранятся по старшим адресам памяти. Напомним, что архитектура процессоров Intel подразумевает хранение младших байтов данных по младшим адресам.
Универсальный сетевой формат данных удобен при организации глобальных сетей, так как в узлах такой сети могут использоваться компьютеры с различной архитектурой.
Для выполнения преобразований из обычного формата в сетевой и обратно в интерфейсе WindowsSockets предусмотрен специальный набор функций. В частности, для заполнения поля sin_port нужно использовать функцию htons, выполняющую преобразование 16-разрядных данных из формата Intel в сетевой формат. Ниже показано, как инициализируется полеsin_port в приложении SERVER, описанном далее:
#define SERV_PORT 5000 srv_address.sin_port = htons(SERV_PORT);
Вернемся снова к структуре sockaddr_in. Поле sin_addr этой структуры в свою очередь представляет собой структуру in_addr
struct in_addr
{
union
{
struct {u_char s_b1, s_b2, s_b3, s_b4;} S_un_b;
struct {u_short s_w1, s_w2;} S_un_w;
u_long S_addr;
} S_un;
};
#define s_addr S_un.S_addr
#define s_host S_un.S..un_b.s_b2
#define s_net S_un.S_un_b.s_bl
#define s_imp S_un.S_un_w.s_w2
#define s_impno S_un.S_un_b.s_b4
#define s_lh S_un.S_un_b.s_b3
При инициализации сокета в этой структуре следует указать адрес IP, с которым будет работать данный сокет.
Если сокет будет работать с любым адресом (например, создается сервер, который будет доступен из узлов с любым адресом), адрес для сокета можно указать следующим образом:
srv_address.sin_addr.s_addr = INADDR_ANY;
В том случае, если сокет будет работать с определенным адресом IP (например, создается приложение-клиент, которое будет обращаться к серверу с конкретным адресом IP), в указанную структуру необходимо записать реальный адрес.
Датаграммный протокол UDP позволяет посылать пакеты данных одновременно всем рабочим станциям в широковещательном режиме. Для этого надо указать адрес как INADDR_BROADCAST.
Если известен адрес в виде четырех десятичных чисел, разделенных точкой (именно так его вводит пользователь), то можно заполнить поле адреса при помощи функции inet_addr:
dest_sin.sin_addr.s_addr = inet_addr("200.200.200.201");
В случае ошибки функция возвращает значение INADDR_NONE, что можно использовать для проверки.
Обратное преобразование адреса IP в текстовую строку можно при необходимости выполнить с помощью функции inet_ntoa, имеющей следующий прототип:
char FAR *inet_ntoa(struct in_addr in);
При ошибке эта функция возвращает значение NULL.
Однако чаще всего приходится работать с доменными именами, применяя сервер DNS или файл HOSTS. В этом случае вначале следует воспользоваться функцией gethostbyname, возвращающей адрес IP, а затем записать полученный адрес в структуру sin_addr.
PHOSTENT phe;
phe = gethostbyname("ftp.microsoft.com");
if(phe == NULL)
{
closesocket(srv_socket);
MessageBox(NULL, "gethostbyname Error", "Error", MВ_ОК);
return;
}
memcpy((char FAR *)&(dest_sin.sin_addr), phe->h_addr, phe->h_length);
В случае ошибки функция gethostbyname возвращает null. При этом причину ошибки можно выяснить, проверив код возврата функции WSAGetLastError.
Если же указанный узел найден в базе DNS или в файле HOSTS, функция gethostbyname возвращает указатель на структуру hostent, описанную ниже:
struct hogtent
{
char FAR * h_name; // имя узла
char FAR *FAR *h_aliases; // список альтернативных имен
short h_addrtype; // тип адреса узла
short h_length; // длина адреса
char FAR *FAR *h_addr_list; // список адресов
#define h_addr h_addr_list[0] // адрес
};
typedef struct hostent *PHOSTENT;
typedef struct hostent FAR *LPHOSTENT;
Искомый адрес находится в первом элементе списка h_addr_list[0], на который можно также ссылаться при помощи h_addr. Длина поля адреса находится в поле h_lenglh.
1.1.2.4. Привязка адреса к сокету
После того как подготовлена структура SOCKADDR (записью в нее параметров сокета, в частности, адреса), следует выполнить привязку адреса к сокету при помощи функции bind:
int bind(SOCKET sock, const struct sockaddr FAR *addr, int namelen);
Параметр sock должен содержать дескриптор сокета, созданного функцией socket. В поле addr следует записать указатель на подготовленную структуру SOCKADDR, а в поле namelen - размер этой структуры.
В случае ошибки функция bind возвращает значение SOCKET_ERROR. Дальнейший анализ причин ошибки следует выполнять при помощи функции WSAGetLastError. Возможные коды ошибок перечислены ниже:
Код ошибки Описание
WSANOTINITIALISED Перед использованием функции необходимо вызвать функцию WSAStanup
WSAENETDOWN Сбой в сети
WSAEADDRINUSE Указанный адрес уже используется
WSAEFAULT Значение параметра namelen меньше размера структуры sockaddr
WSAEINPROGRESS Выполняется блокирующая функция интерфейса WindowsSockets
WSAEAFNOSUPPORT Этот протокол не может работать с указанным семейством адресов
WSAEINVAL Сокет уже привязан к адресу
WSAENOBUFS Установлено слишком много соединений
WSAENOTSOCK Указанный в параметре дескриптор не является сокетом
Пример вызова функции bind показан ниже:
if(bind(srv_socket, (LPSOCKADDR)&srv_address, sizeof(srv_address)) == SOCKET_ERROR)
{
closesocket(srv_socket);
MessageBox(NULL, "bind Error", "Error", MB_OK);
return;}