지직전기

[Socket] send()와 recv()는 1:1 매핑일까? feat 네이글알고리즘 본문

STUDY/C, C++

[Socket] send()와 recv()는 1:1 매핑일까? feat 네이글알고리즘

MSH103 2024. 9. 7. 00:31

send()와 recv()는 1:1 매핑일까?

socket을 이용한 통신프로그램을 짜다보면 send()와 recv() 함수는 각각 데이터 송수신을 담당하는 중요한 역할을 하지만, 흔히 생각하는 것처럼 이 두 함수가 1:1로 매핑되지는 않습니다. send()를 호출하면 즉시 대응하는 recv()가 실행될 것이라고 예상할 수 있지만, 네트워크 상태, 소켓 버퍼 크기, 패킷 크기 등의 다양한 요소가 데이터 송수신의 흐름에 영향을 미칩니다.

아래 예시코드는 채팅서버에서 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'라는 긴 채팅메세지를 for문을 통해 한글자씩 보내도록 만든 코드입니다.

<예시코드>

#include "stdafx.h"
#include <winsock2.h>
#pragma comment(lib, "ws2_32")


int _tmain(int argc, _TCHAR* argv[])
{
	WSADATA wsa = { 0 };
	if (::WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		puts("ERROR: 윈속을 초기화 할 수 없습니다.");
		return 0;
	}

	//접속대기 소켓 생성
	SOCKET hSocket = ::socket(AF_INET, SOCK_STREAM, 0);
	if (hSocket == INVALID_SOCKET)
	{
		puts("ERROR: 소켓을 생성할 수 없습니다.");
		return 0;
	}

	//포트 바인딩 및 연결
	SOCKADDR_IN	svraddr = { 0 };
	svraddr.sin_family = AF_INET;
	svraddr.sin_port = htons(25000);
	svraddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (::connect(hSocket,
		(SOCKADDR*)&svraddr, sizeof(svraddr)) == SOCKET_ERROR)
	{
		puts("ERROR: 서버에 연결할 수 없습니다.");
		return 0;
	}

	//
	int nOpt = 1;
	::setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY,
		(char*)&nOpt, sizeof(nOpt));

	//채팅 메시지 송/수신
	char szBuffer[128] = { 0 };
	while (1)
	{
		//사용자로부터 문자열을 입력 받는다.
		gets_s(szBuffer);
		if (strcmp(szBuffer, "EXIT") == 0)		break;

		//사용자가 입력한 문자열을 한글자씩 서버에 전송한다.
		int nLength = strlen(szBuffer);
		for (int i = 0; i < nLength; ++i)
			::send(hSocket, szBuffer + i, 1, 0);

		//서버로부터 방금 보낸 문자열에 대한 에코 메시지를 수신한다.
		memset(szBuffer, 0, sizeof(szBuffer));
		::recv(hSocket, szBuffer, sizeof(szBuffer), 0);
		printf("From server: %s\n", szBuffer);
	}

	//소켓을 닫고 종료.
	::shutdown(hSocket, SD_BOTH);
	::closesocket(hSocket);
	::WSACleanup();
	return 0;
}

 

Wireshark와 같은 네트워크 트래픽 분석 도구를 사용해 데이터를 분석해 보면, 실제로는 한 글자씩 전송되는 경우도 있지만, 여러 글자가 한 번에 전송되는 경우도 자주 관찰됩니다. 이것은 운영 체제와 네트워크에서 데이터를 효율적으로 처리하기 위한 최적화 기법 때문입니다.

네이글 알고리즘(Nagle Algorithm)

네이글 알고리즘은 네트워크 통신에서 작은 패킷의 수를 줄여 네트워크의 효율성을 높이는 목적을 가진 최적화 기법의 알고리즘 입니다.
기본 원리는 작은 데이터 패킷들을 모아서 더 큰 패킷으로 전송하는 것입니다.
이를 통해 네트워크 트래픽을 줄이고 전송 효율을 높일 수 있습니다.

 

24인승 버스를 예로 들어 보겠습니다. 승객이 두 명만 탑승한 상태에서 목적지까지 이동하려면 빈 버스가 출발해야 합니다. 이는 효율적이지 않습니다. 네이글 알고리즘은 승객이 적당히 탈 때까지 버스가 기다렸다가 이동하는 것과 비슷합니다. 다만, 승객을 기다리는 동안 지연이 발생할 수 있습니다.


네이글 알고리즘 비활성화(TCP_NODELAY)

네이글 알고리즘이 지연을 초래할 수 있기 때문에, 실시간 응답이 중요한 애플리케이션에서는 TCP_NODELAY 옵션을 통해 이 알고리즘을 비활성화할 수 있습니다.

<예시 코드>

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('example.com', 8080))

# 네이글 알고리즘 비활성화 옵션 설정
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

sock.send(b'Hello, World!')


이를 통해 즉각적인 응답을 요구하는 애플리케이션에서 지연을 최소화할 수 있지만, 네트워크 성능에 미치는 영향을 신중히 고려해야 합니다.

 

정리

결론적으로 send()와 recv()는 1:1 매핑되지 않으며, 여러 네트워크 최적화 기법과 시스템 요소가 데이터 송수신에 영향을 미칩니다.

네이글 알고리즘은 작은 패킷을 모아서 전송하는 방식으로 네트워크 효율을 높이지만, 실시간 성능이 중요한 경우에는

TCP_NODELAY 옵션을 사용해 네이글 알고리즘을 비활성화할 수 있습니다.

하지만 언제나 네트워크 상황과 애플리케이션의 요구 사항을 고려하여 적절한 선택을 해야 합니다.