14. 길드/전체 채팅 브로드캐스트 중 수신자 목록 변경
난이도 하 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 길드/전체 채팅 브로드캐스트 중 수신자 목록 변경
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어가 길드 채팅을 보내면 서버는 같은 길드의 모든 접속 세션에게
// S_Chat 패킷을 브로드캐스트한다.
// - 브로드캐스트가 도는 동안에도 길드원은 수시로 접속/종료하고, 길드를
// 탈퇴/가입하며, 일부 세션은 연결이 끊겨 정리(remove)되는 중일 수 있다.
// - 즉, "수신자 목록을 순회하며 보내는 도중"에 그 목록과 각 세션의 상태가
// 다른 스레드에 의해 동시에 바뀔 수 있다.
// - 채팅은 트래픽이 많아 브로드캐스트가 빈번하게(초당 수십~수백 회) 일어난다.
//
// 요구사항:
// - 브로드캐스트 도중 수신자 목록이 바뀌어도 서버가 예외로 죽지 않아야 한다.
// - 막 끊긴/정리된 세션에 송신해 자원 손상이나 예외가 나면 안 된다.
// - 한 수신자의 송신 실패가 다른 수신자에게로의 전파를 막으면 안 된다.
// - 브로드캐스트가 채팅 송신 처리량/지연에 병목이 되면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 깨지는지(동시 인터리빙 포함)
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
using System.Net.Sockets;
public class Session
{
public long PlayerId;
public Socket Socket;
public bool Connected = true;
// 막 끊긴 세션은 다른 스레드가 Connected=false 로 바꾸고 Socket 을 Dispose 한다.
public void Send(byte[] packet)
{
// 단순화: 동기 송신
Socket.Send(packet);
}
}
public class Guild
{
public int Id;
// 길드원 세션 목록. 가입/탈퇴/접속/종료 시 다른 스레드가 Add/Remove 한다.
public List<Session> Members = new List<Session>();
}
public class ChatService
{
private readonly Dictionary<int, Guild> _guilds;
public ChatService(Dictionary<int, Guild> guilds)
{
_guilds = guilds;
}
// C_GuildChat 처리: 길드원 전원에게 브로드캐스트
public void BroadcastGuildChat(int guildId, long fromPlayerId, string text)
{
Guild g = _guilds[guildId];
byte[] packet = BuildChatPacket(fromPlayerId, text);
// (A) 수신자 목록 순회하며 송신
foreach (Session s in g.Members)
{
// (B) 세션 상태/소켓 사용
if (s.Connected)
s.Send(packet);
}
}
private byte[] BuildChatPacket(long fromPlayerId, string text)
{
// 패킷 직렬화 (생략)
return Array.Empty<byte>();
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 길드/전체 채팅 브로드캐스트 중 수신자 목록 변경
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어가 길드 채팅을 보내면 서버는 같은 길드의 모든 접속 세션에게
// S_Chat 패킷을 브로드캐스트한다.
// - 브로드캐스트가 도는 동안에도 길드원은 수시로 접속/종료하고, 길드를
// 탈퇴/가입하며, 끊긴 세션은 다른 스레드가 목록에서 제거하고 객체를 파괴한다.
// - 즉, "수신자 목록을 순회하며 보내는 도중"에 그 목록과 각 세션 객체가
// 다른 스레드에 의해 동시에 바뀌거나 해제될 수 있다.
// - 채팅은 트래픽이 많아 브로드캐스트가 빈번하게 일어난다.
//
// 요구사항:
// - 브로드캐스트 도중 목록이 바뀌어도 순회가 깨지거나 해제된 세션을 건드리면 안 된다.
// - 한 수신자의 송신 실패/지연이 다른 수신자에게로의 전파를 막으면 안 된다.
// - 브로드캐스트가 채팅 처리량/지연에 병목이 되면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 깨지는지(경합 인터리빙·수명 포함)
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <vector>
#include <string>
#include <atomic>
struct Packet { const uint8_t* data; size_t len; };
class Session {
public:
int64_t playerId;
std::atomic<bool> connected{true};
// 끊긴 세션은 다른 스레드가 이 객체를 delete 한다.
void Send(const Packet& p) {
// 단순화: 동기 블로킹 송신 (소켓 write)
rawWrite(p.data, p.len);
}
private:
void rawWrite(const uint8_t*, size_t) { /* ::send (생략) */ }
};
class Guild {
public:
int id;
// 길드원 세션 포인터 목록. 가입/탈퇴/접속/종료 시 다른 스레드가 push/erase 하고
// 종료 시 Session 을 delete 한다.
std::vector<Session*> members;
};
class ChatService {
public:
// C_GuildChat 처리: 길드원 전원에게 브로드캐스트
void BroadcastGuildChat(Guild& g, int64_t fromPlayerId, const std::string& text) {
Packet pkt = BuildChatPacket(fromPlayerId, text);
// (A) 수신자 목록 순회하며 송신
for (Session* s : g.members) {
// (B) 세션 상태/객체 사용
if (s->connected.load())
s->Send(pkt);
}
}
private:
Packet BuildChatPacket(int64_t, const std::string&) {
return Packet{nullptr, 0};
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.