39. 수신 버퍼 컴팩션 누락: 부분 프레임 유실로 인한 프레임 desync (C#)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: TCP 게임 세션의 수신 파이프라인.
// 프레임 포맷: [ushort length][payload(length 바이트)] (리틀 엔디안, length는 payload 길이)
// TCP는 스트림이라 한 번의 Receive가 프레임 경계와 무관하게 임의 바이트를 준다:
// - 한 번에 여러 프레임이 뭉쳐 오기도 하고(N개 완결 + 다음 프레임의 앞부분 일부)
// - 한 프레임이 여러 번에 걸쳐 쪼개져 오기도 한다.
//
// 요구사항:
// 1) 완결된 프레임만 골라 순서대로 디스패치한다.
// 2) 아직 덜 온 부분 프레임의 바이트는 버리지 말고 보존해, 다음 수신분과 이어붙여 파싱한다.
// 3) 어떤 조각 경계로 쪼개져 와도 프레임 경계가 어긋나선 안 된다.
//
// 아래 수신기에는 이 요구사항을 위반하는 지점이 있다. (A)(B)를 중심으로 진단·수정하라.
using System;
public class FrameReceiver
{
private readonly byte[] _buffer = new byte[64 * 1024];
private int _count; // _buffer[0.._count)에 유효 데이터
public event Action<byte[]> OnFrame = delegate { };
// 소켓에서 읽은 data를 누적 버퍼에 이어붙이고 완결 프레임을 처리한다.
public void OnReceive(ReadOnlySpan<byte> data)
{
// (B) 누적 버퍼 뒤에 이어붙임
data.CopyTo(_buffer.AsSpan(_count));
_count += data.Length;
int offset = 0;
while (_count - offset >= 2)
{
int len = BitConverter.ToUInt16(_buffer.AsSpan(offset, 2));
if (_count - offset - 2 < len)
break; // payload가 아직 덜 왔다 → 다음 수신까지 대기
var frame = _buffer.AsSpan(offset + 2, len).ToArray();
OnFrame(frame);
offset += 2 + len;
}
// (A) 이번에 처리하고 남은 바이트 정리
_count = 0;
}
} 결함 코드 · C++
// 시나리오: TCP 게임 세션의 수신 파이프라인 (C++ 서버).
// 프레임 포맷: [uint16 length][payload(length 바이트)] (리틀 엔디안, length는 payload 길이)
// TCP는 스트림이라 한 번의 recv가 프레임 경계와 무관하게 임의 바이트를 준다:
// - 한 번에 여러 프레임이 뭉쳐 오기도 하고(N개 완결 + 다음 프레임의 앞부분 일부)
// - 한 프레임이 여러 번에 걸쳐 쪼개져 오기도 한다.
//
// 요구사항:
// 1) 완결된 프레임만 골라 순서대로 디스패치한다.
// 2) 아직 덜 온 부분 프레임의 바이트는 버리지 말고 보존해, 다음 수신분과 이어붙여 파싱한다.
// 3) 어떤 조각 경계로 쪼개져 와도 프레임 경계가 어긋나선 안 된다.
//
// 아래 수신기에는 이 요구사항을 위반하는 지점이 있다. (A)(B)를 중심으로 진단·수정하라.
#include <cstdint>
#include <cstring>
#include <functional>
#include <vector>
class FrameReceiver {
public:
std::function<void(const std::vector<uint8_t>&)> onFrame;
// 소켓에서 읽은 [data, data+n)을 누적 버퍼에 이어붙이고 완결 프레임을 처리한다.
void onReceive(const uint8_t* data, size_t n) {
// (B) 누적 버퍼 뒤에 이어붙임
size_t oldSize = buffer_.size();
buffer_.resize(oldSize + n);
std::memcpy(buffer_.data() + oldSize, data, n);
size_t offset = 0;
while (buffer_.size() - offset >= 2) {
uint16_t len = 0;
std::memcpy(&len, buffer_.data() + offset, 2);
if (buffer_.size() - offset - 2 < len)
break; // payload가 아직 덜 왔다
std::vector<uint8_t> frame(buffer_.begin() + offset + 2,
buffer_.begin() + offset + 2 + len);
if (onFrame) onFrame(frame);
offset += 2 + len;
}
// (A) 이번에 처리하고 남은 바이트 정리
buffer_.clear();
}
private:
std::vector<uint8_t> buffer_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.