데이터 프레이밍이란?
이 기술을 사용하는 이유는 TCP 통신의 메시지 경계 문제를 해결하기 위해서이다.
TCP는 데이터 스트림을 연속적으로 전송하므로, 하나의 큰 데이터 덩어리를 여러 번에 나눠 보내거나, 여러 개의 작은 메시지가 합쳐져서 한 번에 도착할 수 있다.
LengthDelmitedCodec
과 같은 데이터 프레이밍 기술은 이러한 문제를 해결하고, 메시지 단위로 안정적인 통신을 가능하게 한다.
TCP
는 바이트의 연속적인 흐름일 뿐이므로, 한 메시지가 어디서 시작하고 어디서 끝나는지 알 수 없다.
프레이밍 기술은 각 메시지에 명확한 시작과 끝을 부여하여, 수신자가 완전한 하나의 메시지를 정확히 파싱하도록 한다.
Framed
와 같은 래퍼는 개발자가 복잡한 바이트 레벨의 파싱 로직을 직접 구현할 필요 없이, send()
와 next()
같은 간단한 메서드로 메시지를 주고 받을 수 있게 해준다.
LengthDelimitedCodec
의 정확한 기능
LengthDelimitedCodec
은 길이 접두사 프레이밍을 구현하는 코덱이다. 이 코덱은 데이터 스트림을 다룰 때, 두 가지 주요 기능을 수행한다.
// server
pub async fn run_server() {
let listener = TcpListener::bind("0.0.0.0:9999").await.unwrap();
let Ok((mut stream, addr)) = listener.accept().await else {
eprintln!("fail");
return;
};
// TcpStream을 LengthDelimitedCodec으로 래핑
let mut framed = Framed::new(stream, LengthDelimitedCodec::new());
while let Some(result) = framed.next().await {
match result {
Ok(bytes) => {
let message = String::from_utf8_lossy(&bytes);
println!("수신: {}", message);
}
Err(e) => {
eprintln!("통신 오류!: {}", e);
break;
}
}
}
}
// client
pub async fn run_client() {
let Ok(mut stream) = TcpStream::connect("127.0.0.1:9999").await else {
println!("fail to connect <127.0.0.1:9999>");
return;
};
// 보낼 데이터 설정
let send_data = vec!["안녕하세요", "만나서 반가워요", "잘 부탁드려요"];
let mut framed = Framed::new(stream, LengthDelimitedCodec::new());
for data in send_data {
if let Err(e) = framed.send(Bytes::from(data)).await {
eprintln!("메시지 전송 실패: {}", e);
}
}
}
TCP
통신을 할 때, 보낼 데이터의 길이를 먼저 보낸 후 데이터를 보내는 방식으로 프로토콜을 커스텀 하는게 흔하다.
데이터 프레이밍 기술은 위 방식을 추상화하여 개발자가 편하게 사용할 수 있고 실수 없이 안정적으로 구현해놓은 기능인 것 같다.
모든 기술은 장점이 있다면 그에 따르는 트레이드오프가 있다.
데이터 프레이밍 기술의 주요 단점은 아래와 같다고 한다.
위와 같은 트레이드오프가 존재한다고는 하지만 압도적으로 편리한 사용성과 유연성을 고려했을 때, 앞으로 rust에서 TCP 통신을 할 때 적극적으로 사용할 예정이다.