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

Для чего нужен UDP?

1.0 Junior🔥 171 комментариев
#Сети и протоколы

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

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

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

Для чего нужен UDP?

UDP (User Datagram Protocol) часто недооценивают, но это мощный инструмент для специфических задач. Давайте разберемся, когда и почему его используют.

Краткий ответ

UDP нужен когда:

  • Скорость важнее надежности
  • Потеря нескольких пакетов не критична
  • Нужна низкая латентность
  • Требуется broadcast/multicast

Вспомни: TCP гарантирует доставку, UDP - нет. Это и есть ключевое различие.

1. TCP vs UDP - фундаментальные различия

TCP (Transmission Control Protocol):

// Надежный, упорядоченный, медленный
int sockfd = socket(AF_INET, SOCK_STREAM, 0);  // TCP

// Установка соединения (handshake)
connect(sockfd, &server_addr, sizeof(server_addr));

// Отправка (гарантирует доставку)
send(sockfd, "data", 4, 0);

// Получение (в порядке отправки)
recv(sockfd, buffer, 100, 0);

close(sockfd);

// Затраты: 3-way handshake + acknowledgments + retransmissions

UDP (User Datagram Protocol):

// Быстрый, неупорядоченный, ненадежный
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);  // UDP

// Нет соединения! Просто отправляем
struct sockaddr_in dest;
dest.sin_family = AF_INET;
dest.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &dest.sin_addr);

sendto(sockfd, "data", 4, 0, (struct sockaddr*)&dest, sizeof(dest));

// Получение (может прийти в другом порядке или не прийти)
struct sockaddr_in src;
socklen_t src_len = sizeof(src);
recvfrom(sockfd, buffer, 100, 0, (struct sockaddr*)&src, &src_len);

close(sockfd);

// Затраты: минимальные, только одна отправка пакета

Таблица сравнения:

ХарактеристикаTCPUDP
НадежностьГарантирует доставкуМожет потерять пакеты
УпорядоченностьСохраняет порядокМожет перепутать порядок
ЗадержкаВысокая (связь)Низкая (датаграмма)
Broadcast/MulticastНетДа
Header size20 байт8 байт
СкоростьМедленнее (проверки)Быстрее (без проверок)
СостояниеStateful (соединение)Stateless (без соединения)
ПримерыHTTP, Email, FTPDNS, VoIP, Игры, Streaming

2. Когда использовать UDP

Потоковое видео (YouTube, Netflix):

// Пример: отправка видеопакета
struct VideoPacket {
    uint32_t frame_id;
    uint32_t timestamp;
    uint8_t data[1024];
};

VideoPacket pkt;
pkt.frame_id = current_frame;
pkt.timestamp = get_time_ms();

// Отправляем видеопакет по UDP
sendto(sock, &pkt, sizeof(pkt), 0, (struct sockaddr*)&client, addr_len);

// Если пакет потеряется:
// - Пользователь видит небольшой артефакт (пиксельный квадрат)
// - Следующий пакет уже новее, поэтому старый можно игнорировать
// - Переотправка старого пакета замедлит воспроизведение!

VoIP (Skype, Telegram):

// Аудиопакеты по UDP
struct AudioPacket {
    uint32_t sequence;
    uint32_t timestamp;
    uint16_t audio_data[160];  // 20ms аудио
};

// Быстрая отправка
sendto(socket, &audio, sizeof(audio), 0, dest, dest_len);

// Если пакет потеряется:
// - Слышна небольшая пауза/щелчок
// - Переотправка добавит задержку (неприемлемо для голоса!)
// - Поэтому UDP лучше, чем надежный TCP с буферизацией

Онлайн игры (Dota 2, CS:GO):

// Пример: отправка позиции игрока 60 раз в секунду
struct PlayerUpdate {
    float x, y, z;           // Позиция
    float vx, vy, vz;        // Скорость
    uint32_t timestamp;      // Когда это был снято
};

// Отправляем 60 пакетов в секунду
for (int i = 0; i < 60; i++) {
    PlayerUpdate pos;
    pos.x = player.x;
    pos.y = player.y;
    pos.z = player.z;
    pos.timestamp = get_time_ms();
    
    // UDP позволяет отправлять очень часто
    sendto(sock, &pos, sizeof(pos), 0, enemy_addr, addr_len);
    usleep(1000000 / 60);  // ~16ms
}

// Почему UDP:
// - Низкая латентность (мс)
// - Если пакет потеряется, следующий обновит позицию
// - TCP буферизация добавила бы десятки мс задержки

DNS запросы:

// Пример: UDP DNS query
struct DNSHeader {
    uint16_t id;
    uint16_t flags;
    uint16_t qdcount;  // вопросов
    uint16_t ancount;  // ответов
    // ...
};

// Простой одноразовый запрос - идеально для UDP
sendto(sock, dns_query, query_len, 0, nameserver, addr_len);
recvfrom(sock, buffer, 512, 0, NULL, NULL);

// TCP DNS был бы оверхилл: нужно соединение, handshake, etc.

3. Низкая латентность - главный плюс UDP

TCP с буферизацией:

Клиент отправляет       0ms
    ↓
ОС буферизует          +1ms
    ↓
Сервер ждет ACK        +1ms
    ↓
Проверка целостности   +0.1ms
    ↓
Передача приложению    +0.1ms
________________
Итого:                 ~2-3ms (даже в локальной сети!)

UDP без буферизации:

Клиент отправляет       0ms
    ↓
Датаграмма в сеть      +0.1ms
    ↓
Сервер получает        +0.1ms
    ↓
Передача приложению    +0.1ms
________________
Итого:                 ~0.3ms (в 10x быстрее!)

4. Broadcast и Multicast - только UDP

TCP - только unicast (один на один):

// TCP может отправлять только конкретному адресу
// Нет встроенной поддержки broadcast/multicast

UDP - broadcast (всем в сети):

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

// Разрешаем broadcast
int broadcast_flag = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, 
           &broadcast_flag, sizeof(broadcast_flag));

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5000);
addr.sin_addr.s_addr = inet_addr("255.255.255.255");  // BROADCAST!

// Отправляем всем в сети
sendto(sockfd, "Discovery packet", 16, 0, 
       (struct sockaddr*)&addr, sizeof(addr));

// Используется для:
// - Service discovery (Bonjour, mDNS)
// - Network announcements
// - Device discovery

UDP - multicast (группе):

// Multicast для подписки на группу
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.1");  // Multicast группа
mreq.imr_interface.s_addr = htonl(INADDR_ANY);

// Подписываемся на группу
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, 
           &mreq, sizeof(mreq));

// Теперь получаем все пакеты, отправленные на эту группу
recvfrom(sockfd, buffer, 1024, 0, NULL, NULL);

// Используется для:
// - Market data feeds (финансовые торги)
// - IPTV потоки
// - Sensor networks

5. Практические примеры - реальный код

Простой UDP echo сервер:

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    server_addr.sin_addr.s_addr = INADDR_ANY;
    
    bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    
    char buffer[1024];
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    
    while (true) {
        // Блокирует, пока не придет датаграмма
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer), 0,
                            (struct sockaddr*)&client_addr, &client_len);
        
        printf("Received %ld bytes\n", n);
        
        // Отправляем обратно
        sendto(sockfd, buffer, n, 0,
               (struct sockaddr*)&client_addr, client_len);
    }
    
    close(sockfd);
    return 0;
}

6. Когда НЕ использовать UDP

// ❌ Не используй UDP для:

// 1. Файловых передач (потеря данных критична)
// 2. Банковских операций (каждый байт важен)
// 3. Email (сообщения должны прийти полностью)
// 4. Веб-браузера (HTTP нужен TCP)
// 5. Долгоживущих соединений без loss tolerance

7. Надежность в UDP - проблема и решение

Проблема: некоторые пакеты могут потеряться

// Попытка 1: просто отправляем
sendto(sock, pkt, size, 0, dest, dest_len);
// 1% потери пакетов → ошибка!

Решение 1: повторная отправка (application level)

// Отправляем с номером последовательности
struct Packet {
    uint32_t seq_num;
    uint32_t timestamp;
    uint8_t data[512];
};

// Отправляем несколько раз
for (int attempt = 0; attempt < 3; attempt++) {
    sendto(sock, &pkt, sizeof(pkt), 0, dest, dest_len);
    usleep(100000);  // Жди 100ms
}

// Получатель:
// - Игнорирует дубликаты (по seq_num)
// - Просит повтор, если пакет потерян

Решение 2: Forward Error Correction (FEC)

// Вместо отправки 1 пакета, отправляем 3:
// Пакет A, Пакет B, Пакет (A XOR B)
// Если потеряется любой из трех, можно восстановить

// Используется в:
// - Потоковом видео (стандарт 3GPP)
// - Сетях 5G
// - Спутниковых каналах

8. Best Practices для UDP

✓ Правила:

// 1. Обработка потерь
// Приложение ДОЛЖНО быть готово к потере пакетов
// Не полагайся на UDP как на надежный транспорт

// 2. Размер пакета
// Максимум 512 байт (IPv4 без фрагментации)
const int MAX_PACKET = 508;  // 512 - 4 байта заголовка

sendto(sock, data, MAX_PACKET, 0, dest, dest_len);

// 3. Таймауты
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

// 4. Обработка EWOULDBLOCK
ssize_t n = recvfrom(sockfd, buffer, 1024, 0, NULL, NULL);
if (n < 0 && errno == EWOULDBLOCK) {
    // Таймаут - нет данных
}

// 5. Не используй UDP если нужна надежность на 100%
// TCP был создан именно для этого

Заключение

UDP - это специализированный инструмент:

  • Для потоковых данных (видео, аудио, сенсоры)
  • Для низкой латентности (игры, торговля, VoIP)
  • Для broadcast/multicast (discovery, IPTV)

Главное правило: Используй UDP когда потеря нескольких пакетов не критична, но скорость критична. TCP для надежности, UDP для скорости.

Современный интернет:

  • QUIC (вместо TCP) - использует UDP для улучшенной производительности HTTP/3
  • WebRTC - использует UDP для видеосвязи
  • DNS-over-HTTPS - использует UDP для быстроты

UDP - это не "плохой TCP", это инструмент с другими приоритетами!

Для чего нужен UDP? | PrepBro