1. TCP 길이 프리픽스 패킷 수신 루프
난이도 하 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - TCP 길이 프리픽스 패킷 수신 루프
// ----------------------------------------------------------------------------
// 시나리오:
// - 게임 클라이언트와 TCP 로 연결된 세션이다.
// - 모든 패킷은 [2바이트 길이(payload 크기, little-endian)] + [payload] 형식.
// - 비동기 Receive 콜백에서 들어온 바이트를 누적 버퍼에 모으고,
// 완성된 패킷을 하나씩 잘라 OnPacket 으로 넘긴다.
// - 한 번의 Receive 콜백에 패킷이 0개, 1개, 여러 개, 혹은 "패킷의 일부"만
// 도착할 수 있다(TCP 는 스트림이라 메시지 경계가 없다).
//
// 요구사항:
// - 부분 수신/복수 수신을 모두 올바르게 처리해야 한다.
// - 처리량이 중요하므로 불필요한 복사는 피하고 싶다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 발생하는지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰한 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
public class PacketSession
{
// 누적 버퍼와 현재 쌓인 바이트 수
private readonly byte[] _buffer = new byte[4096];
private int _writePos = 0;
// 소켓 Receive 콜백이 호출하는 진입점.
// data[0..len) 에 이번에 도착한 바이트가 들어있다.
public void OnReceive(byte[] data, int len)
{
// (A) 도착한 바이트를 누적 버퍼 뒤에 이어붙인다.
Array.Copy(data, 0, _buffer, _writePos, len);
_writePos += len;
int readPos = 0;
// 버퍼에 완성된 패킷이 있는 동안 계속 잘라낸다.
while (true)
{
// 길이 헤더(2바이트)가 아직 안 들어왔으면 대기
if (_writePos - readPos < 2)
break;
// (B) payload 길이 읽기 (little-endian)
ushort payloadLen = (ushort)(_buffer[readPos] | (_buffer[readPos + 1] << 8));
// (C) payload 전체가 도착했는지 확인
if (_writePos - readPos < 2 + payloadLen)
break;
// payload 구간을 잘라 핸들러로 전달
int payloadStart = readPos + 2;
OnPacket(_buffer, payloadStart, payloadLen);
// 다음 패킷으로 이동
readPos += 2 + payloadLen;
}
// (D) 처리하고 남은(미완성) 바이트를 버퍼 앞으로 당긴다.
int remain = _writePos - readPos;
Array.Copy(_buffer, readPos, _buffer, 0, remain);
_writePos = remain;
}
private void OnPacket(byte[] buffer, int offset, int count)
{
// 패킷 디스패치 (생략)
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - TCP 길이 프리픽스 패킷 수신 루프
// ----------------------------------------------------------------------------
// 시나리오:
// - 게임 클라이언트와 TCP 로 연결된 세션이다.
// - 모든 패킷은 [2바이트 길이(payload 크기, big-endian/네트워크 바이트오더)] + [payload] 형식.
// - 비동기 Receive 콜백에서 들어온 바이트를 누적 버퍼에 모으고,
// 완성된 패킷을 하나씩 잘라 OnPacket 으로 넘긴다.
// - 한 번의 Receive 콜백에 패킷이 0개, 1개, 여러 개, 혹은 "패킷의 일부"만
// 도착할 수 있다(TCP 는 스트림이라 메시지 경계가 없다).
//
// 요구사항:
// - 부분 수신/복수 수신을 모두 올바르게 처리해야 한다.
// - 처리량이 중요하므로 불필요한 복사는 피하고 싶다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 발생하는지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰한 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <cstring>
class PacketSession {
public:
// 소켓 Receive 콜백이 호출하는 진입점.
// data[0..len) 에 이번에 도착한 바이트가 들어있다.
void OnReceive(const uint8_t* data, int len)
{
// (A) 도착한 바이트를 누적 버퍼 뒤에 이어붙인다.
std::memcpy(_buffer + _writePos, data, len);
_writePos += len;
int readPos = 0;
// 버퍼에 완성된 패킷이 있는 동안 계속 잘라낸다.
while (true)
{
// 길이 헤더(2바이트)가 아직 안 들어왔으면 대기
if (_writePos - readPos < 2)
break;
// (B) payload 길이 읽기 (네트워크 바이트오더 가정)
uint16_t payloadLen = *reinterpret_cast<const uint16_t*>(_buffer + readPos);
// (C) payload 전체가 도착했는지 확인
if (_writePos - readPos < 2 + payloadLen)
break;
// payload 구간을 잘라 핸들러로 전달
int payloadStart = readPos + 2;
OnPacket(_buffer + payloadStart, payloadLen);
// 다음 패킷으로 이동
readPos += 2 + payloadLen;
}
// (D) 처리하고 남은(미완성) 바이트를 버퍼 앞으로 당긴다.
int remain = _writePos - readPos;
std::memcpy(_buffer, _buffer + readPos, remain);
_writePos = remain;
}
private:
// 누적 버퍼와 현재 쌓인 바이트 수
uint8_t _buffer[4096];
int _writePos = 0;
void OnPacket(const uint8_t* /*buffer*/, int /*count*/)
{
// 패킷 디스패치 (생략)
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.