네트워크 정리 (통합본)

1. 네트워크 계층

OSI 7계층과 TCP/IP 4계층

OSI 7계층은 네트워크 통신 과정을 역할별로 나눈 참조 모델이다.

  • 물리 계층: 비트 전송
  • 데이터링크 계층: 같은 네트워크 안에서 프레임 전달, MAC
  • 네트워크 계층: IP를 이용한 라우팅
  • 전송 계층: TCP·UDP를 이용한 종단 간 통신
  • 세션·표현·응용 계층: 연결 관리, 데이터 표현, 애플리케이션 프로토콜

실제 인터넷에서는 TCP/IP 4계층 모델을 주로 사용한다.

  • 네트워크 액세스
  • 인터넷
  • 전송
  • 응용

게임 클라이언트 개발에서는 주로 전송 계층의 TCP·UDP와 그 위의 게임 프로토콜을 다룬다.


2. TCP와 UDP

TCP

TCP는 연결 지향적인 바이트 스트림 프로토콜이다.

특징:

  • 데이터 도착 보장
  • 순서 보장
  • 중복 제거
  • 재전송
  • 흐름 제어
  • 혼잡 제어
  • 메시지 경계 없음

TCP는 시퀀스 번호와 ACK를 이용해 손실과 순서를 관리한다. ACK가 일정 시간 오지 않거나 중복 ACK가 발생하면 데이터를 재전송한다.

UDP

UDP는 비연결형 데이터그램 프로토콜이다.

특징:

  • 도착 보장 없음
  • 순서 보장 없음
  • 재전송 없음
  • 메시지 경계 보존
  • 오버헤드가 적음
  • 애플리케이션이 전송 정책을 직접 설계 가능

UDP가 물리적으로 더 빠른 것이 아니라, TCP의 재전송·순서 보장·혼잡 제어 등으로 인한 대기 과정이 없기 때문에 실시간 처리에 유리하다.

게임에서의 선택 기준

기준은 다음과 같다.

반드시 도착해야 하는가, 아니면 최신 데이터만 중요한가.

TCP가 적합한 데이터:

  • 로그인·인증
  • 채팅
  • 인벤토리
  • 재화 변경
  • 매칭
  • 상점

UDP가 적합한 데이터:

  • 위치
  • 회전
  • 이동 입력
  • 시선 방향
  • 고빈도 실시간 상태

게임에서는 신뢰성과 실시간성이 모두 필요한 데이터에 대해 UDP 위에 ACK, 재전송, 시퀀스 번호 등을 선택적으로 구현하기도 한다.


3. TCP 연결 과정

3-Way Handshake

TCP 연결 수립 과정이다.

  1. 클라이언트가 SYN 전송
  2. 서버가 SYN+ACK 응답
  3. 클라이언트가 ACK 전송

목적은 양쪽의 송수신 가능 여부를 확인하고 초기 시퀀스 번호를 동기화하는 것이다.

4-Way Handshake

TCP 연결 종료 과정이다.

  1. FIN
  2. ACK
  3. FIN
  4. ACK

TCP 연결은 양방향이므로 각 방향을 독립적으로 종료해야 한다.

TIME_WAIT

마지막 ACK를 보낸 쪽이 일정 시간 연결 정보를 유지하는 상태다.

필요한 이유:

  • 마지막 ACK 유실 시 FIN에 다시 응답
  • 이전 연결의 지연 패킷이 새 연결에 섞이는 것을 방지

유지 시간은 2MSL(Maximum Segment Lifetime의 2배)이다. 패킷이 네트워크에서 살아있을 수 있는 최대 시간의 2배를 기다려서, 이전 연결의 지연 패킷이 완전히 소멸한 뒤에 포트를 재사용한다.


4. 흐름 제어와 혼잡 제어

흐름 제어

수신자가 처리할 수 있는 만큼만 보내도록 조절한다.

수신자는 자신의 수신 버퍼 여유 공간을 rwnd로 알리고, 송신자는 그 범위를 넘지 않도록 전송한다.

핵심:

수신자를 보호하는 기능

혼잡 제어

네트워크가 감당할 수 있는 만큼만 보내도록 조절한다.

혼잡 윈도우 cwnd를 사용하며, 손실이 발생하면 전송 속도를 줄인다.

핵심:

네트워크와 라우터를 보호하는 기능

실제 전송 가능량은 일반적으로 rwndcwnd 중 작은 값에 의해 제한된다.


5. Head-of-Line Blocking (HOL 블로킹)

TCP가 실시간 게임에 불리한 이유의 정식 명칭. 2번의 “재전송·순서 보장으로 인한 대기 과정”이 바로 이것이다.

패킷 하나가 유실되면, 그 뒤의 패킷들이 이미 도착해 커널 버퍼에 있어도 순서 보장 때문에 애플리케이션에 전달되지 않는다. 유실된 패킷의 재전송이 완료될 때까지 뒤의 모든 데이터가 줄줄이 막힌다.

면접 활용:

“FPS에서 실시간 상태에 TCP를 안 쓰는 핵심 이유는 Head-of-Line Blocking입니다. 패킷 하나의 유실이 그 뒤 최신 데이터까지 전부 지연시키는데, 게임에서는 낡은 데이터를 살리려고 최신 데이터를 막는 것이 손해이기 때문입니다.”

이 용어 하나로 “차이를 아는 사람”에서 “이유를 아는 사람”으로 격상된다.


6. Nagle 알고리즘과 TCP_NODELAY

게임 네트워크 면접 단골.

  • Nagle 알고리즘: 작은 패킷을 여러 개 보내면 헤더 오버헤드가 커지므로, TCP가 작은 데이터를 모았다가 한 번에 보내는 기본 최적화. ACK가 올 때까지 작은 데이터를 버퍼에 붙잡아 둔다.
  • 문제: 대역폭에는 이득이지만 지연이 늘어난다. 채팅 한 줄, 버튼 입력 하나가 즉시 안 나가고 대기할 수 있다.
  • 해결: TCP_NODELAY 소켓 옵션으로 Nagle을 끈다. 게임·채팅처럼 지연에 민감한 TCP 통신에서는 끄는 것이 일반적.

핵심 문장:

“Nagle은 처리량을 위해 지연을 희생하는 최적화이므로, 지연이 우선인 게임 TCP 통신에서는 TCP_NODELAY로 비활성화합니다.”


7. TCP 메시지 경계와 부분 송수신

TCP는 바이트 스트림이므로 send()recv()가 일대일로 대응하지 않는다.

예를 들어:

  • send() 한 번의 데이터가 여러 번의 recv()로 나뉠 수 있음
  • 여러 번의 send()가 한 번의 recv()에 합쳐질 수 있음

따라서 애플리케이션이 패킷 경계를 직접 정의해야 한다.

대표적인 방식:

  • 고정 길이
  • 구분자
  • 길이 헤더

게임에서는 보통 패킷 앞에 전체 크기와 패킷 타입을 기록하는 길이 헤더 방식을 사용한다.

또한 send()recv()의 반환값은 요청한 전체 크기가 아니라 실제 처리한 바이트 수이므로, 부분 송수신을 고려한 반복 처리나 누적 버퍼가 필요하다.


8. 소켓

소켓은 애플리케이션이 네트워크 통신을 하기 위해 사용하는 운영체제의 통신 인터페이스이자 통신 끝점이다.

TCP 서버 호출 순서

socket()
bind()
listen()
accept()
recv() / send()
closesocket()

accept()는 클라이언트와 통신할 새로운 소켓을 반환한다. 기존 리스닝 소켓은 계속 다른 연결을 받는다.

TCP 클라이언트 호출 순서

socket()
connect()
send() / recv()
closesocket()

주요 함수

  • bind: 로컬 IP와 포트를 소켓에 할당
  • listen: 연결 요청 대기 상태로 전환
  • accept: 완료된 연결을 가져와 통신용 소켓 생성

9. 블로킹과 논블로킹

블로킹 소켓

작업이 완료될 때까지 함수가 반환되지 않는다.

메인 스레드에서 블로킹 recv()를 호출하면 데이터가 도착할 때까지 게임 전체가 멈출 수 있다.

논블로킹 소켓

즉시 처리할 수 없으면 바로 반환한다.

게임 클라이언트에서는 일반적으로:

  • 네트워크 전용 스레드 사용
  • 논블로킹 소켓 사용
  • 이벤트 기반 I/O 사용

등의 구조를 사용한다.


10. 네트워크 스레드와 메인 스레드

네트워크 수신은 별도 스레드에서 처리하고, 완성된 패킷을 스레드 안전 큐에 넣는 방식이 일반적이다.

구조:

네트워크 스레드
→ 데이터 수신
→ 패킷 조립
→ 스레드 안전 큐에 Push

메인 스레드
→ 큐에서 Pop
→ 패킷 핸들러 실행
→ 게임 상태 변경

중요한 원칙은 게임 상태를 네트워크 스레드에서 직접 변경하지 않는 것이다. 게임 상태 변경을 메인 스레드에 집중시키면 동기화 범위를 줄일 수 있다.


11. 패킷 핸들러 디스패치 구조

10번의 다음 단계. 메인 스레드가 큐에서 꺼낸 패킷을 어떻게 처리하는가.

  • 패킷 헤더의 타입 ID를 키로, 처리 함수를 값으로 갖는 핸들러 맵(테이블)을 둔다.
  • 새 패킷 타입 추가 = 핸들러 등록 한 줄. 거대한 switch문 대신 확장에 열린 구조.
  • 자기 엔진과 연결 가능: “데이터 관리자에서 ID 기반으로 조합 레시피를 찾던 것과 같은 사고방식으로, 패킷 타입 ID를 핸들러에 매핑했습니다.”

12. 직렬화

직렬화는 메모리상의 객체를 전송 가능한 연속된 바이트 배열로 변환하는 과정이다.

구조체를 그대로 전송하면 문제가 발생할 수 있다.

  • 구조체 패딩
  • 플랫폼별 타입 크기 차이
  • 엔디언 차이
  • 포인터가 포함될 가능성
  • 가상 함수 테이블 포인터
  • 컴파일러별 메모리 배치 차이

따라서 다음 방법을 사용한다.

  • 고정 크기 타입 사용: int32_t, uint16_t
  • 필드 단위 직렬화
  • 네트워크 바이트 순서 통일
  • Protobuf, FlatBuffers 같은 스키마 기반 직렬화

13. 엔디언

엔디언은 멀티바이트 값을 메모리에 저장하는 바이트 순서다.

  • 리틀 엔디언: 낮은 바이트를 낮은 주소에 저장
  • 빅 엔디언: 높은 바이트를 낮은 주소에 저장

네트워크 바이트 순서는 빅 엔디언이다.

대표 함수:

htons()
htonl()
ntohs()
ntohl()

프로토콜에서는 송수신 양쪽이 동일한 바이트 순서를 사용하도록 명확히 정의해야 한다.


14. MTU와 단편화

MTU는 한 번에 전송할 수 있는 최대 데이터 크기다. 이더넷에서는 일반적으로 1500바이트다.

IP 패킷이 MTU보다 크면 단편화될 수 있다.

UDP에서 단편화가 위험한 이유:

  • 조각 하나만 유실되어도 전체 데이터그램이 폐기됨
  • UDP 자체 재전송이 없음
  • 조각 수가 늘어날수록 전체 손실 가능성 증가

그래서 UDP 게임 패킷은 헤더와 경로 오버헤드를 고려해 대체로 1200바이트 이하로 설계하는 경우가 많다.


15. 지연과 대역폭의 구분

한 줄이면 충분하지만 혼동하면 티가 나는 지점.

  • 대역폭(throughput): 시간당 보낼 수 있는 데이터 양. 도로의 차선 수.
  • 지연(latency, RTT): 한 번 왕복에 걸리는 시간. 도로의 길이.

게임이 민감한 것은 대부분 지연이다. 대역폭이 아무리 커도 RTT가 길면 반응성은 나쁘다. 글로벌 서비스에서 국가별 체감이 다른 이유도 물리적 거리에 따른 RTT 차이.


16. 연결 끊김 감지와 Heartbeat

정상 종료는 recv()가 0을 반환하면서 감지할 수 있다.

하지만 랜선 단절이나 클라이언트 강제 종료처럼 상대가 아무 신호 없이 사라지는 경우에는 즉시 알기 어렵다.

그래서 애플리케이션 수준의 Heartbeat를 사용한다.

Heartbeat의 목적:

  • 비정상 연결 종료 감지
  • NAT 매핑 유지
  • RTT 측정
  • 서버 상태 확인

일정 시간 동안 응답이 없으면 연결이 끊긴 것으로 판단한다.

TCP Keepalive vs 애플리케이션 Heartbeat

“Heartbeat 대신 TCP Keepalive를 쓰면 안 되나요?”라는 꼬리 질문 대비.

  • TCP Keepalive: OS 수준 기능. 기본 주기가 매우 길고(수 시간 단위 기본값) 세밀한 제어가 어렵다. 연결 생존만 확인.
  • 애플리케이션 Heartbeat: 주기를 게임에 맞게 설정 가능, RTT 측정 가능, 게임 로직(예: 일시정지 감지, 재접속 트리거)과 연동 가능.

“생존 확인만이 아니라 RTT 측정과 게임 로직 연동이 필요하므로 애플리케이션 수준 Heartbeat를 사용합니다.”


17. 재접속(Reconnect) 처리

글로벌 라이브 서비스의 필수 시나리오. 모바일 네트워크 전환, 불안정한 해외 회선에서 연결 끊김은 일상이다.

기본 흐름:

  1. 연결 유실 감지 (Heartbeat 무응답 또는 send 실패)
  2. 유저에게 재연결 중 UI 표시 (게임을 즉시 종료시키지 않음)
  3. 세션 토큰으로 재인증 — 로그인부터 다시 시키지 않기 위해 서버가 세션을 일정 시간 유지
  4. 상태 재동기화 — 끊긴 동안의 변화를 서버에서 스냅샷으로 받아 클라이언트 상태를 서버 기준으로 덮어씀
  5. 끊기기 직전 진행 중이던 요청(구매 등)의 결과를 서버에 확인 (21번의 요청 ID 활용)

핵심 문장:

“재접속은 세션 토큰으로 재인증하고, 서버 스냅샷으로 상태를 재동기화하며, 끊기기 직전 요청의 처리 결과를 확인하는 세 단계로 처리합니다.”


18. 서버 권위

서버 권위는 게임 상태의 최종 판정을 서버가 담당하는 구조다.

클라이언트는 다음과 같은 요청만 전송한다.

  • 이동 입력
  • 공격 입력
  • 아이템 사용 요청
  • 상호작용 요청

서버는 다음을 직접 검증하고 계산한다.

  • 이동 가능 여부
  • 사거리
  • 쿨타임
  • 명중 판정
  • 데미지
  • 아이템 보유량
  • 재화 변경

클라이언트는 사용자가 조작할 수 있으므로, 클라이언트가 보낸 값은 항상 신뢰할 수 없는 입력으로 취급해야 한다.


19. 패킷 암호화와 서버 검증의 관계

크로스파이어는 안티치트가 핵심인 게임이므로 한 줄 준비.

  • 패킷 암호화·서명은 도청과 위·변조의 난이도를 높이는 보조 수단이다.
  • 그러나 클라이언트는 유저의 손 안에 있으므로 암호화 키도 결국 클라이언트에 있다. 근본 방어는 서버 검증(18번 서버 권위).

핵심 문장:

“암호화는 공격 비용을 높이는 보조 수단이고, 본질적인 방어는 클라이언트가 보낸 값을 서버가 항상 검증하는 구조입니다.”


20. 위치 동기화

온라인 게임의 위치 동기화에는 다음 기법들이 사용된다.

클라이언트 사이드 예측

내 입력을 서버 응답 전에 로컬에서 먼저 실행한다.

목적:

내 캐릭터의 조작 지연 감소

서버 리콘실리에이션

서버 상태와 클라이언트 예측 결과가 다를 때 교정한다.

과정:

  1. 서버가 확정한 상태로 복원
  2. 아직 서버가 확인하지 않은 입력을 다시 적용
  3. 현재 상태 재구성

스냅샷 보간

다른 플레이어의 이전 두 상태 사이를 부드럽게 보간한다.

목적:

원격 캐릭터 움직임의 끊김 방지

외삽

마지막 위치와 속도를 기준으로 미래 위치를 잠시 추정한다.

잘못 예측할 위험이 있으므로 짧은 시간만 사용한다.

정리하면:

내 캐릭터는 예측, 다른 캐릭터는 보간, 최종 판정은 서버가 담당한다.


21. 요청-응답 상관관계 관리

클라이언트가 서버에 요청을 보내고 응답을 받는 모든 기능(전적 조회, 구매, 우편 등)에 적용되는 기본기.

요청 ID(시퀀스 번호)를 붙이는 이유:

  1. 늦은 응답 무시: A의 전적을 요청하고 응답이 오기 전에 B를 클릭하면, 늦게 도착한 A의 응답을 그대로 그리면 안 된다. 각 요청에 ID를 붙이고, 최신 요청의 응답만 반영한다.
  2. 타임아웃: 일정 시간 응답이 없으면 실패로 간주하고 실패 UI를 보여준다. 무한 대기는 없다.
  3. 재시도와 멱등성: 타임아웃 후 재시도했는데 사실 첫 요청이 서버에 도착해 있었다면? 구매 요청이 두 번 처리되면 안 된다. 요청 ID를 서버가 기억해서 같은 ID의 요청은 한 번만 처리(멱등 처리)하는 구조가 필요하다. 결제·재화 관련 기능의 필수 개념.

핵심 문장:

“요청에는 ID를 붙여서 늦은 응답을 걸러내고, 타임아웃으로 무한 대기를 막고, 재시도 시 서버가 같은 ID를 중복 처리하지 않게 합니다.”


22. 낙관적 UI와 레이턴시 하이딩

서버 권위(18번)와 유저 체감 반응성을 동시에 잡는 클라이언트 기법.

  • 원칙: 돌이킬 수 있는 것(연출, 버튼 상태, 사운드)은 서버 응답 전에 먼저 실행하고, 돌이킬 수 없는 것(재화 차감 표시, 아이템 지급 확정)은 반드시 서버 응답 후에 확정한다.
  • 연출이 곧 도구: 구매 버튼 클릭 즉시 구매 연출을 재생하면, 연출 길이(200~300ms)가 서버 왕복 시간을 자연스럽게 흡수한다. 웹은 스피너를 띄우지만 게임은 기다림을 연출로 바꾼다.
  • 실패 시 롤백: 낙관이 틀렸을 때(잔액 부족, 품절) 연출을 중단하고 실패 사유를 명확히 표시하며, 미리 바꿔둔 클라이언트 상태를 전부 되돌린다.

핵심 문장:

“서버 권위는 유지하되, 클라이언트는 되돌릴 수 있는 연출을 먼저 실행해 대기 시간을 숨기고, 실패 응답이 오면 상태를 롤백하고 사유를 표시합니다.”


23. 실패 흐름 4종 체크리스트

설계 답변의 마무리 멘트 “그리고 실패 시에는~“에 넣을 재료. 어떤 네트워크 기능이든 이 4가지를 점검하면 빠짐이 없다.

  1. 타임아웃: 응답이 안 옴 → 실패 UI + 재시도 수단 제공
  2. 거절 응답: 서버가 명시적으로 거부(잔액 부족, 권한 없음) → 사유 표시 + 상태 롤백
  3. 연결 끊김: 요청 도중 연결 유실 → 재접속 흐름으로 전환, 재접속 후 요청 결과 재확인
  4. 늦은/중복 응답: 이미 화면이 바뀐 뒤 도착 → 요청 ID로 무시

24. 반드시 외울 핵심 문장

면접 직전에는 아래 문장을 정확히 말할 수 있으면 된다.

  1. TCP는 신뢰성과 순서를 보장하는 바이트 스트림이고, UDP는 메시지 경계를 보존하지만 도착과 순서를 보장하지 않는다.
  2. TCP에서는 send()recv()가 일대일로 대응하지 않으므로 길이 헤더와 누적 버퍼가 필요하다.
  3. 흐름 제어는 수신자를 보호하고, 혼잡 제어는 네트워크를 보호한다.
  4. 게임에서는 반드시 도착해야 하는 데이터는 TCP, 최신 값만 중요한 실시간 데이터는 UDP를 사용한다.
  5. 네트워크 수신은 별도 스레드에서 처리하되 게임 상태 변경은 메인 스레드에서 수행한다.
  6. 클라이언트가 보내는 값은 신뢰할 수 없으므로 판정과 상태 변경은 서버가 담당해야 한다.
  7. 내 캐릭터는 클라이언트 예측과 리콘실리에이션을 사용하고, 다른 캐릭터는 스냅샷 보간을 사용한다.
  8. UDP에서도 신뢰성이 필요하면 시퀀스 번호, ACK, 재전송을 애플리케이션 수준에서 선택적으로 구현한다.
  9. TCP가 실시간 게임에 불리한 핵심 이유는 Head-of-Line Blocking으로, 패킷 하나의 유실이 뒤의 최신 데이터까지 지연시키기 때문이다.
  10. Nagle 알고리즘은 처리량을 위해 지연을 희생하므로, 지연에 민감한 게임 TCP 통신에서는 TCP_NODELAY로 끈다.
  11. 요청에는 ID를 붙여 늦은 응답을 걸러내고, 타임아웃을 걸고, 재시도 시 서버가 중복 처리하지 않게 한다.
  12. 되돌릴 수 있는 연출은 서버 응답 전에 실행해 대기를 숨기고, 되돌릴 수 없는 확정은 서버 응답 후에 한다. 실패하면 롤백한다.
  13. 재접속은 세션 토큰 재인증, 스냅샷 재동기화, 직전 요청 결과 확인으로 처리한다.
  14. 암호화는 보조 수단이고, 본질은 서버가 모든 클라이언트 입력을 검증하는 것이다.

학습 우선순위

시간이 부족하면 이 순서로:

  1. 21~23번 — 모의면접 감점 지점 직결. 설계 질문 전부에 재사용됨
  2. 5번(HOL), 6번(Nagle) — 나올 확률 높고 암기량 적음
  3. 17번(재접속) — 라이브 직무 어필용. 필리핀 서버 플레이 경험과 묶으면 스토리가 됨
  4. 나머지는 한 번 읽고 24번 핵심 문장만