네트워크 통신/Boost

Boost.asio I/O 고정 크기 버퍼

마달랭 2024. 11. 8. 12:02
반응형

개요

다른 I/O와 마찬가지로, 네트워크 I/O에서도 메모리 버퍼를 사용한다.
메모리 버퍼란, 프로세스의 주소 영역 내에 연속된 메모리 공간으로, 데이터를 저장하는데 사용한다.

어떤 입력 연산을 실행했다면, 프로세스에 도착한 데이터는 주소 공간 어딘가에 저장되어야만 처리할 수 있다.
즉, 버퍼가 있어야 I/O를 처리할 수 있다는 것이다.
입력 연산을 실행하기 전에 버퍼를 할당해둬야 하고 입력 연산 중에 데이터를 저장할 곳으로 이 버퍼를 사용해야 한다.
입력 연산이 끝나면, 버퍼에 입력 데이터가 저장되어 있어 나중에 어플리케이션이 처리할 수 있다.

이와 마찬가지로 출력 연산을 실행하기 전에는 데이터를 준비하여 출력 연산에 쓸 출력 버퍼에 저장해둬야 한다.
그러면 출력 버퍼가 데이터의 소스가 된다.

버퍼는 네트워크 I/O뿐만 아니라 모든 I/O를 실행하는 어플리케이션이라면 꼭 사용해야 하는 필수 요소다.

 

 

고정 크기 I/O 버퍼

크기가 고정된 I/O 버퍼는 대체로 I/O 연산 중 보내거나 받을 메시지의 크기를 알고 있을 때에 사용된다.

예를 들어, 서버로 보낼 요청에 대한 문자열이라면 스택에 할당된 상수 문자 배열에 저장할 수도 있다.(const char*)

또한 자유 메모리에 할당된 쓰기가 가능한 버퍼라면 소켓에서 읽은 데이터를 저장하는 데이터 목적지로 사용할 수 있다.

 

Boost.asio에서는 고정 크기를 버퍼를 나타내는 클래스가 존재한다.

  • asio::mutable_buffer
  • asio::const_buffer

두 클래스 모두 연속된 메모리 블록을 블록의 첫 번째 바이트 주소와 블록의 크기로 표현한다.

이름에서 알 수 있듯이 mutable_buffer는 쓰기가 가능한 버퍼를 나타내고, const_buffer는 읽기만 가능한 버퍼이다.

하지만 위 두 클래스는 Boost.asio I/O 함수와 메서드에서 직접 사용할 수 없다.

그 대신 MutableBufferSequence와 ConstBufferSequence라는 개념이 사용된다.

 

MutableBufferSequence는 asio::mutable_buffer 객체의 모음을 나타나는 개체이다.

ConstBufferSequence는 asio::const_buffer 객체의 모음을 나타내는 객체이다.

I/O 연산을 하는 Boost.asio 함수와 메서드들은 버퍼에 대한 인자로 위 두 개념을 만족하는 객체를 받는다.

 

대부분의 경우 단일 I/O 연산에서 하나의 버퍼를 사용하지만, 특별한 경우 여러개의 버퍼를 사용할 때도 있다.

위 두 개념만 만족한다면, 버퍼들의 묶음인 결합 버퍼도 사용할 수 있다.

 

예를 들어 vector<asio::mutable_buffer> 클래스는 MutableBufferSequence개념을 만족시킨다.

따라서 I/O에 관련된 함수와 메서드에서 결합 버퍼를 나타낼 때 이 클래스를 쓸 수 있다.

 

 

출력 버퍼 만들기

  1. 버퍼를 할당한다. 여기서는 기본 문자열 타입의 변수를 사용한다.
  2. 버퍼를 출력할 데이터로 채운다.
  3. 버퍼를 ConstBufferSequence 개념의 요구사항을 만족하는 객체로 표현한다.
  4. 이제 이 버퍼를 Boost.asio의 출력 메서드나 함수에서 사용할 수 있다.

원격 어플리케이션으로 Hello라는 문자열을 보내고 싶다고 가정한다.

 

#include <boost/asio.hpp>
#include <iostream>

using namespace boost;

int main()
{
	std::string buf; // 버퍼 초기화
	buf = "Hello";   // 버퍼에 데이터 추가

	// Step 3. 버퍼를 const_buffer 시퀀스로 변환
	// 과거엔 buffer메서드에 buf만 전달해 주면 됐다.
	// 현재는 data(), size() 메서드를 통해 명시해 주어야 한다.
	asio::const_buffers_1 output_buf = asio::buffer(buf.data(), buf.size());

	// Step 4. 이제 output_buf를 출력 연산에 사용할 수 있다.

	return 0;
}

 

 

입력 버퍼 만들기

  1. 버퍼를 할당한다, 버퍼는 받아들일 데이터 블록만큼은 커야 한다.
  2. 버퍼를 Mutable_Buffer 시퀀스 개념의 요구사항을 만족하는 객체로 초기화 한다.
  3. 버퍼를 입력 연산에 사용한다.
#include <boost/asio.hpp>
#include <iostream>
#include <memory> // std::unique_ptr<>를 사용하기 위해 추가

using namespace boost;

int main()
{
	// 20바이트 보다 작은 데이터를 받는다고 가정
	const size_t BUF_SIZE_BYTES = 20;

	// Step 1. 버퍼를 할당한다.
	std::unique_ptr<char[]> buf(new char[BUF_SIZE_BYTES]);

	// Step 2. MutableBuffer 시퀀스 개념을 만족하는 버퍼 형식으로 변환한다.
	asio::mutable_buffers_1 input_buf =
		asio::buffer(static_cast<void*>(buf.get()),
			BUF_SIZE_BYTES);

	// Step 3. 이제 input_buf를 입력 연산에 사용할 수 있다.

	return 0;
}

 

C에서 고정 크기의 입력 버퍼를 할당 할때는 char buf[20] 이런식으로 할당 했었다.

하지만 Boost에서 입력 버퍼를 할당할 때는 unique_ptr<>를 사용하여 버퍼를 할당한다.

 

두 방식의 큰 차이점은 없으나, 약간의 차이점은 존재한다.

 

메모리 관리

  • char buf[20];는 스택에 할당되므로 함수가 끝나면 자동으로 해제
  • std::unique_ptr<char[]>는 힙 메모리에 할당되며, unique_ptr가 소멸될 때 자동으로 해제

 

버퍼 크기 변경

  • char buf[20];는 고정된 크기로, 할당 후 크기를 변경할 수 없음
  • std::unique_ptr<char[]> 역시 한 번 할당된 크기를 직접 변경할 수는 없지만, 필요 시 더 큰 크기의 버퍼를 새로 할당하여 교체할 수 있음

 

사용 사례

  • 간단한 고정 크기 버퍼가 필요할 때는 char buf[20];이 간편하고 효율적
  • 크기가 가변적이거나 함수 밖에서도 버퍼를 사용할 필요가 있는 경우 std::unique_ptr<char[]>가 더 유연

 

 

const_buffers_1, mutable_buffers_1

위의 두 예제 코드 모두 일반적인 버퍼 형태가 아닌 buffers_1 의 클래스 타입을 사용한 것을 볼 수 있다.

위에서 언급했듯 const_buffer나 mutable_buffer은 각각의 스퀀스 개념에 만족하지 않는다.

따라서 통신에 사용할 버퍼는 단순한 mutable_buffer가 아니라 asio::mutable_buffers_1이나 asio::const_buffers_1 같은 형식이 되어야 한다.

mutable_buffer 자체는 일반적인 버퍼지만, Boost.Asio에서는 이를 buffers_1 형태로 감싸야 통신에 적합한 버퍼 시퀀스가 된다.

 

 

728x90
반응형