Bunch부터 TickFlush까지 — Actor의 값은 어떻게 네트워크로 전달되는가
앞선 글에서 Unreal은 UNetConnection을 통해 특정 상대방과의 논리적인 연결을 관리한다고 설명했다.
그렇다면 실제로 Actor의 값이나 RPC 같은 데이터는 이 연결을 통해 어떻게 전달될까?
이때 등장하는 개념이
Channel → Bunch → Packet
이다.
1. Channel은 연결 안의 논리적인 통로다
하나의 UNetConnection 안에는 여러 개의 UChannel이 존재할 수 있다.
UNetConnection
│
├─ UControlChannel
│
├─ UActorChannel
│
├─ UActorChannel
│
└─ ...
Channel은 네트워크 연결 자체가 아니다.
UNetConnection
→ 상대방과의 연결
UChannel
→ 그 연결 안에서 특정 네트워크 데이터를 처리하는 논리적인 통로
2. 대표적인 Channel
대표적으로 다음과 같은 Channel이 있다.
UControlChannel
연결 제어 메시지를 처리한다.
공식 API에서도 UControlChannel을 "A channel for exchanging connection control messages"라고 설명한다.
UNetConnection
↓
UControlChannel
↓
Control Message
UActorChannel
Actor와 Subobject의 네트워크 데이터를 처리한다.
공식 API에서는 UActorChannel을 Actor 및 Subobject의 properties와 RPC를 교환하는 Channel이라고 설명한다. 또한 Actor Channel 내부의 실제 Property/RPC replication은 FObjectReplicator가 담당한다고 설명한다.
UNetConnection
↓
UActorChannel
↓
Actor / Subobject
3. UControlChannel이 연결을 만드는 것은 아니다
이 부분은 자주 혼동된다.
UNetConnection
↓
UControlChannel
순서다.
즉,
UControlChannel
→ 연결을 만드는 객체
가 아니다.
정확하게는
UNetConnection
→ 연결 관리
UControlChannel
→ 그 연결 위에서 Control Message 처리
라고 이해해야 한다.
4. FNetworkNotify와 Control Message
Control Channel을 통해 들어온 메시지는 World 등의 네트워크 알림 인터페이스와 연결된다.
FNetworkNotify에는 대표적으로 다음 네 가지 함수가 있다.
NotifyAcceptingConnection()
NotifyAcceptedConnection()
NotifyAcceptingChannel()
NotifyControlMessage()
각각의 역할을 단순화하면:
NotifyAcceptingConnection()
→ 연결 요청을 받아들일 것인가?
NotifyAcceptedConnection()
→ 연결이 만들어졌음을 알림
NotifyAcceptingChannel()
→ 새로운 Channel을 열어도 되는가?
NotifyControlMessage()
→ Control Message를 처리/알림
공식 문서에서도 NotifyAcceptingChannel()은 원격 요청으로 새로운 Channel이 생성될 때 이를 받아들일지 결정하는 notification이라고 설명한다.
따라서 FNetworkNotify는
네트워크 시스템과 World/PendingNetGame 등의 상위 객체를 연결하는 notification interface
라고 이해하는 것이 가장 정확하다.
접속 허용 여부는 게임 규칙에 맞게 결정 해야 하는데. 이 판단을 네트워크에 둘것이냐, 아니면 게임 코드에 둘것이냐를 언리얼은 게임 코드에 결정하는 쪽을 사용하고 이 판단의 과정을 게임 코드에서 FNetworkNotify라고 하는 4개의 함수로 이루어진 인터페이스를 통해 처리한다.
5. Bunch란 무엇인가?
Channel을 이해했다면 이제 Bunch를 볼 차례다.
쉽게 말하면 Bunch는 Unreal의 네트워크 프로토콜에서 Channel을 통해 전달되는 데이터 단위다.
개념적으로:
UActorChannel
↓
FOutBunch
↓
UNetConnection
↓
Packet
↓
UDP
반대로 수신 측에서는:
UDP
↓
Packet
↓
Bunch
↓
ChIndex
↓
Channel
↓
데이터 처리
하나의 Packet에는 여러 개의 Bunch가 포함될 수 있다.
UDP Packet
├─ Bunch A
├─ Bunch B
└─ Bunch C
각 Bunch는 어떤 Channel에 속하는지 식별할 수 있는 정보와 함께 신뢰성, Open/Close 등의 프로토콜 정보를 가진다.
6. ChIndex는 왜 필요한가?
Bunch에는 Channel을 식별하기 위한 Channel Index가 있다.
Bunch
├─ ChIndex
├─ Reliable
├─ Open / Close
└─ Payload
수신 측에서는 이 값을 사용해 해당 Channel을 찾는다.
개념적으로:
ChIndex = 5
↓
Channels[5]
↓
해당 Channel
Channel을 매번 별도의 ID 검색 구조로 찾는 대신 배열 인덱스를 이용하면 빠르게 접근할 수 있다.
Channels[ChIndex]
그리고 해당 슬롯에 Channel 객체가 존재하는지 여부도 바로 확인할 수 있다.
7. Open / Close는 Connection의 종료가 아니다
Bunch에서 Open이나 Close가 등장한다고 해서 Connection 자체가 열리고 닫히는 것은 아니다.
여기서의 의미는 Channel의 상태다.
Connection
│
├─ Channel A
│
└─ Channel B
Channel Bunch의 Close:
Channel B 종료
Connection의 Close:
전체 Connection 종료
서로 완전히 다른 개념이다.
8. Actor가 변경됐다고 즉시 Send되는 것은 아니다
이제 Replication의 핵심으로 들어간다.
게임 코드에서:
Health = 80;
이라고 했다고 해서 그 순간 바로 UDP 패킷을 보내는 것은 아니다.
Unreal은 네트워크 Tick 흐름에서 복제할 데이터를 계산하고 전송한다.
개념적으로:
게임 코드
↓
Actor 상태 변경
↓
다음 네트워크 처리 시점
↓
Replication 대상 판단
↓
Actor Channel
↓
Bunch 생성
↓
Connection
↓
Packet
↓
UDP
9. 서버의 TickFlush
서버에서 중요한 지점이 TickFlush다.
UWorld Tick
↓
게임 로직 처리
↓
TickFlush
↓
NetDriver
↓
ServerReplicateActors()
언리얼은 프레임 끝의 TickFlush에서 이번 프레임에 무엇을 복제할지를 모아서 네트워크 데이터로 만든다.
UWorld의 프레임이 끝나면 NetDriver의 TickFlush가 실행되고 서버에서는 ServerReplicateActors()가 복제할 엑터를 고르고 UActorChannel의 SendBunch가 번치를 커넥션에 넘긴다.
공식 API에서는 ServerReplicateActors() 를 NetDriver에 포함된 Connection으로 관련 Actor를 복제하기 위해 호출되는 함수라고 설명한다.
따라서 개념적으로:
ServerReplicateActors()
↓
복제 대상 Actor 판단
↓
각 Connection의 상태 확인
↓
UActorChannel
↓
네트워크 데이터 생성
↓
FOutBunch
↓
Connection
↓
Packet
이라고 볼 수 있다.
단, ServerReplicateActors()가 단순히 "변경된 Actor만 찾아서 보낸다"고 이해하면 안 된다.
실제 Replication에는 relevancy, update frequency, channel 상태, NetUpdate 관련 정책 등 여러 조건이 관여한다.
10. Bunch와 Packet은 같은 것이 아니다
이 부분은 반드시 구분해야 한다.
Bunch
≠
Packet
송신 방향에서는:
Actor
↓
UActorChannel
↓
FOutBunch
↓
UNetConnection
↓
Packet
↓
UDP
하나의 Packet에 여러 Bunch가 들어갈 수도 있다.
Packet
├─ Bunch
├─ Bunch
└─ Bunch
따라서 Bunch를
"UDP Packet 그 자체"
라고 표현하면 안 된다.
Bunch는 Unreal의 네트워크 프로토콜 계층에서 사용되는 데이터 단위이고, Packet은 실제 전송되는 네트워크 패킷이다.
11. 전체 Replication 흐름
이제 전체 과정을 연결해보자.
[게임 로직]
Actor 값 변경
↓
Health = 80
↓
[World Tick]
↓
[NetDriver TickFlush]
↓
ServerReplicateActors()
↓
복제 대상 / Connection 판단
↓
UActorChannel
↓
FObjectReplicator 등을 통한 데이터 직렬화
↓
FOutBunch
↓
UNetConnection
↓
Packet 구성
↓
UDP Socket
↓
Client
여기서 최신 UE에서는 Actor Channel이 Actor의 생성과 lifetime을 관리하는 반면 실제 Property/RPC replication은 FObjectReplicator가 담당한다는 점도 기억해두면 좋다.
12. Tick Rate와 Network Speed는 다르다
게임 네트워크 성능을 이해할 때 반드시 구분해야 하는 것이 있다.
Tick Rate
vs
Network Speed
Tick Rate
NetServerMaxTickRate
서버가 초당 몇 번 Tick할 수 있는지를 제한한다.
예를 들어:
NetServerMaxTickRate = 30
이면 Dedicated Server에서 엔진 Tick Rate를 최대 30 ticks/sec 수준으로 제한하는 의미다.
공식 API에서도 NetServerMaxTickRate를 Dedicated Server에서 엔진 Tick Rate를 제한하는 설정으로 설명한다.
13. Tick이 30이라고 Packet도 30개는 아니다
이 둘은 1:1 관계가 아니다.
30 Tick/sec
이라고 해서
30 Packet/sec
이라는 의미는 아니다.
한 Tick에서:
Packet 0개
일 수도 있고,
Packet 여러 개
가 나갈 수도 있다.
즉,
Tick Rate
→ 얼마나 자주 게임/네트워크 처리를 수행하는가
Packet Rate
→ 실제로 몇 개의 Packet이 전송되는가
는 서로 다른 개념이다.
14. CurrentNetSpeed
이번에는 데이터의 양을 제한하는 값이다.
CurrentNetSpeed는 현재 Connection에 적용된 네트워크 전송률을 나타내며 일반적으로 bytes/sec 단위로 다룬다.
예를 들어:
CurrentNetSpeed = 50,000
이라면 대략적으로
초당 50,000 bytes 수준
의 전송률을 허용하는 설정으로 이해할 수 있다.
하지만 이것이
"매초 반드시 50,000 bytes를 전송한다"
는 의미는 아니다.
실제로 보낼 데이터가 없다면 더 적게 보낸다.
허용 전송률
↓
50,000 bytes/sec
실제 전송량
↓
보낼 데이터에 따라
0 ~ 50,000 수준
Epic의 공식 지원 답변도 관련 bandwidth 설정값을 bytes/sec로 설명하고, 실제 per-frame bandwidth limit은 frame rate의 영향을 받는다고 설명한다.
15. MaxClientRate와 MaxInternetClientRate
UNetDriver에는 다음과 같은 설정이 있다.
MaxClientRate
MaxInternetClientRate
개념적으로는 클라이언트에 적용할 수 있는 전송률의 상한을 설정하는 값이다.
Epic 개발자 설명에서는:
MaxClientRate
→ LAN에서 사용할 수 있는 최대 데이터 전송률
MaxInternetClientRate
→ Internet 연결에서 사용할 수 있는 최대 데이터 전송률
이라는 의미로 설명해 왔다.
즉 LAN과 Internet에 서로 다른 bandwidth 정책을 적용하기 위한 값이라고 이해할 수 있다.
16. 두 값을 사용하는 이유
예를 들어:
MaxClientRate=100000
MaxInternetClientRate=50000
이라면 Internet 환경에서는 Internet용 상한이 더 낮다.
따라서 Internet 환경에서 적용되는 최대 전송률은 50,000 수준을 넘지 않도록 제한된다.
반대로:
MaxClientRate=50000
MaxInternetClientRate=100000
이라면
MaxClientRate = 50,000
MaxInternetClientRate = 100,000
이므로 Internet 상한이 높다고 해서 일반 상한인 MaxClientRate를 100,000으로 올리는 것은 아니다.
즉 더 낮게 설정된 상한보다 높여서 사용할 수는 없다.
개념적으로 보면:
Internet 환경
MaxClientRate 50,000
MaxInternetClientRate 100,000
↓
실제로 허용 가능한 상한
↓
50,000
반대라면:
MaxClientRate 100,000
MaxInternetClientRate 50,000
↓
실제로 허용 가능한 상한
↓
50,000
Epic의 과거 네트워크 설명에서도 두 값이 LAN/Internet 환경별 최대 데이터 전송률을 제한하는 용도로 설명되며, 최신 Epic 지원 답변에서도 ConfiguredInternetSpeed, ConfiguredLanSpeed, MaxClientRate, MaxInternetClientRate가 bandwidth 제한에 관여한다고 설명한다.
17. Tick Rate와 Network Speed를 함께 보면
결국 두 값은 서로 다른 제한이다.
┌─────────────────────────────┐
│ Server Tick Rate
│ NetServerMaxTickRate
│
│ 얼마나 자주 처리하는가?
└─────────────────────────────┘
+
┌─────────────────────────────┐
│ Network Bandwidth
│ CurrentNetSpeed
│
│ 얼마나 많은 데이터를
│ 전송할 수 있는가?
└─────────────────────────────┘
따라서
Tick Rate = 처리 빈도
NetSpeed = 전송량 제한
이라고 기억하면 된다.
18. Unreal 네트워크 전체 구조
지금까지 세 편의 내용을 모두 연결하면 다음과 같다.
FURL
↓
UEngine::Browse()
│
┌─────────┴─────────┐
↓ ↓
Local Remote
↓ ↓
LoadMap() UPendingNetGame
↓ ↓
UWorld NetDriver
│ ↓
│ InitConnect()
│ ↓
│ UNetConnection
│ ↓
│ Handshake
│
↓
NetDriver
↓
InitListen()
↓
Server
│
↓
UDP Socket
│
┌────────┼────────┐
↓ ↓ ↓
Connection Connection Connection
A B C
│ │ │
Channel Channel Channel
│
┌───┴──────────┐
↓ ↓
Control Actor
Channel Channel
↓
Replication
↓
Bunch
↓
Packet
↓
UDP
이 구조를 이해하면 Unreal의 네트워크를 단순히
"Replication을 하면 서버에서 클라이언트로 값이 복사된다."
정도로 보는 것이 아니라,
World → NetDriver → Connection → Channel → Bunch → Packet → UDP Socket
이라는 여러 계층의 흐름으로 볼 수 있게 된다.
그리고 반대 방향의 데이터는
UDP Socket
↓
Packet
↓
Bunch
↓
Channel
↓
Connection
↓
Actor / Control 처리
순서로 올라온다.
이것이 Unreal 네트워크를 소스 코드 레벨에서 따라가기 위한 기본적인 골격이다.
'Unreal' 카테고리의 다른 글
| Unreal Network Flow 소소한 정리 (2) (0) | 2026.09.11 |
|---|---|
| Unreal Network Flow 소소한 정리 (1) (0) | 2026.09.11 |
| CommonLoadingScreen 분석 및 정리 (0) | 2026.09.11 |
| 언리얼 게임 프레임워크 주요 클래스 정리 (0) | 2026.09.07 |
| 플레이어 컨트롤러의 입력 키 복구 작업중 발생한 문제 정리. (0) | 2026.08.09 |

