Разработка сайта для Вашего бизнеса. Веб дизайн. Дизайн логотипа, фирменного стиля, рекламная фотография . Комплексный рекламный креатив.

Ralex. We do the work.
На рынке с 1999го года. Средняя ценовая категория. Ориентация на эффективность решений.
Ознакомтесь с нашим портфолио
Узнайте больше о услугах
Свяжитесь с нами:
E-mail: [email protected]
Tel: (044) 587 - 84 - 78
Custom web design & дизайн и разработка сайта "под ключ"
Креативный, эффективный дизайн. Система управления сайтом (СУС).
Custom flexible разработка систем электронной коммерции
Система e-commerce разрабатывается под индивидуальные потребности. Гибкая функциональность.
Search Engine Optimzation & оптимизация под поисковые системы (SEO)
Постоянная оптимизация и мониторинг сайта в поисковых системах. Достигаем результата быстро и эффективно
Custom logo design & дизайн логотипа и фирменного стиля
Многолетний опыт. Огромное портфолио. Уникальное предложение и цена.
профессиональная рекламная фотография
креативно, смело, качественно
Custom logo design & рекламный креатив. дизайн рекламы
Многолетний опыт. Огромное портфолио. Уникальное предложение и цена.

Веб-сервер на C ++ і сокетах

  1. Введення в HTTP
  2. Що буде робити сервер?
  3. Про сокетах
  4. створення сокета
  5. Прив'язка сокета до адресою (bind)
  6. Підготовка сокета до прийняття вхідних з'єднань (listen)
  7. Очікування вхідного з'єднання (accept)
  8. Отримання запиту і відправка відповіді
  9. Послідовна обробка запитів

Наша взаимовыгодная связь https://banwar.org/

Створимо HTTP-сервер, який обробляє запити браузера і повертає відповідь у вигляді HTML-сторінки.

Введення в HTTP

Для початку розберемося, що з себе представляє HTTP. Це текстовий протокол для обміну даними між браузером і веб-сервером.

Приклад HTTP-запиту:

GET /page.html HTTP / 1.1 Host: site.com

Перший рядок передає метод запиту, ідентифікатор ресурсу (URI) і версію HTTP-протоколу. Потім перераховуються заголовки запиту, в яких браузер передає ім'я хоста, підтримувані кодування, cookie та інші службові параметри. Після кожного заголовка ставиться символ розриву рядків \ r \ n.

У деяких запитів є тіло. Коли відправляється форма методом POST, в тілі запиту передаються значення полів цієї форми.

POST / submit HTTP / 1.1 Host site.com Content-Type: application / x-www-form-urlencoded name = Sergey & last_name = Ivanov & birthday = 1990-10-05

Тіло запиту відділяється від заголовків одним порожнім рядком. Тема «Content-Type» говорить серверу, в якому форматі закодовано тіло запиту. За замовчуванням, в HTML-формі дані кодуються методом «application / x-www-form-urlencoded».

Іноді необхідно передати дані в іншому форматі. Наприклад, при завантаженні файлів на сервер, бінарні дані кодуються методом «multipart / form-data».

Сервер обробляє запит клієнта і повертає відповідь.

Приклад відповіді сервера:

HTTP / 1.1 200 OK Host: site.com Content-Type: text / html; charset = UTF-8 Connection: close Content-Length: 21 <h1> Test page ... </ h1>

У першому рядку відповіді передається версія протоколу і статус відповіді. Для успішних запитів зазвичай використовується статус «200 OK». Якщо ресурс не знайдений на сервері, повертається "404 Not Found».

Тіло відповіді так само, як і у запиту, відділяється від заголовків одним порожнім рядком.

Повна специфікації протоколу HTTP описується в стандарті rfc-2068. Зі зрозумілих причин, ми не будемо реалізовувати всі можливості протоколу в рамках цього матеріалу. Досить реалізувати підтримку роботи з заголовками запиту і відповіді, отримання методу запиту, версії протоколу і URL-адреси.

Що буде робити сервер?

Сервер буде приймати запити клієнтів, парсити заголовки і тіло запиту, і повертати тестову HTML-сторінку, на якій відображені дані запиту клієнта (запитаний URL, метод запиту, cookie та інші заголовки).

Про сокетах

Для роботи з мережею на низькому рівні традиційно використовують сокети. Сокет - це абстракція, яка дозволяє працювати з мережевими ресурсами, як з файлами. Ми можемо писати і читати дані з сокета майже так само, як зі звичайного файлу.

У цьому матеріалі ми будемо працювати з віндового реалізацією сокетів, яка знаходиться в заголовки <WinSock2.h>. В Unix-подібних ОС принцип роботи з сокетами такий же, тільки відрізняється API. Ви можете докладніше почитати про сокетах Берклі , Які використовуються в GNU / Linux.

створення сокета

Створимо сокет за допомогою функції socket, яка знаходиться в заголовки <WinSock2.h>. Для роботи з IP-адресами нам знадобиться заголовки <WS2tcpip.h>.

#include <iostream> #include <sstream> #include <string> // Для коректної роботи freeaddrinfo в MinGW // Детальніше: http://stackoverflow.com/a/20306451 #define _WIN32_WINNT 0x501 #include <WinSock2.h> # include <WS2tcpip.h> // Необхідно, щоб лінковка відбувалася з DLL-бібліотекою // Для роботи з сокетами #pragma comment (lib, "Ws2_32.lib") using std :: cerr; int main () {// службова структура для зберігання інформації // про реалізацію Windows Sockets WSADATA wsaData; // старт використання бібліотеки сокетів процесом // (подгружается Ws2_32.dll) int result = WSAStartup (MAKEWORD (2, 2), & wsaData); // Якщо сталася помилка підвантаження бібліотеки if (result! = 0) {cerr << "WSAStartup failed:" << result << "\ n"; return result; } Struct addrinfo * addr = NULL; // структура, що зберігає інформацію // про IP-адресу слущающего сокета // Шаблон для ініціалізації структури адреси struct addrinfo hints; ZeroMemory (& hints, sizeof (hints)); // AF_INET визначає, що використовується мережу для роботи з сокетом hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; // Задаємо потоковий тип сокета hints.ai_protocol = IPPROTO_TCP; // Використовуємо протокол TCP // Сокет бінді на адресу, щоб приймати вхідні з'єднання hints.ai_flags = AI_PASSIVE; // ініціалізувавши структуру, що зберігає адресу сокета - addr. // HTTP-сервер буде висіти на 8000-м порту локалхоста result = getaddrinfo ( "127.0.0.1", "8000", & hints, & addr); // Якщо ініціалізація структури адреси завершилася з помилкою, // виведемо повідомленням про це і завершимо виконання програми if (result! = 0) {cerr << "getaddrinfo failed:" << result << "\ n"; WSACleanup (); // вивантаження бібліотеки Ws2_32.dll return 1; } // Створення сокета int listen_socket = socket (addr-> ai_family, addr-> ai_socktype, addr-> ai_protocol); // Якщо створення сокета завершилося з помилкою, виводимо повідомлення, // звільняємо пам'ять, виділену під структуру addr, // вивантажуємо dll-бібліотеку і закриваємо програму if (listen_socket == INVALID_SOCKET) {cerr << "Error at socket:" << WSAGetLastError () << "\ n"; freeaddrinfo (addr); WSACleanup (); return 1; } // ...

Ми підготували всі дані, які необхідно для створення сокета і створили сам сокет. Функція socket повертає цілочисельне значення файлового дескриптора, який виділений операційною системою під сокет.

Прив'язка сокета до адресою (bind)

Наступним кроком, нам необхідно прив'язати IP-адреса до сокету, щоб він міг приймати вхідні з'єднання. Для прив'язки конкретного адреси до сокету використовується фукнція bind. Вона приймає цілочисельний ідентифікатор файлового дескриптора сокета, адреса (поле ai_addr зі структури addrinfo) і розмір адреси в байтах (використовується для підтримки IPv6).

// Прив'язуємо сокет до IP-адресою result = bind (listen_socket, addr-> ai_addr, (int) addr-> ai_addrlen); // Якщо прив'язати адресу до сокету не вдалося, то виводимо повідомлення // про помилку, звільняємо пам'ять, виділену під структуру addr. // і закриваємо відкритий сокет. // Вивантажуємо DLL-бібліотеку з пам'яті і закриваємо програму. if (result == SOCKET_ERROR) {cerr << "bind failed with error:" << WSAGetLastError () << "\ n"; freeaddrinfo (addr); closesocket (listen_socket); WSACleanup (); return 1; }

Підготовка сокета до прийняття вхідних з'єднань (listen)

Підготуємо сокет до прийняття вхідних з'єднань від клієнтів. Це робиться за допомогою функції listen. Вона приймає дескриптор слухача сокета і максимальну кількість одночасних з'єднань.

У разі помилки, функція listen вирощує значення константи SOCKET_ERROR. При успішному виконанні вона поверне 0.

// ініціалізувавши слухає сокет if (listen (listen_socket, SOMAXCONN) == SOCKET_ERROR) {cerr << "listen failed with error:" << WSAGetLastError () << "\ n"; closesocket (listen_socket); WSACleanup (); return 1; }

У константі SOMAXCONN зберігається максимально можливе число одночасних TCP-з'єднань. Це обмеження працює на рівні ядра ОС.

Очікування вхідного з'єднання (accept)

Функція accept очікує запит на установку TCP-з'єднання від віддаленого хоста. Як аргумент їй передається дескриптор слухача сокета.

При успішній установці TCP-з'єднання, для нього створюється новий сокет. Функція accept повертає дескриптор цього сокета. Якщо сталася помилка з'єднання, то повертається значення INVALID_SOCKET.

// Приймаємо вхідні з'єднання int client_socket = accept (listen_socket, NULL, NULL); if (client_socket == INVALID_SOCKET) {cerr << "accept failed:" << WSAGetLastError () << "\ n"; closesocket (listen_socket); WSACleanup (); return 1; }

Отримання запиту і відправка відповіді

Після установки з'єднання з сервером, браузер відправляє HTTP-запит. Ми отримуємо вміст запиту через функцію recv. Вона приймає дескриптор TCP-з'єднання (в нашому випадку це client_socket), покажчик на буфер для збереження отриманих даних, розмір буфера в байтах і додаткові прапори (які зараз нас не цікавлять).

При успішному виконанні функція recv поверне розмір отриманих даних. У разі помилки повертається значення SOCKET_ERROR. Якщо з'єднання було закрито клієнтом, то повертається 0.

Ми створимо буфер розміром 1024 байт для збереження HTTP-запиту.

const int max_client_buffer_size = 1024; char buf [max_client_buffer_size]; result = recv (client_socket, buf, max_client_buffer_size, 0); std :: stringstream response; // сюди буде записуватися відповідь клієнту std :: stringstream response_body; // тіло відповіді if (result == SOCKET_ERROR) {// помилка отримання даних cerr << "recv failed:" << result << "\ n"; closesocket (client_socket); } Else if (result == 0) {// з'єднання закрито клієнтом cerr << "connection closed ... \ n"; } Else if (result> 0) {// Ми знаємо фактичний розмір отриманих даних, тому ставимо мітку кінця рядка // У буфері запиту. buf [result] = '\ 0'; // Дані успішно отримані // формуємо тіло відповіді (HTML) response_body << "<title> Test C ++ HTTP Server </ title> \ n" << "<h1> Test page </ h1> \ n" << "< p> This is body of the test page ... </ p> \ n "<<" <h2> Request headers </ h2> \ n "<<" <pre> "<< buf <<" </ pre > \ n "<<" <em> <small> Test C ++ Http Server </ small> </ em> \ n "; // Формуємо весь відповідь разом з заголовками response << "HTTP / 1.1 200 OK \ r \ n" << "Version: HTTP / 1.1 \ r \ n" << "Content-Type: text / html; charset = utf- 8 \ r \ n "<<" Content-Length: "<< response_body.str (). length () <<" \ r \ n \ r \ n "<< response_body.str (); // Відправляємо відповідь клієнту за допомогою функції send result = send (client_socket, response.str (). C_str (), response.str (). Length (), 0); if (result == SOCKET_ERROR) {// сталася помилка при отправле даних cerr << "send failed:" << WSAGetLastError () << "\ n"; } // Закриваємо з'єднання до клієнтом closesocket (client_socket); }

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

У разі помилки, функція повертає значення SOCKET_ERROR. У разі успіху - кількість переданих байт.

Спробуємо скомпілювати програму, не забувши попередньо завершити функцію main.

// Прибираємо за собою closesocket (listen_socket); freeaddrinfo (addr); WSACleanup (); return 0; }

Весь вихідний код прикладу.

Якщо скомпілювати і запустити програму, то вікно консолі «підвисне» в очікуванні запиту на встановлення TCP-з'єднання. Відкрийте в браузері адресу http://127.0.0.1:8000/ . Сервер поверне відповідь, як на малюнку нижче і завершить роботу.

Послідовна обробка запитів

Щоб сервер не завершував роботу після обробки першого запиту, а продовжував обробляти нові сполуки, потрібно зробити цикл ту частину коду, яка приймає запит на установку з'єднання і повертає відповідь.

const int max_client_buffer_size = 1024; char buf [max_client_buffer_size]; int client_socket = INVALID_SOCKET; for (;;) {// Приймаємо вхідні з'єднання client_socket = accept (listen_socket, NULL, NULL); if (client_socket == INVALID_SOCKET) {cerr << "accept failed:" << WSAGetLastError () << "\ n"; closesocket (listen_socket); WSACleanup (); return 1; } Result = recv (client_socket, buf, max_client_buffer_size, 0); std :: stringstream response; // сюди буде записуватися відповідь клієнту std :: stringstream response_body; // тіло відповіді if (result == SOCKET_ERROR) {// помилка отримання даних cerr << "recv failed:" << result << "\ n"; closesocket (client_socket); } Else if (result == 0) {// з'єднання закрито клієнтом cerr << "connection closed ... \ n"; } Else if (result> 0) {// Ми знаємо розмір отриманих даних, тому ставимо мітку кінця рядка // У буфері запиту. buf [result] = '\ 0'; // Дані успішно отримані // формуємо тіло відповіді (HTML) response_body << "<title> Test C ++ HTTP Server </ title> \ n" << "<h1> Test page </ h1> \ n" << "< p> This is body of the test page ... </ p> \ n "<<" <h2> Request headers </ h2> \ n "<<" <pre> "<< buf <<" </ pre > \ n "<<" <em> <small> Test C ++ Http Server </ small> </ em> \ n "; // Формуємо весь відповідь разом з заголовками response << "HTTP / 1.1 200 OK \ r \ n" << "Version: HTTP / 1.1 \ r \ n" << "Content-Type: text / html; charset = utf- 8 \ r \ n "<<" Content-Length: "<< response_body.str (). length () <<" \ r \ n \ r \ n "<< response_body.str (); // Відправляємо відповідь клієнту за допомогою функції send result = send (client_socket, response.str (). C_str (), response.str (). Length (), 0); if (result == SOCKET_ERROR) {// сталася помилка при отправле даних cerr << "send failed:" << WSAGetLastError () << "\ n"; } // Закриваємо з'єднання до клієнтом closesocket (client_socket); }}

Коли сервер закінчить обробку запиту одного клієнта, він закриє з'єднання з ним і буде очікувати нового запиту.

Вихідний код остаточної версії сервера.

У другій частині цієї статті ми напишемо парсер HTTP-заголовків і створимо нормальний API для управління HTTP-запитами і відповідями.

Примітка: якщо ви використовуєте MinGW в Windows, то бібліотеку Ws2_32.lib потрібно вручну прописати в настройках лінковщік.

Що буде робити сервер?
Категории
  • Биология
  • Математика
  • Краеведению
  • Лечебная
  • Наука
  • Физике
  • Природоведение
  • Информатика
  • Новости

  • Новости
    https://banwar.org/
    Наша взаимовыгодная связь https://banwar.org/. Запустив новый сайт, "Пари Матч" обещает своим клиентам незабываемый опыт и возможность выиграть крупные суммы.


    Наши клиенты
    Клиенты

    Быстрая связь

    Тел.: (044) 587-84-78
    E-mail: [email protected]

    Имя:
    E-mail:
    Телефон:
    Вопрос\Комментарий: