- •57 Програмне забезпечення мереж еом. Частина 3. Відання 2. Програмування інтерфейсу «WinSocket»
- •Лабораторна робота № 1. Основні теоретичні положення (інтерфейс windows sockets)
- •1.1. Сокети, датаграми й канали зв'язку
- •1.1.1. Ініціалізація додатка й завершення його роботи
- •1.1.2. Створення й ініціалізація сокету
- •1.3. Індивідуальні завдання на роботу №1
- •Лабораторна робота № 2. Створення каналу зв'язку
- •2.1. Сторона сервера
- •2.3. Передача й прийом даних
- •2.2. Сторона клієнта
- •2.4. Додаток ServerSocket
- •2.5. Додаток ClientSocket
- •2.6. Індивідуальні завдання на роботу №2
- •Лабораторна робота № 3. Датаграммный протокол
- •3.1. Додаток ServerDSocket
- •3.2. Додаток ClientDSocket
- •3.3. Індивідуальні завдання на роботу №3
1.1.2. Створення й ініціалізація сокету
Сокет - це опис постачальника транспорту. В Win32 сокет відрізняється від опису файлу, а тому представлено окремим типом - SOCKET. Сокет створюється однієї із двох функцій:
SOCKET WSASocket (
int af,
int type,
int protocol,
LPWSAPROTOCOL_INFO lpProtocolInfo,
GROUP g,
DWORD dwFlags
)
SOCKET socket (
int af,
int type,
int protocol
);
Перший параметр - af, визначає сімейство адрес протоколу. Другий параметр - type, це тип сокету для даного протоколу. Він може приймати одне з наступних значень: SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET, SOCK_RAW і SOCK_RDM. Третій параметр - protocol, указує конкретний транспорт, якщо для даного сімейства адрес і типу сокету існує кілька записів. У табл. 1.2 перераховані значення, використовувані в полях сімейства адрес, типу сокету й протоколу для даного мережного транспорту.
Табл. 1.2. Параметри сокетів
|
Протокол |
Сімейство адрес |
Тип сокету |
Протокол |
|
|
1 |
2 |
3 |
4 |
5 |
|
Internet Protocol (IP) |
AF_INET |
TCP UDP Прості сокети |
SOCK_STREAM SOCK_DGRAM SOCK_RAW |
IPPROTO_IP IPPROTO_UDP IPPROTO_RAW 1PPROTO_ICMP |
|
IPX/SPX |
AF_NS |
MSAFD nwln-kipx [IPX] |
SOCK_DGRAM |
NSPROTO_IPX |
|
|
AF_IPX |
MSAFD nwln- kspx [SPX] MSAFD nwln- kspx [SPX] [псевдопоток] MSAFD nwln- kspx [SPXII] MSAFD nwlnk spx [SPXII] -[псевдопоток] |
SOCK_ SEQ- PACKET SOCK_STREAM SOCK_SEQ- PACKET SOCK_STREAM |
NSPROTO_SPX NSPROTO_SPX NSPROTO_SPXII NSPROTO_SPXII |
|
NetBIOS |
AF_NETBIOS |
Последова- тельні пакети |
SOCK_SEQ- PACKET |
Номер LANA |
|
|
|
Дейтаграми |
SOCK_DGRAM |
Номер LANA |
|
AppleTalk |
AF_APPLE TALK |
MSAFD Apple- Talk [ADSP] |
SOCK_RDM |
ATPROTO_ADSP |
|
|
|
MSAFD Apple- Talk [ADSP] [псевдопоток] |
SOCK_STREAM |
ATPROTO_ADSP |
|
|
|
MSAFD Apple- Talk [PAP] |
SOCK_RDM |
ATPROTO_PAP |
|
|
|
MSAFD Apple- Talk [RTMP] |
SOCK_DGRAM |
DDPPROTO_ RTMP |
|
|
|
MSAFD Apple- Talk [ZIP] |
SOCK_DGRAM |
DDPPROTO_ZIP |
|
1 |
2 |
3 |
4 |
5 |
|
ATM |
AF_ATM |
MSAFD ATM AAL5 |
SOCK_RAW |
ATMPROTO_AAL5 |
|
|
|
Native ATM (AAL5) |
SOCK_RAW |
ATMPROTO_AAL5 |
|
Infrared Sockets |
AF_IRDA |
MSAFD Irda [IrDA] |
SOCK_STREAM |
IRDA_PROTO_ SOCK_ STREAM |
Початкові три параметри для створення сокету підрозділені на три рівні. Перший і найважливіший - сімейство адрес. Він указує використовуваний у цей час протокол і обмежує застосування другого й третього параметрів. Аналогічно, вибір сімейства адрес і типу сокету обмежує вибір протоколу.
Можна передати в параметрі protocol значення 0. У цьому випадку система вибирає постачальника транспорту, виходячи з інших двох параметрів - af і type. Перераховуючи запису каталогу для протоколів, варто перевірити значення поля dwProviderFlags зі структури WSAPROTOCOL_INFO. Якщо воно дорівнює PFL_ MATCHES_PROTOCOL_ZERO - це стандартний транспорт, застосовуваний, якщо в параметрі протоколу socket або WSASocket передане значення 0.
Перелічивши всі протоколи за допомогою WSAEnumProtocols, варто передати структуру WSAPROTOCOL_INFO у функцію WSASocket як параметр lpProtocolInfo. Потім треба вказати константу FROM_PROTOCOL_INFO у всіх параметрах (af, type і protocol) - для них будуть використатися значення з переданої структури WSAPROTOCOL_INFO.
У двох останніх прапорах з WSASocket параметр групи завжди дорівнює 0, тому що жодна з версій Winsock не підтримує групи сокетів, у параметрі dwFlags указують один або кілька наступних прапорів:
WSA_FLAG_OVERLAPPED;
WSA_FLAG_MULTIPOINT_C_ROOT;
WSA_FLAG_MULTIPOINT_C_LEAF;
WSA_FLAG_MULTIPOINT_D_ROOT;
WSA_FLAG_MULTIPOINT_D_LEAF.
Перший прапор - WSA_FLAG_OVERLAPPED, указує, що даний сокет допускає перекритий ввід-вивід. Якщо створюється сокет функцією socket, прапор WSA_FLAG_OVERLAPPED заданий за замовчуванням. Рекомендується завжди задавати цей прапор при використанні WSASocket. Останні чотири прапори ставляться до сокету багатоадресного віщання.
При створенні сокету функцією WSASocket можна передати виклику структуру WSAPROTOCOL_INFO, щоб визначити тип сокету, якому необхідно створити. Можна створювати типи сокетів, для яких немає запису в каталозі постачальників транспорту.
1.1.2.2. Видалення сокету
Для звільнення ресурсів додаток повинне закривати сокети, які йому більше не потрібні, викликаючи функцію closesocket:
int closesocket(SOCKET sock);
Нижче перераховані коди помилок для цієї функції:
Код помилки Опис
WSANOTINIT1ALISED Перед використанням функції closesocket необхідно викликати функцію WSAStartup
WSAENETDOWN Збій у мережі
WSAENOTSOCK Зазначений у параметрі дескриптор не є сокетом
WSAEINPROGRESS Виконується функція, що блокує, інтерфейсу Windows Sockets
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, а саме молодші байти даних зберігаються по старших адресах пам'яті (big endian). Нагадаємо, що архітектура процесорів Intel має на увазі зберігання молодших байтів даних по молодших адресах (little endian).
Універсальний мережний формат даних зручний при організації глобальних мереж, тому що у вузлах такої мережі можуть використатися комп'ютери з різною архітектурою.
Для виконання перетворень зі звичайного формату в мережний і назад в інтерфейсі Windows Sockets передбачений спеціальний набір функцій. Зокрема, для заповнення поля sin_port потрібно використати функцію htons або WSAHtons, що виконують перетворення 16-розрядних даних з формату Intel у мережний формат.
u_short htons(u_short hostshort);
int WSAHtons(
SOCKET s,
u_short hostshort,
u_short FAR * lpnetshort
);
Нижче показано, як инициализируется поле 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 Виконується функція, що блокує, інтерфейсу Windows Sockets
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;
}
