> Есть ли у TCP и UDP операция подтверждения доставки (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: Avito

Стек: Go

> Пример ответа

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

У TCP есть встроенное подтверждение доставки - механизм ACK (acknowledgment), который работает на уровне протокола и гарантирует доставку сегментов. UDP не имеет никакого подтверждения доставки - это fire-and-forget протокол. Если нужна гарантия доставки поверх UDP, её реализуют на прикладном уровне (например, QUIC, WebRTC, или собственный протокол с ACK). В Go это различие проявляется при использовании net.TCPConn и net.UDPConn.

Подробное объяснение

TCP - это протокол с установлением соединения и гарантированной доставкой. Подтверждение доставки реализовано через:

  • ACK-сегменты: получатель отправляет отправителю подтверждение о получении данных. Номер ACK указывает на следующий ожидаемый байт.
  • Кумулятивные ACK: одно подтверждение может покрывать несколько сегментов.
  • Таймауты и ретрансмиссия: если отправитель не получает ACK за RTO (retransmission timeout), он повторно отправляет данные.
  • SACK (Selective Acknowledgment): позволяет подтверждать отдельные сегменты, а не только кумулятивный поток.

UDP - это протокол без установления соединения. Он просто отправляет датаграммы в сеть без какой-либо обратной связи. Никаких ACK, никаких таймаутов, никакой ретрансмиссии. Причины:

  • Минимальный overhead - заголовок 8 байт против 20+ у TCP.
  • Низкая задержка - нет handshake, нет ожидания подтверждений.
  • Подходит для real-time приложений (VoIP, стриминг, игры), где потеря пакета лучше, чем задержка.

Важно понимать: даже в TCP подтверждение доставки не означает, что приложение получило данные - это подтверждение на уровне ядра ОС. Гарантия доставки заканчивается на уровне сетевого стека получателя.

На практике

В Go работа с подтверждениями доставки зависит от выбранного протокола:

  • TCP: используете net.Dial("tcp", ...) - подтверждения обрабатываются автоматически. Приложение получает гарантию, что данные записаны в сокет, но не что они прочитаны на другом конце. Для подтверждения на уровне приложения нужно реализовать собственный протокол (например, отправить ответное сообщение).

  • UDP: net.Dial("udp", ...) или net.ListenPacket("udp", ...) - нет никаких подтверждений. Если нужна надёжность, вы реализуете её сами: добавляете sequence numbers, таймауты, ретрансмиссию, ACK-сообщения. Это сложно сделать правильно - именно поэтому появился QUIC, который работает поверх UDP, но предоставляет надёжность TCP.

  • QUIC: в Go можно использовать github.com/quic-go/quic-go - это протокол поверх UDP с подтверждениями, мультиплексированием и меньшей задержкой, чем TCP.

Пример кода

GO
// TCP: подтверждение доставки на уровне протокола
conn, err := net.Dial("tcp", "example.com:80")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
_, err = conn.Write([]byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"))
if err != nil {
log.Fatal(err)
}
// TCP ACK уже отправлен и обработан ядром.
// Но мы не знаем, получил ли сервер данные, пока не прочитаем ответ.
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Ответ сервера: %s\n", buf[:n])
GO
// UDP: нет подтверждений, нужно реализовывать самому
conn, err := net.Dial("udp", "example.com:1234")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
_, err = conn.Write([]byte("ping"))
if err != nil {
log.Fatal(err)
}
// Никакой гарантии, что пакет дошёл.
// Если нужен ACK, нужно ждать ответного пакета с таймаутом.
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
log.Println("Нет подтверждения:", err)
return
}
fmt.Printf("Получен ACK: %s\n", buf[:n])

Как отвечать на собеседовании

Начните с прямого ответа: TCP - да, UDP - нет. Затем кратко объясните механизм ACK в TCP. Упомяните, что подтверждение в TCP работает на уровне протокола и не гарантирует доставку приложению - это важный нюанс. Для UDP скажите, что надёжность можно добавить на прикладном уровне, и приведите примеры: QUIC, собственные протоколы. Если спросят про Go - покажите разницу между net.TCPConn и net.UDPConn, упомяните, что в TCP всё автоматически, а в UDP нужно писать свою логику подтверждений. Не углубляйтесь в детали реализации TCP-стека, если не просят - достаточно упомянуть ACK, таймауты и ретрансмиссию.

Что проверяет интервьюер

Интервьюер проверяет:

  • Понимание фундаментальной разницы между TCP и UDP - это база, которую должен знать senior.
  • Умение объяснить, что подтверждение доставки - это не то же самое, что гарантия доставки приложению.
  • Понимание trade-off: надёжность TCP против скорости и простоты UDP.
  • Знание современных решений поверх UDP (QUIC) - это плюс для senior.
  • Умение применить знания в контексте Go: как это выглядит на практике с net пакетом.

Типичные ошибки

  • Утверждение, что TCP гарантирует доставку данных приложению - это не так, гарантия заканчивается на уровне ядра.
  • Забывают упомянуть, что UDP тоже может иметь подтверждения, если их реализовать на прикладном уровне.
  • Путают ACK на уровне TCP с подтверждением на уровне приложения - это разные вещи.
  • Не упоминают QUIC как современное решение, которое сочетает преимущества UDP и надёжность TCP.
  • В контексте Go говорят о подтверждениях в UDP, не уточняя, что это самописная логика, а не встроенная возможность.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью