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

Опишите TCP 3-way handshake.

1.0 Junior🔥 251 комментариев
#Linux и операционные системы#Сети и протоколы#Язык C++

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

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

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

Опишите TCP 3-way handshake

TCP 3-way handshake (трёхсторонний процесс установления соединения) — это последовательность из трёх пакетов, которую выполняют два хоста для установления надёжного TCP-соединения. Этот процесс гарантирует, что оба хоста готовы к обмену данными и синхронизирует начальные номера последовательности (sequence numbers).

Этапы 3-way handshake

Этап 1: SYN (Synchronize)

Клиент → Сервер
├─ Flag: SYN
├─ SEQ: x (начальный номер последовательности клиента)
├─ ACK: 0 (нет подтверждения)
└─ Состояние клиента: SYN_SENT

Этап 2: SYN-ACK (Synchronize + Acknowledge)

Сервер → Клиент
├─ Flag: SYN + ACK
├─ SEQ: y (начальный номер последовательности сервера)
├─ ACK: x + 1 (подтверждение SEQ клиента)
└─ Состояние сервера: SYN_RECEIVED

Этап 3: ACK (Acknowledge)

Клиент → Сервер
├─ Flag: ACK
├─ SEQ: x + 1
├─ ACK: y + 1 (подтверждение SEQ сервера)
└─ Состояние клиента: ESTABLISHED
└─ Состояние сервера: ESTABLISHED

Диаграмма временной последовательности

Клиент                          Сервер
  |                               |
  |────── SYN (seq=x) ──────────>|
  |                               |
  |<───── SYN-ACK (seq=y, ack=x+1) ─|
  |                               |
  |────── ACK (seq=x+1, ack=y+1) ──>|
  |                               |
  |<======== СОЕДИНЕНИЕ УСТАНОВЛЕНО ========>
  |                               |
  |<── Данные (seq=y+1) ────────|
  |                               |
  |── Данные (seq=x+1) ────────>|
  |                               |

Детальный пример с номерами последовательности

Предположим:
Клиент выбрал ISS (Initial Sequence Number): 1000
Сервер выбрал ISS: 2000

Шаг 1: Клиент отправляет SYN
├─ Source Port: 54321
├─ Dest Port: 80
├─ Sequence Number: 1000 (ISS клиента)
├─ Acknowledgment Number: 0
└─ Flags: SYN

Шаг 2: Сервер отправляет SYN-ACK
├─ Source Port: 80
├─ Dest Port: 54321
├─ Sequence Number: 2000 (ISS сервера)
├─ Acknowledgment Number: 1001 (ack = client_seq + 1)
└─ Flags: SYN, ACK

Шаг 3: Клиент отправляет ACK
├─ Source Port: 54321
├─ Dest Port: 80
├─ Sequence Number: 1001 (seq = prev_seq + 1)
├─ Acknowledgment Number: 2001 (ack = server_seq + 1)
└─ Flags: ACK

Теперь оба хоста синхронизированы и готовы обмениваться данными.

Состояния сокета при handshake

На стороне клиента:

CLOSED (начальное)
  ↓ (connect() вызов)
SYN_SENT (ожидание SYN-ACK)
  ↓ (получен SYN-ACK, отправлен ACK)
ESTABLISHED (соединение готово)

На стороне сервера:

LISTEN (ожидание входящих соединений)
  ↓ (получен SYN)
SYN_RECEIVED (ожидание ACK)
  ↓ (получен ACK)
ESTABLISHED (соединение готово)

Реализация на C++

Серверная сторона:

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

int main() {
    // Создание сокета
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        std::cerr << "Socket creation failed" << std::endl;
        return 1;
    }
    
    // Привязка к порту
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        std::cerr << "Bind failed" << std::endl;
        return 1;
    }
    
    // listen() переводит сокет в режим LISTEN (готов к handshake)
    if (listen(server_socket, 5) < 0) {
        std::cerr << "Listen failed" << std::endl;
        return 1;
    }
    
    std::cout << "Server listening on port 8080..." << std::endl;
    
    // accept() выполняет 3-way handshake автоматически
    // и возвращает новый сокет для этого соединения
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    
    int client_socket = accept(server_socket, 
                               (struct sockaddr*)&client_addr, 
                               &client_len);
    
    if (client_socket < 0) {
        std::cerr << "Accept failed" << std::endl;
        return 1;
    }
    
    std::cout << "Client connected from " 
              << inet_ntoa(client_addr.sin_addr) 
              << ":" << ntohs(client_addr.sin_port) << std::endl;
    
    // На этом моменте handshake завершён и соединение ESTABLISHED
    
    // Отправка/получение данных
    const char* message = "Hello, Client!";
    send(client_socket, message, strlen(message), 0);
    
    char buffer[1024];
    int n = recv(client_socket, buffer, sizeof(buffer), 0);
    if (n > 0) {
        buffer[n] = '\0';
        std::cout << "Received: " << buffer << std::endl;
    }
    
    // Закрытие соединения
    close(client_socket);
    close(server_socket);
    
    return 0;
}

Клиентская сторона:

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

int main() {
    // Создание сокета
    int client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (client_socket < 0) {
        std::cerr << "Socket creation failed" << std::endl;
        return 1;
    }
    
    // Адрес сервера
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    server_addr.sin_addr.s_addr = inet_aton("127.0.0.1", &server_addr.sin_addr);
    
    // connect() выполняет 3-way handshake:
    // 1. Отправляет SYN
    // 2. Ждёт SYN-ACK
    // 3. Отправляет ACK
    if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        std::cerr << "Connection failed" << std::endl;
        return 1;
    }
    
    std::cout << "Connected to server!" << std::endl;
    
    // На этом моменте handshake завершён и соединение ESTABLISHED
    
    // Получение данных
    char buffer[1024];
    int n = recv(client_socket, buffer, sizeof(buffer), 0);
    if (n > 0) {
        buffer[n] = '\0';
        std::cout << "Server says: " << buffer << std::endl;
    }
    
    // Отправка данных
    const char* message = "Hello, Server!";
    send(client_socket, message, strlen(message), 0);
    
    // Закрытие соединения
    close(client_socket);
    
    return 0;
}

Особые флаги TCP

Флаги в TCP заголовке:

┌─────────────────────────────────┐
│ URG | ACK | PSH | RST | SYN | FIN │  (6 флагов)
└─────────────────────────────────┘

В handshake используются:
SYN = 1  (синхронизация, инициация)
ACK = 1  (подтверждение получения)

Пример: Захват пакетов tcpdump

# Для просмотра реального 3-way handshake
tcpdump -i eth0 'tcp port 8080' -S -n

# Вывод будет примерно:
# 192.168.1.100.54321 > 192.168.1.200.8080: Flags [S], seq 1000
# 192.168.1.200.8080 > 192.168.1.100.54321: Flags [S.], seq 2000, ack 1001
# 192.168.1.100.54321 > 192.168.1.200.8080: Flags [.], seq 1001, ack 2001

Почему необходимо 3 пакета?

Для синхронизации начальных номеров:

  • Оба хоста должны сообщить свой ISS (Initial Sequence Number)
  • Оба должны подтвердить получение ISS другого
  • Это требует минимум 3 пакетов

Защита от старых пакетов: Помогает различить новое соединение от остатков старого

SYN Flood атака

Аттакующий отправляет множество SYN пакетов, но не отправляет финальный ACK.
Сервер переходит в SYN_RECEIVED и ждёт (в очереди полусоединений).
Сервер может исчерпать ресурсы и отказать в обслуживании.

Защита:
- SYN cookies
- Ограничение очереди полусоединений
- Фильтрация по IP

Временные ограничения

// Установка timeout для connect (Linux)
int timeout = 3;  // секунды
setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, 
           &timeout, sizeof(timeout));

// Установка timeout для accept (Linux)
setsockopt(server_socket, SOL_SOCKET, SO_RCVTIMEO, 
           &timeout, sizeof(timeout));

Практическое применение в Backend

1. Подключение к БД: Когда приложение подключается к PostgreSQL/MySQL, происходит TCP handshake.

2. HTTP запросы: Каждый HTTP запрос начинается с 3-way handshake (если нет keep-alive).

3. Микросервисы: Общение между сервисами через gRPC/REST требует handshake.

4. Мониторинг: Тулы мониторинга используют handshake для проверки доступности портов.

Закрытие соединения (4-way handshake)

Клиент → Сервер: FIN (я готов закрыться)
Сервер → Клиент: ACK (понял)
Сервер → Клиент: FIN (я тоже готов)
Клиент → Сервер: ACK (подтверждаю)

Итоговая важность

Понимание TCP 3-way handshake критично для:

  • Диагностики сетевых проблем (Wireshark, tcpdump)
  • Оптимизации соединений (connection pooling, keep-alive)
  • Написания надёжного сетевого кода
  • Разработки микросервисных архитектур
  • Защиты от DDoS атак
Опишите TCP 3-way handshake. | PrepBro