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#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.