개요
다른 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에 관련된 함수와 메서드에서 결합 버퍼를 나타낼 때 이 클래스를 쓸 수 있다.
출력 버퍼 만들기
- 버퍼를 할당한다. 여기서는 기본 문자열 타입의 변수를 사용한다.
- 버퍼를 출력할 데이터로 채운다.
- 버퍼를 ConstBufferSequence 개념의 요구사항을 만족하는 객체로 표현한다.
- 이제 이 버퍼를 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;
}
입력 버퍼 만들기
- 버퍼를 할당한다, 버퍼는 받아들일 데이터 블록만큼은 커야 한다.
- 버퍼를 Mutable_Buffer 시퀀스 개념의 요구사항을 만족하는 객체로 초기화 한다.
- 버퍼를 입력 연산에 사용한다.
#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 형태로 감싸야 통신에 적합한 버퍼 시퀀스가 된다.
'네트워크 통신 > Boost' 카테고리의 다른 글
Boost.asio I/O TCP 소켓 동기적 쓰기 (0) | 2024.11.08 |
---|---|
Boost.asio I/O 가변 크기 버퍼, 스트림 버퍼 (0) | 2024.11.08 |
Boost.asio I/O 연산 (0) | 2024.11.08 |
Boost.asio 소켓 연결 수락 (0) | 2024.11.07 |
Boost.asio 소켓 연결 요청 (2) | 2024.11.07 |