Домівка > Перекладені > Як розробити простий UDP сервер і клієнт із використанням WinSock.

Як розробити простий UDP сервер і клієнт із використанням WinSock.

Цей проект містить прості програми-приклади UDP сервера та клієнта. Якщо ви ніколи не писали програми, що використовують UDP, це прекрасний проект для початку. Сервер виконується на локальному комп’ютері та очікує датаграму із запитом поточного серверного часу з віддаленого комп’ютера. По отриманню такої датаграми сервер повертає свій поточний час клієнту, який відображає його.

Підвалини

UDP позначає протокол датаграм користувача. Клієнт відсилає датаграму серверу, який у свою чергу обробляє дані відсилає відповідь в зворотньому напрямку. В цій статті показується як використовувати функції sendto та recvfrom.

Серверна програма

Серверна програма – це простий UDP сервер, який очікує датаграми від клієнта. Коли він отримує датаграму, що містить текст “GET TIME\r\n”, він повертає поточний серверний час клієнтові.

Ініціалізація бібліотеки Winsock

Виклику будь-якої функції сокетів має передувати ініціалізація бібліотеки Winsock. Це робиться за допомогою функції WSAStartup.

/* Ініціалізація бібліотеки Winsock */
if (WSAStartup(0x0101, &w) != 0)
{
    fprintf(stderr, "Не змогли ініціалізувати бібліотеку Winsock.\n");
    exit(0);
}

Шістнадцяткове число 0x0101 це версія бібліотеку, яку ми будемо використовувати, а змінна w це структура типу WSADATA.

Відкриття датаграмного сокета

Наступний крок це відкриття датаграмного сокету для UDP. Це робиться за допомогою функції сокетів, що повертає описувач сокета.

/* Відкриття датаграмного сокета */
sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd == INVALID_SOCKET)
{
    fprintf(stderr, "Не змогли створити сокет.\n");
    WSACleanup();
    exit(0);
}

AF_INET визначає тип адрес, що ми будемо використовувати, а SOCK_DGRAM вказує функції, що ми бажаємо хочемо використовувати UDP замість TCP/IP.

Встановлення серверних даних

Після цього необхідно встановити деякі дані про сервер у структурі sockaddr_in. Спочатку, очищаємо пам’ять структури. Тоді встановлюємо сімейство сервера, яке завжди AF_INET. Потім номер порту за допомогою функції htons. Залежно від введених параметрів командного рядку, сервер або намагатиметься отримати свою IP адресу, і це найбажаніший підхід, або, якщо це не спрацює, адреса може бути призначена вручну. Для автоматичного визначення адреси серверного комп’ютера, використовуються функція gethostname, яка повертає вказівник на структуру типу hostent. Зрештою, кожна частина адреси в вигляді xxx.xxx.xxx.xxx копіюється в структуру server.

/* Очищення структури */
memset((void *)&server, '', sizeof(struct sockaddr_in));

/* Встановлення сімейства та порту */
server.sin_family = AF_INET;
server.sin_port = htons(port_number);

/* Встановлення адреси автоматично, за бажанням */
if (argc == 2)
{
    /* Get host name of this computer */
    gethostname(host_name, sizeof(host_name));
    hp = gethostbyname(host_name);

    /* перевірка на нульовий вказівник */
    if (hp == NULL)
    {
        fprintf(stderr, "Could not get host name.\n");
        closesocket(sd);
        WSACleanup();
        exit(0);
    }

    /* Присвоєння адреси */
    server.sin_addr.S_un.S_un_b.s_b1 = hp->h_addr_list[0][0];
    server.sin_addr.S_un.S_un_b.s_b2 = hp->h_addr_list[0][1];
    server.sin_addr.S_un.S_un_b.s_b3 = hp->h_addr_list[0][2];
    server.sin_addr.S_un.S_un_b.s_b4 = hp->h_addr_list[0][3];
}
/* Інакше присвоюємо вручну */
else
{
    server.sin_addr.S_un.S_un_b.s_b1 = (unsigned char)a1;
    server.sin_addr.S_un.S_un_b.s_b2 = (unsigned char)a2;
    server.sin_addr.S_un.S_un_b.s_b3 = (unsigned char)a3;
    server.sin_addr.S_un.S_un_b.s_b4 = (unsigned char)a4;
}

Змінні a1, a2, a3 s a4 є складовими адреси сервера в вигляді xxx.xxx.xxx.xxx як набрано в командному рядку.

Прив’язка адреси до сокету

Наступний крок це зв’язування адреси сервера із сокетом створеним функцією socket. Це робиться через використання функції bind, яка повертає -1 у випадку помилки.

/* Прив’язка адреси до сокету */
if (bind(sd, (struct sockaddr *)&server, 
                     sizeof(struct sockaddr_in)) == -1)
{
    fprintf(stderr, "Could not bind name to socket.\n");
    closesocket(sd);
    WSACleanup();
    exit(0);
}
Отримання датаграми від клієнта

Тепер сервер готовий отримувати датаграми від клієнтів. Це робиться за допомогою функції recvfrom. buffer це буфер для зберігання отриманих датаграм, і BUFFER_SIZE це найбільша можлива кількість байтів, яку можна прийняти. client це структура типу sockaddr_in, що містить дані про клієнта відправника датаграми, включно з його адресою.

client_length = (int)sizeof(struct sockaddr_in);

/* Отримання байтів від клієнта */
bytes_received = recvfrom(sd, buffer, BUFFER_SIZE, 0, 
            (struct sockaddr *)&client, &client_length);
if (bytes_received < 0)
{
    fprintf(stderr, "Could not receive datagram.\n");
    closesocket(sd);
    WSACleanup();
    exit(0);
}
Відправлення відповіді

Щойно датаграма була отримана сервером, сервер порівнює інформацію в датаграм і із рядком “GET TIME\r\n”. Якщо рядки збігаються, тоді сервер повертає час. Інакше, запит скасовується як невірний. Час повертається клієнтові за допомогою функції sendto.

/* Перевірка запиту */
if (strcmp(buffer, "GET TIME\r\n") == 0)
{
    /* Отримання поточного часу */
    current_time = time(NULL);
            
    /* Відправити дані назад */
    if (sendto(sd, (char *)&current_time, 
         (int)sizeof(current_time), 0, 
         (struct sockaddr *)&client, client_length) != 
                                 (int)sizeof(current_time))
    {
        fprintf(stderr, "Помилка при відправленні датаграми.\n");
        closesocket(sd);
        WSACleanup();
        exit(0);
    }
}

Після цього, сервер переходить на наступний прохід циклу із функцією recvfrom.

Клієнтська програма

Клієнтська програма являє собою простого UDP клієнта, який відсилає запити до сервера, щоб отримати поточний час і отримує відповіді від сервера. По-перше, ініціалізуємо Winsock, по-друге, відкриваємо сокет. Далі копіюємо адресу сервера до структури server. Ця частина коду дуже схожа з серверним кодом.

Отримання адреси клієнта

В UDP клієнті, необхідно дізнатись IP адресу клієнтського комп’ютера. Це можна зробити автоматично або вручну, через командний рядок. Код майже тотожний серверному, за винятком того, що номер порту клієнта встановлюється в нуль client.sin_port = htons(0);, де client це структура типу sockaddr_in.

Прив’язування адреси клієнта до сокету

Наступний крок це прив’язування клієнтської адреси до сокету. Цей код теж майже той самий, що й у сервера.

Відправлення запити для отримання часу

Прийшов час відправити запит до сервера для отримання часу. Використовуємо для цього функцію sendto:

/* Відправлення даних для отримання часу */
server_length = sizeof(struct sockaddr_in);
if (sendto(sd, send_buffer, (int)strlen(send_buffer) + 1, 
       0, (struct sockaddr *)&server, server_length) == -1)
{
    fprintf(stderr, "Помилка передавання данних.\n");
    closesocket(sd);
    WSACleanup();
    exit(0);
}

send_buffer – це рядок, що містить текст “GET TIME\r\n” із нульовим символом у кінці.

Отримання часу

Після відправлення запиту, очікуємо на відповідь сервера. Робимо це за допомогою функції recvfrom:

/* Отримання часу */
if (recvfrom(sd, (char *)&current_time, 
               (int)sizeof(current_time), 0, 
               (struct sockaddr *)&server, 
               &server_length) < 0)
{
    fprintf(stderr, "Помилка отримання даних.\n");
    closesocket(sd);
    WSACleanup();
    exit(0);
}

current_time – це змінна типу time_t, яка зберігає час.

Закриття сокету і завершальне очищення

Програма майже завершена. Останній крок це закриття сокету і припинення використання бібліотеки Winsock (Ws2_32.dll). Сокет закривається за допомогою функції closesocket, а WSACleanup припиняє використання бібліотеки:

closesocket(sd);
WSACleanup();

Використання серверної програми

Синтаксисом виклику серверної програми є timeserv [server_address] port. port це номер порту на якому буде знаходитись сервер. Радимо використовувати номери більші за 1023, бо нижчі номери можуть використовуватись різними протоколами. Необов’язковий параметр server_address це локальна IP адреса серверного комп’ютера введена у вигляді xxx.xxx.xxx.xxx. Якщо цей параметр відсутній. Програма намагатиметься автоматично отримати адресу. Здебільшого це працює добре, але якщо ви маєте більш ніж одне мережеве з’єднання, наприклад, карту Ethernet і бездротову мережеву карту, програма може обрати невірну адресу. В такому разі, запустіть ipconfig, щоб визначити адресу та введіть її в командний рядок. Наприклад, для запуску програми із самостійним утворенням адреси на порті 5000, наберіть: timeserv 5000, а для запуску на комп’ютері з локальною адресою 192.168.1.102, наберіть timeserv 192.168.1.102 5000. Для виходу з серверної програми натисніть CTRL+C.

Використання клієнтської програми

Перед запуском клієнтської програми, переконайтесь, що серверна програма працює на серверному комп’ютері. Синтаксис клієнтської програми такий: timecli server_address port [client_address]. server_address це адреса комп’ютера на якому запущений сервер у вигляді xxx.xxx.xxx.xxx. Параметр port це номер порту на який прослуховує сервер. Необов’язковий параметр client_address схожий за своїм значенням до подібного параметра в серверній програмі. Наприклад, для зв’язування з сервером з адресою 192.168.1.102 на порту 5000, треба ввести timecli 192.168.1.102 5000.

Зауваження

Мережеві екрани та антивірусне пз

Деякі мережеві екрани не дозволять вам запустити сервер і/або клієнт на вашому комп’ютері. Це лише спроба захистити вас від програм шпигунів. Ці програми можуть помилково визначити вашу програму як шпигунську. Якщо таке сталось, ви маєте тимчасово вимкнути програми блокувальники. Однак, не забудьте увімкути їх потім.

Компонування коду

Під час компіляції переконайтесь, що ви підключили бібліотечний файл wsock32.lib. Інакше, компонувальник видасть купу помилок про невизначені функції.

Чому програма написана на C?

Можливо виникне запитання, “Чому програма написана на C замість C++?”. Відповідь проста. Призначення цієї програми показати основи програмування каркасу WinSock, а не надання стиснутого сучасного коду. Однак програма буде компілюватись і C++ компіляторами.

Запуск програми на комп’ютері без мережевого з’єднання

Цю програму можливо перевірити на комп’ютері без мережевого з’єднання через використання адреси звортонього зв’язку. Це адреса 127.0.0.1 і вона не пов’язана з будь-яким мережевим обладнанням. Для запуску сервера введіть timeserv 127.0.0.1 5000, а для запуску клієнта, timecli 127.0.0.1 5000 127.0.0.1 на тому ж комп’ютері.

Вихідна стаття

Категорії:Перекладені Позначки:, ,
  1. Коментарів ще немає.
  1. No trackbacks yet.

Залишити відповідь

Заповніть поля нижче або авторизуйтесь клікнувши по іконці

Лого WordPress.com

Ви коментуєте, використовуючи свій обліковий запис WordPress.com. Log Out / Змінити )

Twitter picture

Ви коментуєте, використовуючи свій обліковий запис Twitter. Log Out / Змінити )

Facebook photo

Ви коментуєте, використовуючи свій обліковий запис Facebook. Log Out / Змінити )

Google+ photo

Ви коментуєте, використовуючи свій обліковий запис Google+. Log Out / Змінити )

З’єднання з %s

%d блогерам подобається це: