Опишите TCP 3-way handshake.
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Опишите 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 атак