"~ TCP 네트워크 특성상 Nagle 알고리즘을 사용한다.." 책을 읽다 궁금한 문장이 나와 내용을 정리해본다..
TCP는 안정적인 통신을 보장하는 프토로콜이다.
그렇다면 Nagle 알고리즘이 무엇이고 왜 TCP에 사용되는지 알아보자.
TCP 네트워크의 특성
- 연결 지향 (Connection-oriented): 데이터를 보내기 전에 먼저 상대와 연결을 한다.
- 신뢰성 (Reliable Transmission): 데이터가 손실되거나 순서가 어긋나도 TCP는 자동으로
재전송하고 순서를 맞춰 준다. - 순서 보장 (Ordering): 보낸 순서대로 도착하지 않더라도 TCP가 정렬해서 순서대로 전달한다.
- 흐름 제어/혼잡 제어: 수신자가 감당할 수 있는 만큼만 데이터를 보내고, 네트워크 혼잡 시 전송량을 줄인다.
Nagle 알고리즘 이란?
TCP가 데이터를 보내는 단위는 세그먼트(Segment)이다. 이 세그먼트에 아주 작은 데이터를 자주 보내게 되면
매번 세그먼트 헤더가 붙어 전송 효율이 떨어져 네트워크 대역폭이 낭비가 된다.
즉 Nagle 알고리즘은 작은 데이터 조각을 너무 자주 전송하는 비효율을 줄이기 위해
1984년 John Nagle이 고안된 TCP 최적화 알고리즘이다.
Nagle 알고리즘의 규칙은 다음과 같다.
'ACK(Acknowledgment)를 받기 전에 새로운 작은 데이터 패킷(packet)을 보내지 않고 묶어서 보낸다.'
Nagle 알고리즘은 다음과 같이 동작한다.
작은 데이터를 전송하려 할 때 아직 이전 데이터에 대한 ACK응답을 받지 못했다면
새로운 데이터는 버퍼(send buffer)에 임시로 저장되고 전송을 보류한다.
ACK가 도착하거나 버퍼에 충분한 데이터가 쌓이면 TCP 세그먼트로 묶어 전송한다.
Nagle 알고리즘을 비활성화 할 때
Nagle 알고리즘은 네트워크 효율성에는 도움이 되지만, 모든 애플리케이션에 적합한 것은 아니다.
예를 들어 다음과 같은 경우엔 오히려 문제가 될 수 있다:
- 실시간 입력이 중요한 경우 (예: 키 입력마다 서버로 전송되는 온라인 게임, 채팅)
- 빠른 반응 속도가 필요한 애플리케이션 (예: 금융 거래 시스템, 원격 제어 앱)
- ACK 지연으로 인해 전송 지연이 눈에 띄는 경우
Nagle 알고리즘 ON vs OFF
실제 어떤식으로 볼 수 있을지 궁금하여 실험해 보았다.
🧪 실험 환경
환경: Windows + Visual Studio + WSL(Ubuntu) + Wireshark
구성 1: 로컬 TCP 서버 & 클라이언트 (127.0.0.1)
구성 2: Windows에서 서버 실행 & WSL(Ubuntu)에서 클라이언트 실행 (WSL <-> Windows 간 TCP 통신)
전송: 클라이언트가 1바이트 문자열을 10번 전송
딜레이: QueryPerformanceCounter()를 이용해 약 0.2ms 지연 삽입
🪄기대 결과
Nagle ON: 대부분의 경우, 패킷이 묶여 전송됨
Nagle OFF: 패킷이 개별 전송됨
Wireshark 같은 도구로 패킷 수와 전송 타이밍을 확인하면 차이를 명확히를 보고 싶다!
🧱 실험 코드 핵심
//Client쪽 코드
for (int i = 0; i < 10; ++i)
{
std::string msg = std::to_string(i % 9);
send(sock, msg.c_str(), msg.size(), 0);
LARGE_INTEGER freq, start, end;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&start);
do {
QueryPerformanceCounter(&end);
} while ((end.QuadPart - start.QuadPart) < freq.QuadPart / 5000); // 약 0.2ms 대기
}
🤔0.2ms 지연해보기
LARGE_INTEGER freq, start, end;
QueryPerformanceFrequency(&freq); // 초당 카운트 수를 반환하는 WinAPI 함수
QueryPerformanceCounter(&start);
do {
QueryPerformanceCounter(&end);
} while ((end.QuadPart - start.QuadPart) < freq.QuadPart / 5000); // 약 0.2ms 대기
sleep() 함수로 지연효과를 주어도 차이가 없어서 더 짧은 지연 효과를 주고 싶었다.
QueryPerformanceFrequency()는 성능 카운터(Performance Counter)의 빈도를 반환하는 함수이다.
예를 들어 frep = 3,000,000이라면 1초에 300만번 카운트되는 것이다.
start와 end의 차이가 카운터 기준으로 1 / 5000초 만큼 차인가 나면 반복문을 탈출하게 한다.
약 0.2ms 동안 대기를 하게 만든 것이다.
// server쪽 코드
while (true)
{
int valread = recv(client_socket, buffer, 1024, 0);
if (valread <= 0) break;
std::cout << "받은 데이터 : " << std::string(buffer, valread) << "\n";
packet_count++;
std::cout << "패킷 " << packet_count << ": " << std::string(buffer, valread) << std::endl;
}
🦈Wireshark로 본 결과
생각보다 차이가 없어서 당황했다... Nagle On이든 Off 이든 Len = 1인 패킷들이 전송되었다..
WSL 환경이나 로컬에서 Wireshark로 패킷을 캡처하면 패킷 수나 크기가 Nagle On/Off에 관계 없이
거의 동일하게 보였다..
찾아보니 로컬에서는 ACK 응답이 너무 빨라 ACK가 거의 즉시 도착하여 병합이 발생할 기회가 없고
WSL <-> Windows 간의 내부 가상 통신은 Wireshark에 잡히지 않는다 한다...
그래서 콘솔 recv()로 받은 데이터의 출력으로 Nagle 알고리즘 작동 여부를 간접적으로 느껴보았다.
이런 느낌으로 패킷을 나눠서 보내거나 모아서 보낸다고 이해했다.
다음에는 컴퓨터 두대로 진짜 패킷이 모아서 가는지를 확인해봐야겠다..
QueryPerformanceFrequency 함수 - Win32 apps
성능 카운터의 빈도를 검색합니다.
learn.microsoft.com
'Game Server Class 101' 카테고리의 다른 글
야~ 거기 CPU 좀 못 놀게 해라 Multi-Threading (0) | 2025.05.16 |
---|---|
TCP랑 UDP 특. 정확하거나 빠르거나 (0) | 2025.05.15 |