Сетевое программирование на C++: от сокетов до асинхронности

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Сетевое программирование на C++: от сокетов до асинхронности

Вы когда-нибудь задумывались, как работают чаты, онлайн-игры или веб-серверы? В основе каждого из них лежит сетевое взаимодействие. И если вы хотите писать быстрые, надёжные и низкоуровневые сетевые приложения, сетевое программирование на C++ — это то, что нужно. В этой статье мы не просто пройдёмся по теории, а напишем настоящий код: от простого TCP-чата до асинхронного сервера с помощью Boost.Asio. Готовьте ваши IDE — будет интересно!

Почему C++ для сетей? Три причины

Многие выбирают Python или Java для сетевых задач, но C++ даёт три ключевых преимущества:

  • Скорость: Минимальные накладные расходы, прямой доступ к системным вызовам (syscalls).
  • Контроль: Вы управляете памятью, буферами и таймингами вручную.
  • Экосистема: Boost.Asio, libcurl, Poco — мощные библиотеки для любой задачи.

Но с великой силой приходит и великая ответственность: одна ошибка в управлении сокетом — и ваш сервер упадёт. Давайте разберёмся, как этого избежать.

Основы: сокеты и модель OSI

Прежде чем писать код, вспомним, что такое сокет. Это абстракция, которая позволяет приложению отправлять и получать данные по сети. В C++ мы работаем через sys/socket.h (Linux) или winsock2.h (Windows).

TCP vs UDP: когда что использовать?

  • TCP (SOCK_STREAM): Гарантирует доставку, порядок пакетов. Идеально для чатов, HTTP, баз данных.
  • UDP (SOCK_DGRAM): Быстрее, но без гарантий. Используется в стриминге, DNS, онлайн-играх (где потерю пакета проще пережить, чем задержку).

Пишем TCP-сервер и клиент на C++ (чистые сокеты)

Давайте создадим простейший эхо-сервер. Он принимает сообщение и отправляет его обратно. Это классический «Hello, World» в сетевом программировании на C++.

Серверная часть (echo_server.cpp)

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>

int main() {
    int server_fd, client_fd;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[1024] = {0};

    // 1. Создаём сокет
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. Привязываем к порту (опция reuse port)
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8080);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 3. Слушаем входящие соединения
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    std::cout << "Сервер запущен на порту 8080. Ожидание подключений..." << std::endl;

    // 4. Принимаем клиента
    if ((client_fd = accept(server_fd, (struct sockaddr *)&address,
                            (socklen_t*)&addrlen)) < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    // 5. Читаем и отправляем обратно (эхо)
    int valread = read(client_fd, buffer, 1024);
    std::cout << "Получено: " << buffer << std::endl;
    send(client_fd, buffer, strlen(buffer), 0);
    std::cout << "Эхо-ответ отправлен" << std::endl;

    close(client_fd);
    close(server_fd);
    return 0;
}

Клиентская часть (echo_client.cpp)

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[1024] = {0};

    // 1. Создаём сокет
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        std::cerr << "Socket creation error" << std::endl;
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);

    // 2. Преобразуем IP-адрес (localhost)
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        std::cerr << "Invalid address/ Address not supported" << std::endl;
        return -1;
    }

    // 3. Подключаемся к серверу
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "Connection Failed" << std::endl;
        return -1;
    }

    // 4. Отправляем сообщение
    const char *hello = "Привет, сервер!";
    send(sock, hello, strlen(hello), 0);
    std::cout << "Сообщение отправлено" << std::endl;

    // 5. Читаем ответ
    int valread = read(sock, buffer, 1024);
    std::cout << "Ответ сервера: " << buffer << std::endl;

    close(sock);
    return 0;
}

Как проверить? Скомпилируйте оба файла (например, g++ echo_server.cpp -o server), запустите сервер, затем в другом терминале — клиент. Вы увидите эхо-ответ.

Асинхронное программирование с Boost.Asio

Чистые сокеты хороши для обучения, но в реальной жизни вы не будете писать многопользовательский сервер на блокирующих вызовах. Здесь на помощь приходит Boost.Asio — кроссплатформенная библиотека для асинхронного ввода-вывода. С её помощью можно обрабатывать тысячи соединений в одном потоке.

Установка Boost.Asio

Если у вас Linux:

Блоги

Рекомендаций по книгам