ТОП 10:

Програмний інтерфейс сокетів Берклі



У розділі 6 наводилися загальні принципи взаємодії між клієнтом і сервером у разі використання сокетів. У цьому розділі опишемо особливості використання інтерфейсу сокетів Берклі (Berkeley Sockets) [38, 47, 75, 106].

Програмний інтерфейс сокетів Берклі - це засіб зв'язку між прикладним рів­нем мережної архітектури TCP/IP і транспортним рівнем. Причина такого розта­шування полягає в тому, що це засіб зв'язку між кодом застосування (який ви­конують на прикладному рівні) і реалізацією стека протоколу ОС (найвищим рівнем якої є транспортний). Фактично цей інтерфейс є набором системних ви­кликів ОС.

Назва цього АРІ пов'язана з тим, що він уперше був реалізований у 80-х роках XX століття у версії UNIX, розробленій Каліфорнійським університетом у Берк­лі (BSD UNIX).

 

Особливості роботи з адресами

Цей розділ буде присвячено особливостям відображення даних про адреси і пор­ти, необхідних для використання сокетів.

 

Відображення адрес сокетів

Більшість системних викликів роботи із сокетами приймають як параметри по­кажчики на спеціальні структури даних, що відображають адреси сокетів. У разі використання TCP/IP зі звичайним ІР-протоколом (IPv4) адресу задають струк­турою sockaddrin. Вона визначена у заголовному файлі <netinet/in.h> і містить, зокрема, такі поля:

♦ sin_family - для TCP/IP має дорівнювати константі AF_INET;

Sin_port - цілочисловий номер порту для TCP або UDP;

♦ sin_addr - відображення IP-адреси (структура і naddr з одним полем saddr).

Реалізуючі системні виклики роботи із сокетами, необхідно враховувати мож­ливість використання різних протоколів, а отже, передавання в них різних струк­тур, що відображають адреси. Під час розробки АРІ сокетів Берклі було ухвалено рішення використати як універсальний тип відображення адрес покажчик на спе­ціальну структуру sockaddr, визначену у <sys/socket.h>.

Наприклад, прототип системного виклику bindO, що пов'язує сокет з адре­сою, має такий вигляд:

 

int bindOnt. struct sockaddr *. socklent):

На практиці адреса задається так: визначають структуру типу sockaddr і п і під час виклику покажчик на неї перетворюють до sockaddr *:

 

struct sockaddr_in my_addr:

II ... заповнення структури my_addr. див. далі

bind(sockfd. (struct sockaddr *) &my_addr, sizeof(my_addr));

Безпосередньо структуру sockaddr у застосуваннях не використовують. Якщо виклик повернув покажчик на неї, перед використанням у застосуванні його потрібно привести до покажчика на структуру для конкретного протоколу (на­приклад, на sockaddr_in).

 

Порядок байтів

Історично так склалося, що відображення у пам'яті даних, для яких виділяють більш як один байт (наприклад, двобайтового цілого, що відповідає у С типу short), відрізняється для різних комп'ютерних архітектур. У деяких із них старший байт розташовують у пам'яті перед молодшим (такий порядок називають зворотним -big-endian byte order), а в інших - молодший перед старшим (це прямий поря­док - little-endian byte order). Наприклад, архітектура ІА-32 використовує пря­мий порядок байтів, а комп'ютери Sun Sparestation та Power PC - зворотний.

Проблема полягає в тому, що деякі елементи пакетів (керуючі дані), якими обмінюються комп'ютери для організації передавання інформації, займають у па­м'яті більш як один байт. Так, відображення порту займає 2 байти, а IP-адреси -4 байти. Якщо такий блок інформації буде передано комп'ютеру з іншим поряд­ком байтів, він не зможе коректно його інтерпретувати.

Для вирішення цієї проблеми реалізації стека протоколів на клієнті та на сер­вері потрібно під час передавання даних мережею узгоджено використовувати єдиний порядок байтів. Такий порядок, який називають мережним порядком байтів (network byte order), визначає мережний протокол. Фактично, це зворот­ний порядок. Відповідно, порядок байтів, прийнятий для локального комп'ютера, називають порядком байтів хоста (host byte order). Він може бути як прямим, так і зворотним.

Програміст має перетворювати керуючі дані в мережний порядок байтів пе­ред передаванням інформації із мережі та робити зворотне перетворення після її отримання. Це зокрема стосується IP-адрес і номерів портів. Для реалізації пере­творення використовують функції htonK) і htonsO (перетворення до мережного порядку), ntohl () і ntohs() (перетворення до порядку хоста):

 

#include <netinet/in.h>

uintl6_t htons(uintl6_t hostval): // htonlO - для 32-бітних даних

uintl6_t ntohs(uintl6_t netval): // ntohlO - для 32-бітних даних

 

Далі наведемо приклади використання цих функцій.

 

Робота з ІР-адресами

У протоколі IPv4 IP-адреса є цілим значенням розміром 4 байти. Для того щоб перетворити крапково-десяткове подання такої адреси (n. n. п. п) у ціле число, слід використати функцію inet_aton():

 

#include <arpa/inet.h>

int inet_aton(const char *c_ip. struct in_addr *s_ip);

де: c_ip - рядкове відображення IP-адреси: "192.168.10.28";

s_ip - структура inaddr, що міститиме цілочислове відображення адреси, роз­ташоване в пам'яті відповідно до мережного порядку байтів (поле S addr); цю структуру заповнюють усередині виклику.

У разі успішного перетворення ця функція повертає ненульове значення, у разі помилки — нуль.

 

Розглянемо приклад заповнення структури, що задає адресу сокета:

 

struct sockaddrin my_addr = { 0}: // структура має бути обнулена

my_addr.sin_family = AFINET:

my_addr.sin_port = htons(7500);// задання порту

inet_aton("192.168.10.28".&(my_addr.sin_addr)): // задання адреси

 

Для обнулення структур часто використовують функцію bzero():

 

include <strings.h>

bzero(&my_addr. sizeof(my_addr)):

// заповнення my_addr

 

Зворотне перетворення IP-адреси (із цілого числа у крапково-десяткове по­дання) здійснюють функцією ine_tntoра():

 

#include <arpa/inet.h>

char *inet_ntoa(struct in_addr sip):

 

Ця функція розміщає у статичному буфері рядок із крапково-десятковим по­данням IP-адреси, заданої структурою s_ip, і повертає покажчик на цей буфер:

 

printfС'ІР-адреса: *s\n". inet_ntoa(my_addr.s_addr));

Зазначимо, що наступні спроби виклику функції стиратимуть статичний бу­фер, тому для подальшого використання його вміст потрібно копіювати в окре­мий буфер пам'яті.

 

Створення сокетів

Перший етап, який необхідно виконати на клієнті та сервері, - створити деск­риптор сокета за допомогою системного виклику socket ():

 

#include <sys/socket.h>

int socket(int family, int type, int protocol):

 

де: family - комунікаційний домен (AFINET - домен Інтернету на основі IPv4, AFUNIX - домен UNIX);

 

type - тип сокета (S0CK_STREAM - потоковий сокет, S0CKDGRAM - дейтаграмний сокет, S0CKRAW - сокет прямого доступу (raw socket), призначений для роботи із протоколом мережного рівня, наприклад IP);

 

protocol - тип протоколу (звичайно 0; це означає, що протокол вибирають ав­томатично, виходячи з домену і типу сокета; наприклад, потокові сокети доме­ну Інтернет використовують TCP, а дейтаграмні - UDP).

 

Ось приклад використання socket ():

int sockfd = socket(AF_INET. SOCKJTREAM.0);

 

Виклик socket О повертає цілочисловий дескриптор сокета або -1, якщо ста­лася помилка (при цьому, як і для інших викликів, змінна errno міститиме код по­милки). Цей сокет можна використовувати для різних цілей: організації очіку­вання з'єднань сервером, задання з'єднання клієнтом, отримання інформації про мережну конфігурацію хоста (в останньому випадку він може бути навіть не по­в'язаний з адресою).

Для системних викликів інтерфейсу сокетів Берклі далі за замовчуванням пе­редбачатиметься використання заголовного файла <sys/socket.h>.

 







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

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.235.77.252 (0.008 с.)