서비스
교육 철학

Network Manager (네트워크 매니저)

Abstract

📌
Network Manager 는 TCP 연결 하나를 채널이라는 단위로 감싸, "끊기지 않고 이어지는 바이트의 흐름"을 패킷 단위로 잘라 핸들러에 배달해 줍니다. 프레임워크가 정하지 않는 것은 단 하나 — 패킷이 어떻게 생겼는가입니다. 그 부분을 INetworkChannelHelper 라는 인터페이스로 비워 두었고, 그것을 채워야 채널을 만들 수 있습니다. 이 페이지의 본문은 구조를 설명하고, 이 Abstract 는 실제로 동작하는 가장 작은 헬퍼를 보여 줍니다.
TCP 에서 "패킷"은 직접 만들어야 한다
TCP 는 보낸 순서대로 도착한다는 것만 보장하고, 어디서 끊어 읽을지는 알려 주지 않습니다. "안녕"과 "하세요"를 따로 보내도 받는 쪽에는 "안녕하세요"가 한 덩어리로 오거나 "안"과 "녕하세요"로 쪼개져 올 수 있습니다. 그래서 모든 TCP 프로토콜은 메시지 앞에 "이 뒤로 몇 바이트가 한 메시지다"라는 머리말(헤더)을 붙입니다.
프레임워크는 이 절차를 이렇게 나눠 맡습니다.
단계
프레임워크
헬퍼 (내가 구현)
① 헤더 받기
PacketHeaderLength 바이트가 모일 때까지 기다린다
헤더가 몇 바이트인지 알려 준다
② 헤더 해석
DeserializePacketHeader — 본문 길이를 읽어 돌려준다
③ 본문 받기
그 길이만큼 모일 때까지 기다린다
④ 본문 해석
DeserializePacket — 바이트를 패킷 객체로 만든다
⑤ 배달
패킷의 Id 에 맞는 핸들러를 메인 스레드에서 부른다
직접 해 보기 — 가장 작은 헬퍼
헤더는 8바이트(본문 길이 4 + 패킷 Id 4)로 정합니다.
① 헤더와 패킷
using System.IO; using GameFramework; using GameFramework.Network; using UnityGameFramework.Runtime; /// <summary>헤더 8바이트: [본문 길이 int32][패킷 Id int32]</summary> public sealed class SimplePacketHeader : IPacketHeader, IReference { public int PacketLength { get; set; } public int PacketId { get; set; } public void Clear() { PacketLength = 0; PacketId = 0; } } public abstract class SimplePacket : Packet { public abstract void Write(BinaryWriter writer); public abstract void Read(BinaryReader reader); } public sealed class ChatPacket : SimplePacket { public const int PacketId = 1; public override int Id => PacketId; public string Text { get; set; } public override void Write(BinaryWriter writer) { writer.Write(Text ?? string.Empty); } public override void Read(BinaryReader reader) { Text = reader.ReadString(); } public override void Clear() { Text = null; } }
C#
복사
② 핸들러 — 받은 패킷으로 무엇을 할지.
public sealed class ChatPacketHandler : IPacketHandler { public int Id => ChatPacket.PacketId; public void Handle(object sender, Packet packet) { ChatPacket chat = (ChatPacket)packet; Log.Info("받음: {0}", chat.Text); } }
C#
복사
③ 채널 헬퍼
public sealed class SimpleChannelHelper : INetworkChannelHelper { private const int HeaderLength = 8; public int PacketHeaderLength => HeaderLength; public void Initialize(INetworkChannel networkChannel) { networkChannel.RegisterHandler(new ChatPacketHandler()); } public void Shutdown() { } public void PrepareForConnecting() { } // 이 예제는 하트비트를 보내지 않는다. false 를 돌려주면 누락 통지도 나가지 않는다 public bool SendHeartBeat() { return false; } public bool Serialize<T>(T packet, Stream destination) where T : Packet { SimplePacket simple = packet as SimplePacket; if (simple == null) return false; long start = destination.Position; BinaryWriter writer = new BinaryWriter(destination); writer.Write(0); // 본문 길이 자리. 본문을 쓴 뒤 채운다 writer.Write(packet.Id); simple.Write(writer); writer.Flush(); long end = destination.Position; int bodyLength = (int)(end - start) - HeaderLength; destination.Position = start; writer.Write(bodyLength); writer.Flush(); destination.Position = end; return true; } public IPacketHeader DeserializePacketHeader(Stream source, out object customErrorData) { customErrorData = null; BinaryReader reader = new BinaryReader(source); SimplePacketHeader header = ReferencePool.Acquire<SimplePacketHeader>(); header.PacketLength = reader.ReadInt32(); header.PacketId = reader.ReadInt32(); return header; } public Packet DeserializePacket(IPacketHeader packetHeader, Stream source, out object customErrorData) { customErrorData = null; SimplePacketHeader header = (SimplePacketHeader)packetHeader; SimplePacket packet = CreatePacket(header.PacketId); ReferencePool.Release(header); if (packet == null) return null; packet.Read(new BinaryReader(source)); return packet; } private static SimplePacket CreatePacket(int packetId) { switch (packetId) { case ChatPacket.PacketId: return ReferencePool.Acquire<ChatPacket>(); default: return null; } } }
C#
복사
④ 사용
NetworkComponent network = GameEntry.GetComponent<NetworkComponent>(); INetworkChannel channel = network.CreateNetworkChannel("Game", ServiceType.Tcp, new SimpleChannelHelper()); channel.Connect(System.Net.IPAddress.Parse("127.0.0.1"), 9000); // NetworkConnected 이벤트를 받은 뒤 ChatPacket packet = ReferencePool.Acquire<ChatPacket>(); packet.Text = "hello"; channel.Send(packet); // 보낸 뒤 패킷은 프레임워크가 풀로 돌려놓는다
C#
복사
💡
위 코드는 이 에디션에서 컴파일되는 것까지 확인했습니다. 실제 서버와 주고받는 시험은 하지 않았으므로, 서버 쪽 헤더 형식(바이트 순서 포함)과 맞추는 작업은 직접 해야 합니다. BinaryWriter 는 리틀 엔디언으로 씁니다.
실제 프로젝트로 키울 때
•
직렬화 라이브러리 붙이기. 패킷마다 Write / Read 를 손으로 쓰는 대신 Protobuf 나 MessagePack 을 씁니다. 바뀌는 곳은 Serialize 와 DeserializePacket 두 메서드뿐이고, 게임 코드의 channel.Send(packet) 는 그대로입니다
•
패킷 종류 등록 자동화. CreatePacket 의 switch 는 패킷이 수십 개가 되면 관리가 안 됩니다. 시작할 때 Id → 타입 딕셔너리를 만들어 ReferencePool.Acquire(type) 으로 꺼냅니다
•
하트비트. SendHeartBeat 에서 하트비트 패킷을 Send 하고 true 를 돌려줍니다. 서버 응답이 HeartBeatInterval(기본 30초) 안에 안 오면 NetworkMissHeartBeat 이벤트가 오고, 몇 번 연속이면 끊긴 것으로 보고 재접속합니다
•
재접속. 자동이 아닙니다. NetworkClosed / NetworkError 이벤트에서 직접 Connect 를 다시 부릅니다. 재접속 간격을 점점 늘리는(1초, 2초, 4초 …) 방식이 서버에 부담을 덜 줍니다
•
customErrorData. 헤더가 이상할 때(길이가 음수, 알 수 없는 Id) 이 out 인자에 무언가를 넣으면 NetworkCustomError 이벤트로 전달됩니다. 잘못된 서버나 버전 불일치를 잡아내는 용도입니다
⚠️
본문을 읽을 때 주의할 점
•
기본 제공 헬퍼가 없습니다. 위와 같은 클래스를 직접 만들어야 CreateNetworkChannel 을 부를 수 있습니다.
•
서비스 타입은 TCP 두 가지뿐입니다. Tcp 는 수신을 비동기 콜백으로, TcpWithSyncReceive 는 매 프레임 Update 에서 동기로 읽습니다. 보통 Tcp 를 쓰고, 콜백 스레드를 쓸 수 없는 플랫폼에서 후자를 씁니다.
•
핸들러는 메인 스레드에서 불립니다. 받은 패킷은 내부 이벤트 풀에 쌓였다가 채널의 Update 에서 배달되므로, 핸들러 안에서 Unity API 를 써도 됩니다.
•
패킷 객체는 핸들러가 끝나면 풀로 돌아갑니다. 핸들러 밖에서 쓸 값은 복사해 두세요.
•
게임 코드는 본문의 NetworkManager 대신 GameEntry.GetComponent<NetworkComponent>() 를 쓰고, 이벤트는 EventComponent 로 NetworkConnectedEventArgs 등을 구독합니다.

Network Manager (네트워크 매니저)

관련 소스 파일

•
GameFramework/Network/INetworkChannel.cs
•
GameFramework/Network/INetworkChannelHelper.cs
•
GameFramework/Network/INetworkManager.cs
•
GameFramework/Network/IPacketHeader.cs
•
GameFramework/Network/NetworkManager.cs

목적 및 개요

Network Manager는 Game Framework의 핵심 컴포넌트로, 네트워크 통신을 처리합니다.
네트워크 채널 생성 및 관리, 연결 수립, 패킷 송수신, 네트워크 이벤트 처리를 담당합니다.
•
채널 기반 아키텍처 제공
•
TCP 연결 지원
•
하트비트(heartbeat) 메커니즘을 통한 연결 유지
※ 인터넷 파일 다운로드 관련 내용은 Download Manager 문서를 참고하세요.

시스템 아키텍처

Network Manager는 GameFramework의 모듈 아키텍처 패턴을 따릅니다.
•
GameFrameworkModule을 상속
•
INetworkManager 인터페이스 구현
여러 네트워크 채널을 관리하며, 각 채널은 원격 호스트와의 연결을 나타냅니다.

Network Manager 구성 요소

•
NetworkManager: 네트워크 채널 관리 및 네트워크 이벤트 제공
•
NetworkChannel: 원격 호스트와의 연결, 패킷 송수신 처리
•
NetworkChannelHelper: 패킷 직렬화/역직렬화, 기타 보조 작업 처리
•
Packet: 모든 네트워크 메시지 패킷의 기본 클래스
•
PacketHeader: 패킷 길이 등 패킷 정보를 담는 헤더

네트워크 채널 생성 및 관리

채널 생성

CreateNetworkChannel 메서드를 사용하여 네트워크 채널을 생성합니다.
필요한 인자:
•
이름
•
서비스 타입
•
NetworkChannelHelper
지원 서비스 타입:
•
ServiceType.Tcp: 일반 TCP 연결
•
ServiceType.TcpWithSyncReceive: 동기식 수신을 지원하는 TCP 연결

채널 관리

NetworkManager는 채널을 관리하기 위한 다양한 메서드를 제공합니다:
•
HasNetworkChannel: 채널 존재 여부 확인
•
GetNetworkChannel: 특정 채널 반환
•
GetAllNetworkChannels: 모든 채널 반환
•
DestroyNetworkChannel: 채널 파괴
채널을 파괴하면 이벤트 구독 해제, 종료, 딕셔너리에서 제거가 자동 처리됩니다.

네트워크 채널 동작

원격 호스트 연결

•
Connect(IP, Port) 메서드 사용

패킷 전송

•
Send(Packet packet) 메서드 사용

패킷 수신

수신은 채널에서 자동 처리됩니다. 흐름:
1.
소켓에서 데이터 수신
2.
NetworkChannelHelper를 통해 패킷 헤더 역직렬화
3.
헤더 정보를 기반으로 패킷 본문 역직렬화
4.
등록된 핸들러 또는 기본 핸들러에서 패킷 처리

하트비트(Heartbeat) 메커니즘

연결 유지 및 모니터링을 위한 하트비트 기능을 지원합니다.
주요 속성 및 동작:
•
HeartBeatInterval: 하트비트 패킷 간격
•
MissHeartBeatCount: 누락된 하트비트 개수
•
ResetHeartBeatElapseSecondsWhenReceivePacket: 패킷 수신 시 하트비트 타이머 리셋 여부

네트워크 이벤트 처리

Network Manager는 다양한 이벤트를 제공합니다:
이벤트
설명
인자
NetworkConnected
연결이 수립될 때
NetworkChannel, UserData
NetworkClosed
연결이 종료될 때
NetworkChannel
NetworkMissHeartBeat
하트비트가 누락될 때
NetworkChannel, MissHeartBeatCount
NetworkError
네트워크 오류 발생
NetworkChannel, ErrorCode, SocketErrorCode, ErrorMessage
NetworkCustomError
커스텀 오류 발생
NetworkChannel, CustomErrorData

Network Channel Helper

INetworkChannelHelper 인터페이스는 채널을 돕는 역할을 합니다:
•
Packet Serialization: 패킷 → 바이너리 변환
•
Packet Deserialization: 바이너리 → 패킷 변환
•
Heartbeat Management: 하트비트 패킷 송신
•
Connection Preparation: 신규 연결 준비
개발자는 자신만의 패킷 직렬화/역직렬화 로직을 구현해야 합니다.

패킷 시스템

네트워크 통신의 핵심 구성 요소:
•
Packet: 모든 패킷의 기본 클래스
•
PacketHeader: 패킷 길이 정보를 제공하는 인터페이스
→ 개발자는 Packet을 상속하여 패킷 타입 정의, PacketHeader 구현을 통해 PacketLength 속성 제공 필요

Game Framework과의 통합

•
Update(): 매 프레임 호출 → 모든 네트워크 채널 업데이트
•
Shutdown(): 프레임워크 종료 시 호출 → 모든 네트워크 채널 종료
게임 실행 사이클과 네트워크 동작을 동기화합니다.

사용 예시

네트워크 매니저 사용 흐름 예시:
1.
커스텀 NetworkChannelHelper 구현
2.
NetworkManager를 통해 네트워크 채널 생성
3.
패킷 핸들러 등록
4.
원격 호스트에 연결
5.
패킷 송수신
6.
네트워크 이벤트 처리
7.
필요 시 연결 종료
→ 유연하고 안정적인 네트워크 통신 시스템을 구현할 수 있습니다.