← Назад к вопросам

Что знаешь про сетевое программирование?

2.0 Middle🔥 151 комментариев
#Сети и протоколы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Сетевое программирование в C/C++

Сетевое программирование — критический навык для Backend разработчика. Это фундамент для создания распределённых систем, серверных приложений и inter-process communication (IPC).

1. Основы: OSI модель и TCP/IP

Для backend разработчика наиболее важны:

  • Layer 4 (Transport): TCP, UDP
  • Layer 3 (Network): IP, ICMP
  • Layer 7 (Application): HTTP, HTTPS, WebSocket, gRPC
// TCP/IP vs UDP
TCP (Transmission Control Protocol):
- Надёжная доставка
- In-order гарантии
- Handshake (SYN, SYN-ACK, ACK)
- Используется для: HTTP, HTTPS, SSH, FTP, SMTP

UDP (User Datagram Protocol):
- Без гарантий доставки
- Низкая latency
- Multicast/broadcast поддержка
- Используется для: DNS, streaming, IoT, gaming

2. Основные функции сокетов (POSIX API)

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

// === СЕРВЕР ===
int server_socket = socket(AF_INET, SOCK_STREAM, 0);  // AF_INET = IPv4, SOCK_STREAM = TCP

struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);  // Host To Network Short
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // Слушаем на всех интерфейсах

bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
listen(server_socket, SOMAXCONN);  // SOMAXCONN = максимальная очередь подключений

struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_len);

// === КЛИЕНТ ===
int client_socket = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));

// === ОТПРАВКА И ПОЛУЧЕНИЕ ===
const char* msg = "Hello, server!";
send(client_socket, msg, strlen(msg), 0);

char buffer[1024];
int bytes_received = recv(client_socket, buffer, sizeof(buffer) - 1, 0);
buffer[bytes_received] = '\0';

close(client_socket);
close(server_socket);

Ключевые функции:

  • socket(): Создание сокета
  • bind(): Привязка к адресу и порту
  • listen(): Переход в режим ожидания
  • accept(): Принятие входящего соединения
  • connect(): Установление соединения
  • send(), recv(): Отправка/получение данных
  • close(): Закрытие сокета

3. Блокирующие vs асинхронные операции

Блокирующие (по умолчанию):

int client_socket = accept(server_socket, ...);  // Ждёт входящего соединения
int bytes = recv(client_socket, buffer, size, 0);  // Ждёт данных

Проблема: Один медленный клиент может заблокировать весь сервер.

Асинхронные (non-blocking):

int flags = fcntl(client_socket, F_GETFL, 0);
fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);

int bytes = recv(client_socket, buffer, size, 0);
if (bytes == -1 && errno == EAGAIN) {
    // Данных нет, сокет не заблокирован
}

4. Scalable I/O: select, poll, epoll

select() — POSIX, портативна:

#include <sys/select.h>

fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(server_socket, &read_fds);

struct timeval timeout = {1, 0};  // 1 секунда
int ready = select(server_socket + 1, &read_fds, NULL, NULL, &timeout);

if (FD_ISSET(server_socket, &read_fds)) {
    // server_socket готов для чтения
}

Ограничение: max FD_SETSIZE (обычно 1024)

poll() — POSIX, без ограничений:

#include <poll.h>

struct pollfd fds[1000];
fds[0].fd = server_socket;
fds[0].events = POLLIN;  // Интересуют входящие данные

int ready = poll(fds, 1, 5000);  // 5000ms timeout

for (int i = 0; i < 1; i++) {
    if (fds[i].revents & POLLIN) {
        // Данные готовы к чтению
    }
}

epoll() — Linux, самая эффективная:

#include <sys/epoll.h>

int epoll_fd = epoll_create1(0);

struct epoll_event event;
event.events = EPOLLIN;  // Интересуют входящие события
event.data.fd = server_socket;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event);

struct epoll_event events[64];
int ready = epoll_wait(epoll_fd, events, 64, 5000);

for (int i = 0; i < ready; i++) {
    if (events[i].events & EPOLLIN) {
        int client = accept(server_socket, ...);
        // Обработать входящее соединение
    }
}

Сравнение:

ФункцияМасштабируемостьПортативностьСложность
select()~1000 FDsОтличноНизкая
poll()~10k FDsХорошоСредняя
epoll()100k+ FDsLinux onlyСредняя

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

#include <boost/asio.hpp>
using boost::asio::ip::tcp;

class AsyncServer {
private:
    boost::asio::io_context io_context;
    tcp::acceptor acceptor;
    
void start_accept() {
    auto socket = std::make_shared<tcp::socket>(io_context);
    
    acceptor.async_accept(*socket,
        [this, socket](const boost::system::error_code& ec) {
            if (!ec) {
                // Соединение принято
                handle_connection(socket);
            }
            start_accept();  // Ждём следующего соединения
        });
}

public:
    AsyncServer(boost::asio::io_context& io) : acceptor(io, tcp::endpoint(tcp::v4(), 8080)) {
        start_accept();
    }
    
    void run() {
        io_context.run();  // Блокирующий вызов, обрабатывает события
    }
};

6. HTTP и REST API

Базовый HTTP запрос:

#include <iostream>
#include <string>

void send_http_request(int socket) {
    const char* request = "GET /api/users HTTP/1.1\r\n"
                         "Host: example.com\r\n"
                         "Connection: close\r\n"
                         "\r\n";
    
    send(socket, request, strlen(request), 0);
    
    char buffer[4096];
    int bytes = recv(socket, buffer, sizeof(buffer) - 1, 0);
    buffer[bytes] = '\0';
    
    std::cout << buffer << std::endl;
}

Простой HTTP сервер:

#include <string>
#include <sstream>

struct HttpRequest {
    std::string method;  // GET, POST, etc.
    std::string path;    // /api/users
    std::string version; // HTTP/1.1
};

HttpRequest parse_http_request(const std::string& raw) {
    std::istringstream iss(raw);
    HttpRequest req;
    iss >> req.method >> req.path >> req.version;
    return req;
}

void send_http_response(int socket, int status, const std::string& body) {
    std::string response = "HTTP/1.1 " + std::to_string(status) + " OK\r\n"
                         "Content-Length: " + std::to_string(body.length()) + "\r\n"
                         "Content-Type: application/json\r\n"
                         "Connection: close\r\n"
                         "\r\n" + body;
    
    send(socket, response.c_str(), response.length(), 0);
}

7. Потокобезопасность и синхронизация

#include <thread>
#include <mutex>

std::mutex socket_mutex;

void handle_client(int client_socket) {
    char buffer[1024];
    int bytes = recv(client_socket, buffer, sizeof(buffer), 0);
    
    // Критичная секция — несколько потоков не должны писать одновременно
    std::lock_guard<std::mutex> lock(socket_mutex);
    send(client_socket, "OK", 2, 0);
    close(client_socket);
}

int main() {
    std::vector<std::thread> threads;
    
    for (int i = 0; i < 10; ++i) {
        int client = accept(server_socket, ...);
        threads.emplace_back(handle_client, client);
    }
    
    for (auto& t : threads) t.join();
}

8. Частые проблемы и их решения

1. Address already in use:

int reuse = 1;
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
bind(server_socket, ...);

2. Broken pipe (при отправке закрытому сокету):

signal(SIGPIPE, SIG_IGN);  // Игнорируем SIGPIPE
// или проверяем возвращаемое значение send()

3. Timeout:

struct timeval timeout = {5, 0};  // 5 seconds
setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

4. Keep-alive:

int keep_alive = 1;
setsockopt(client_socket, SOL_SOCKET, SO_KEEPALIVE, &keep_alive, sizeof(keep_alive));

9. TLS/SSL (HTTPS)

#include <openssl/ssl.h>

SSL_CTX* ctx = SSL_CTX_new(TLS_server_method());
SSL_CTX_use_certificate_file(ctx, "cert.pem", SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(ctx, "key.pem", SSL_FILETYPE_PEM);

SSL* ssl = SSL_new(ctx);
SSL_set_fd(ssl, client_socket);
SSL_accept(ssl);  // TLS handshake

// Теперь можем читать/писать через SSL
SSL_read(ssl, buffer, size);
SSL_write(ssl, data, len);

10. Best Practices

1. Используйте асинхронные операции для масштабируемости 2. Обрабатывайте ошибки (EAGAIN, ECONNRESET, etc.) 3. Устанавливайте таймауты для предотвращения зависаний 4. Используйте SO_REUSEADDR для быстрого перезапуска сервера 5. Избегайте блокирующих операций в основном потоке 6. Используйте connection pooling для переиспользования соединений 7. Реализуйте graceful shutdown — закрываем connections корректно 8. Логируйте ошибки сокетов для отладки

Заключение

Сетевое программирование — интеграция:

  • POSIX API для базовых операций
  • Асинхронных моделей для масштабируемости
  • Протоколов (HTTP, TCP/UDP) для коммуникации
  • Безопасности (TLS/SSL) для защиты

Основной паттерн modern backend'а:

event loop (epoll/kqueue/IOCP)
    -> accept connections
    -> dispatch to handler threads
    -> parse HTTP requests
    -> process business logic
    -> send HTTP responses

Это фундамент для создания масштабируемых высокопроизводительных серверов.

Что знаешь про сетевое программирование? | PrepBro