로그인게임하기
현장 강의록

[현장 강의록 5] 소켓과 JSON 으로 만드는 채팅 프로그램

2026.10.11

현장 강의록 · 애니메이션고 2022 · 5편

4편에서 약속을 배웠다면 이번에는 실제로 말을 주고받을 차례입니다. IP 와 포트로 소켓을 열고, 객체를 JSON 문자열로 바꿔 보내고, 보내는 쓰레드와 받는 쓰레드를 나눠 채팅 프로그램을 완성했습니다.

수업 정보

2022년 1학기 한국애니메이션고등학교 2학년 「게임 개발과 미들웨어」 5강 「네트워크 프로그래밍 #2」에서 가르친 내용입니다.

SOCKET

IP 주소와 포트

말뜻
IP 주소세 자리 수 네 개로 이루어진 네트워크 주소입니다. TCP 소켓으로 Listen 을 시작하면 컴퓨터의 IP 주소 위에서 소켓이 기다립니다
포트소켓마다 하나씩 붙이는 번호입니다. 1~65535 를 쓸 수 있고, 100번 아래는 운영체제나 표준이 예약한 번호가 많습니다. 같은 IP 에서 같은 포트를 동시에 두 번 Listen 할 수 없습니다(나가는 연결은 가능)
Inbound · Outbound밖에서 안으로 들어오는 트래픽과 안에서 밖으로 나가는 트래픽입니다. Outbound 는 같은 주소와 포트로 연결을 여러 개 만들 수 있습니다
방화벽운영체제의 방화벽은 Inbound · Outbound 규칙으로 드나드는 길을 지킵니다. Windows 와 리눅스는 Inbound 규칙을 열지 않으면 Listen 이 되지 않습니다

서버 실습에서 가장 자주 막히는 곳이 방화벽입니다. 코드가 맞는데 연결이 안 되면 먼저 Inbound 규칙을 확인합니다.

CONNECT

연결의 흐름

Listen · Connect · Accept 흐름클라이언트 A서버클라이언트 BListen: 연결을 기다림 (방화벽 Inbound 허용 필요)Connect: 서버 주소 · 포트로 연결Accept: 클라이언트 목록에 기록ConnectAccept패킷 (JSON) 보내기응답 받기
서버가 Listen 하고, 클라이언트가 Connect 하면 서버가 Accept 해 목록에 기록합니다. 그다음부터 패킷이 오갑니다.

서버는 연결을 기다리는 상태(Listen)로 시작합니다. 클라이언트가 서버 주소와 포트로 연결(Connect)하면 서버가 받아들이고(Accept) 그 클라이언트를 배열에 기록합니다. 서버가 "누구에게 보낼지" 를 알 수 있는 것은 이 목록 덕분입니다.

JSON

JSON, 객체를 글자로

4편에서는 BinaryWriter 로 직접 순서를 정해 직렬화했습니다. 이번에는 JSON 을 썼습니다. JSON 은 자바스크립트의 객체 표기법에서 나왔지만 거의 모든 언어가 라이브러리로 지원해서 사실상 표준처럼 씁니다. 객체는 JSON 문자열이 되고, JSON 문자열은 다시 객체가 됩니다.

JSON STRUCTURE5강 7쪽 예시
{
  "Title": "The Cuckoo's Calling",
  "Author": "Robert Galbraith",
  "Genre": "classic crime novel",
  "Detail": {
    "Publisher": "Little Brown",
    "Publication_Year": 2013,
    "ISBN-13": 9781408704004,
    "Language": "English",
    "Pages": 494
  },
  "Price": [
    { "type": "Hardcover", "price": 16.65 },
    { "type": "Kindle Edition", "price": 7.03 }
  ]
}
모양뜻예
이름: 값데이터는 이름과 값의 쌍입니다"Pages": 494
{ }객체. 안에 이름: 값 쌍이 들어갑니다"Detail": { … }
[ ]배열. 값 여러 개를 차례로 담습니다"Price": [ … ]
값의 종류string, number, object, array, true, false, null"English", 2013
실습: 객체를 JSON 으로, JSON 을 객체로
  1. foreach 로 불러온 캐릭터를 JSON 문자열로 바꿉니다.
  2. 그 문자열을 json.txt 파일에 저장합니다.
  3. json.txt 를 읽어 문자열을 얻습니다.
  4. Type 을 Deserialize 에 넘겨 Character 객체로 되돌립니다.
JSON 변환설명용 예시 — 위 실습의 답 (Newtonsoft.Json)
// 객체 → JSON → 파일
string json = JsonConvert.SerializeObject(character);
File.WriteAllText("json.txt", json);

// 파일 → JSON → 객체
string loaded = File.ReadAllText("json.txt");
Character restored = (Character)JsonConvert.DeserializeObject(loaded, typeof(Character));
SEND

보내고 받기

실습: Unity 에서 메시지 보내기
  1. System.Net 네임스페이스의 TcpClient 를 만들고 서버 주소 · 포트로 연결합니다.
  2. 연결로 네트워크 스트림을 열고 문자열을 보낸 뒤 스트림을 닫습니다.

Unity C# 에서도 TcpClient 는 NetworkStream 을 돌려줍니다.

메시지 보내기 · 받기설명용 예시 — 5강 10~12쪽 실습
// 클라이언트: 보내기
using (TcpClient client = new TcpClient("127.0.0.1", 9000))
using (NetworkStream stream = client.GetStream())
{
    byte[] body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(packet));
    stream.Write(body, 0, body.Length);
}

// 서버: 기다렸다가 받기
TcpListener listener = new TcpListener(IPAddress.Any, 9000);
listener.Start();                                   // Listen
TcpClient peer = listener.AcceptTcpClient();        // Accept (연결이 올 때까지 기다림)
using (StreamReader reader = new StreamReader(peer.GetStream(), Encoding.UTF8))
{
    string json = reader.ReadToEnd();
    Character received = JsonConvert.DeserializeObject<Character>(json);
}

실습 순서는 "서버가 Listen → 클라이언트가 Connect → 서버가 스트림을 기다림" 이었습니다. 캐릭터 객체를 JSON 으로 보내고, 서버에서 같은 캐릭터로 되돌아오는지 확인했습니다.

PROTOCOL

우리만의 약속

공용

Network Common

클라이언트와 서버가 같은 패킷 클래스를 써야 서로의 데이터를 이해합니다. 그래서 패킷을 정의하는 공용 라이브러리를 둡니다. 대부분의 클래스가 Serializable 입니다.

경계

Transfer Protocol

스트림은 끝이 없는 물줄기라서 어디까지가 패킷 하나인지 약속해야 합니다. 머리(Header)에 몸통(Body)의 크기를 적어 보내는 방법을 주로 씁니다.

HeaderBody
몸통의 길이 (예: 4바이트 정수)패킷 JSON 문자열

받는 쪽은 먼저 머리를 읽어 길이를 알고, 그 길이만큼 몸통을 읽습니다. 이 약속이 없으면 두 패킷이 붙어 오거나 패킷 하나가 둘로 나뉘어 올 때 구별할 수 없습니다.

THREAD

보내는 쓰레드와 받는 쓰레드

가운데 우체국에서 한 직원은 편지를 계속 내보내고 다른 직원은 계속 받아 정리하며, 양쪽 방의 로라와 에드윈이 마법 거울로 대화하는 그림
보내는 일과 받는 일을 쓰레드 둘이 나눠 맡습니다.

보내는 쓰레드

  • while(true) 로 계속 기다립니다
  • 보낼 패킷이 들어오면 JSON 으로 바꿉니다
  • 그 즉시 서버로 보냅니다

받는 쓰레드

  • while(true) 로 계속 기다립니다
  • 패킷이 오면 끝까지 읽습니다
  • 읽은 문자열을 JSON 으로 보고 원래 객체로 되돌립니다
MessageLoop설명용 예시 — 5강 15쪽
BlockingCollection<GamePacket> sendQueue = new BlockingCollection<GamePacket>();

void SendLoop()                       // 보내는 쓰레드
{
    while (true)
    {
        GamePacket packet = sendQueue.Take();          // 들어올 때까지 기다림
        WritePacket(stream, JsonConvert.SerializeObject(packet));
    }
}

void ReceiveLoop()                    // 받는 쓰레드
{
    while (true)
    {
        string json = ReadPacket(stream);              // Header 로 길이를 읽고 Body 를 읽음
        GamePacket packet = Deserialize(json);
        Dispatch(packet);
    }
}

게임 화면을 그리는 메인 쓰레드는 이 두 쓰레드를 기다리지 않습니다. 보낼 것은 큐에 넣고 기다리지 않고 돌아오고, 받은 것은 큐에서 꺼내 처리합니다.

DELEGATE

메시지 표

받은 패킷을 무엇으로 처리할지는 메시지 표로 정합니다. C# 의 delegate 는 함수를 담을 수 있는 타입입니다(Unity 는 더 쓰기 쉬운 Action 을 줍니다). 패킷 종류마다 처리할 함수를 미리 등록해 두고, 받은 패킷의 종류로 함수를 찾아 부릅니다.

메시지 표설명용 예시 — 5강 16쪽
Dictionary<PacketTypes, Action<GamePacket>> messageTable = new Dictionary<PacketTypes, Action<GamePacket>>();

void Init()
{
    messageTable[PacketTypes.SendChat] = OnChat;
    messageTable[PacketTypes.SendLogin_Response] = OnLoginResult;
}

void Dispatch(GamePacket packet)        // 받는 쓰레드가 부른다
{
    if (messageTable.TryGetValue(packet.Type, out Action<GamePacket> handler))
        handler(packet);
}

void OnChat(GamePacket packet)
{
    var chat = (GamePacket_BroadcastChat)packet;
    chatLog.text += $"\n{chat.Nickname}: {chat.Message}";
}

새 패킷이 생기면 표에 줄을 더하고 함수를 만들면 됩니다. 받는 코드는 고치지 않습니다.

CHAT

채팅 프로그램 완성

  1. 네트워크 클래스보내기 · 받기 쓰레드를 가진 클래스를 Sources 에 넣음
  2. 채팅 UI여러 줄 Text, 한 줄 입력, 전송 버튼
  3. 연결Text 두 개를 GameScene 멤버로, 버튼 이벤트를 GameScene 함수로
  4. 전송입력한 글을 SendChat 패킷으로
  5. 표시BroadcastChat 을 받아 여러 줄 Text 에 쌓음

수업에서는 제가 직접 만든 네트워크 전송 클래스를 나눠 주고, 학생들은 그 위에 채팅 화면을 붙였습니다. 두 대의 컴퓨터에서 서로의 메시지가 뜨는 순간 교실이 시끄러워졌습니다.

응용흐름
결투장 랭킹클라이언트가 랭킹을 요청하면 서버가 결투장 DB 를 조회해 List.Sort() 로 정렬하고 1~50위를 보냅니다
캐릭터 생성클라이언트가 이름과 기본 능력치를 담은 패킷을 보내면 서버가 DB 에 저장하고, 다음 로그인 때 그 캐릭터 정보를 보냅니다
NOW

지금 우리 서버에서는

패킷 종류로 처리 함수를 찾는 메시지 표는 우리 C# 서버 라이브러리 ElServer 에도 그대로 있습니다(ElServer 리뷰).

정리

  1. Listen, Connect, Accept. 서버가 기다리고, 클라이언트가 찾아오고, 서버가 받아 목록에 적습니다.
  2. JSON 과 길이 머리. 객체는 JSON 으로, 패킷의 경계는 Header 의 길이로 약속합니다.
  3. 보내기와 받기는 쓰레드를 나누고, 처리는 메시지 표로.

다음 편부터는 3D 게임 프로그래밍 수업입니다. 코드보다 먼저 그리는 설계, 순서도부터 시작합니다.

← 개발 노트 목록