46. 세션 송신 펌프의 동시 진입 (같은 소켓 중첩 SendAsync·프레임 오염, C#)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 세션 비동기 송신 펌프 (C# 게임서버)
// - 각 세션은 송신 큐를 가진다. 여러 게임플레이 스레드가 Enqueue 로 패킷을 넣는다.
// - 소켓 쓰기는 한 번에 하나의 SendAsync 만 진행돼야 한다(겹치면 안 됨).
// 그래서 "지금 송신 중인가"를 _sending 플래그로 표시하고, 송신 중이 아니면
// 펌프를 깨워(kick) 큐를 비운다. 송신 완료 콜백도 다음 청크를 이어 보내려고
// 같은 경로(TrySend)를 다시 호출한다.
//
// 요구사항: 같은 소켓에 대해 SendAsync 가 동시에 두 번 진행되지 않아야 한다.
//
// 과제: 두 스레드가 거의 동시에 Enqueue 하거나, Enqueue 와 완료 콜백이 겹치면
// 같은 소켓에 SendAsync 가 중첩 실행돼 바이트가 뒤섞이고 프레임이 깨진다.
// (A),(B) 를 근거로 원인·수정안을 설명하라.
using System;
using System.Collections.Concurrent;
using System.Net.Sockets;
public sealed class Session
{
private readonly Socket _socket;
private readonly ConcurrentQueue<byte[]> _sendQueue = new();
private bool _sending;
public Session(Socket s) { _socket = s; }
public void Enqueue(byte[] packet)
{
_sendQueue.Enqueue(packet);
TrySend();
}
private void TrySend()
{
if (_sending) // (A)
return;
if (!_sendQueue.TryDequeue(out var packet))
return;
_sending = true; // (B)
BeginSend(packet);
}
private void BeginSend(byte[] packet)
{
var args = new SocketAsyncEventArgs();
args.SetBuffer(packet, 0, packet.Length);
args.Completed += OnSendCompleted;
if (!_socket.SendAsync(args))
OnSendCompleted(this, args);
}
private void OnSendCompleted(object? sender, SocketAsyncEventArgs args)
{
_sending = false;
TrySend(); // 다음 청크 이어 보내기
}
} 결함 코드 · C++
// 시나리오: 세션 비동기 송신 펌프 (C++ 게임서버, IOCP/asio 류를 단순화)
// - 각 세션은 송신 큐를 가진다. 여러 워커 스레드가 Enqueue 로 패킷을 넣는다.
// - 소켓 쓰기는 한 번에 하나의 비동기 쓰기(DoSend)만 진행돼야 한다.
// "지금 송신 중"을 sending_ 플래그로 표시하고, 아니면 펌프를 깨운다.
// 쓰기 완료 콜백(OnSendComplete)도 다음 청크를 위해 TrySend 를 다시 호출한다.
//
// 요구사항: 같은 소켓에 비동기 쓰기가 동시에 두 번 진행되면 안 된다.
//
// 과제: 두 스레드의 Enqueue 가 겹치거나 Enqueue 와 완료 콜백이 겹치면 같은
// 소켓에 쓰기가 중첩돼 프레임이 뒤섞인다. (A),(B) 를 근거로 설명하라.
// (컴파일 가능해야 함)
#include <cstdint>
#include <deque>
#include <vector>
#include <cstdio>
using Packet = std::vector<uint8_t>;
class Session {
std::deque<Packet> send_queue_;
bool sending_ = false;
void DoSend(const Packet& /*p*/) {
// 실제로는 여기서 socket 에 비동기 쓰기를 건다(IOCP WSASend 등).
// 완료되면 OnSendComplete 가 (다른 스레드에서) 호출된다.
}
public:
void Enqueue(Packet p) {
send_queue_.push_back(std::move(p));
TrySend();
}
void TrySend() {
if (sending_) // (A)
return;
if (send_queue_.empty())
return;
sending_ = true; // (B)
Packet p = std::move(send_queue_.front());
send_queue_.pop_front();
DoSend(p);
}
void OnSendComplete() {
sending_ = false;
TrySend();
}
};
int main() {
Session s;
s.Enqueue(Packet{1, 2, 3});
s.OnSendComplete();
std::printf("ok\n");
return 0;
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.