네트워크 면접 질문 모범답변 (게임 클라이언트 프로그래머용)

1. 네트워크 기본

1. OSI 7계층을 설명해주세요.

네트워크 통신 과정을 7단계로 나눈 표준 참조 모델입니다. 물리(비트 전송) → 데이터링크(같은 네트워크 내 프레임 전달, MAC) → 네트워크(서로 다른 네트워크 간 라우팅, IP) → 전송(종단 간 신뢰성/포트, TCP/UDP) → 세션(연결 관리) → 표현(인코딩/암호화) → 응용(HTTP 등 애플리케이션 프로토콜) 순입니다. 계층을 나누는 이유는 각 계층이 자신의 역할만 책임지고 아래 계층의 구현을 몰라도 되도록 하기 위함입니다. 게임 클라이언트 프로그래머가 실질적으로 다루는 영역은 전송 계층(TCP/UDP)과 그 위의 애플리케이션 프로토콜 설계입니다.

2. TCP/IP 4계층을 설명해주세요.

실제 인터넷에서 사용하는 실용적인 모델로, 네트워크 액세스(물리+데이터링크) → 인터넷(IP) → 전송(TCP/UDP) → 응용(HTTP, 게임 프로토콜 등) 4계층입니다. OSI 7계층의 세션/표현/응용을 응용 계층 하나로 합친 형태입니다. 데이터를 보낼 때는 위에서 아래로 각 계층의 헤더가 붙고(캡슐화), 받을 때는 아래에서 위로 헤더가 벗겨집니다(역캡슐화).

3. IP의 역할은 무엇인가요?

서로 다른 네트워크에 있는 호스트 간에 패킷을 목적지까지 전달(라우팅)하는 것입니다. IP는 비신뢰적(unreliable)이고 비연결형입니다. 패킷이 유실되거나, 중복되거나, 순서가 바뀔 수 있으며 이를 보장하지 않습니다. 그래서 신뢰성이 필요하면 그 위의 TCP가 책임지고, 게임에서 UDP를 쓸 때는 애플리케이션이 직접 책임져야 합니다.

4. TCP와 UDP의 차이는 무엇인가요?

TCP는 연결 지향적이며 신뢰성(순서 보장, 재전송, 흐름/혼잡 제어)을 제공하는 대신 지연이 발생할 수 있고, UDP는 비연결형으로 신뢰성 보장이 없는 대신 오버헤드가 적고 지연이 낮습니다. TCP는 바이트 스트림이라 메시지 경계가 없고, UDP는 데이터그램 단위라 경계가 보존됩니다. 게임에서는 로그인·인벤토리·채팅처럼 반드시 도착해야 하는 데이터는 TCP(또는 신뢰성 계층을 얹은 UDP), 위치 동기화처럼 최신 값만 중요한 데이터는 UDP가 적합합니다.

5. TCP는 어떻게 신뢰성을 보장하나요?

핵심은 시퀀스 번호와 ACK(확인 응답)입니다. 보낸 데이터의 각 바이트에 시퀀스 번호를 붙이고, 수신 측이 ACK로 어디까지 받았는지 알려줍니다. 일정 시간 내 ACK가 오지 않으면(타임아웃) 또는 중복 ACK가 반복되면 손실로 판단해 재전송합니다. 여기에 체크섬으로 손상 감지, 시퀀스 번호로 순서 재조립과 중복 제거, 흐름 제어와 혼잡 제어로 과부하 방지까지 수행합니다.

6. TCP 3-way handshake를 설명해주세요.

연결 수립 과정입니다. ① 클라이언트가 SYN(자신의 초기 시퀀스 번호 포함)을 보내고, ② 서버가 SYN+ACK로 응답하며 자신의 초기 시퀀스 번호를 보내고, ③ 클라이언트가 ACK를 보내면 연결이 성립됩니다. 3번 주고받는 이유는 양쪽 모두 “내 송신 능력과 상대의 수신 능력”을 확인하고, 서로의 초기 시퀀스 번호를 동기화하기 위해서입니다. 2번만으로는 클라이언트의 수신 확인이 서버에게 전달되지 않습니다.

7. TCP 4-way handshake를 설명해주세요.

연결 종료 과정입니다. ① 종료를 원하는 쪽이 FIN을 보내고, ② 상대가 ACK로 응답합니다(이때 상대는 아직 보낼 데이터가 남아 있을 수 있음). ③ 상대도 보낼 데이터를 다 보낸 후 FIN을 보내고, ④ 처음 종료를 요청한 쪽이 ACK를 보냅니다. 수립과 달리 4단계인 이유는, 연결이 양방향이라 각 방향을 독립적으로 닫아야 하기 때문입니다(half-close). 마지막 ACK를 보낸 쪽은 TIME_WAIT 상태로 일정 시간 대기합니다.

8. TIME_WAIT 상태가 필요한 이유는 무엇인가요?

두 가지입니다. 첫째, 마지막 ACK가 유실됐을 때 상대가 재전송한 FIN에 다시 ACK로 응답하기 위해서입니다. TIME_WAIT 없이 바로 닫아버리면 상대는 정상 종료를 확인하지 못합니다. 둘째, 네트워크에 남아 떠도는 이전 연결의 지연 패킷이, 같은 포트로 새로 열린 연결에 잘못 섞여 들어가는 것을 막기 위해서입니다. 그래서 보통 2×MSL(최대 세그먼트 생존 시간)만큼 대기합니다. 서버를 자주 재시작하는 개발 환경에서 “포트가 이미 사용 중” 오류가 나는 원인이 바로 이것이고, SO_REUSEADDR 옵션으로 완화합니다.

9. TCP의 흐름 제어란 무엇인가요?

수신자가 감당할 수 있는 만큼만 보내도록 송신 속도를 조절하는 것입니다. 수신자는 자신의 수신 버퍼 여유 공간을 윈도우 크기(rwnd)로 광고하고, 송신자는 그 이상 보내지 않습니다. 수신 버퍼가 가득 차면 윈도우가 0이 되어 송신이 멈춥니다. 게임 클라이언트가 수신 데이터를 제때 read하지 않으면 버퍼가 차고, 결국 서버 쪽 송신까지 밀리는 원인이 됩니다.

10. TCP의 혼잡 제어란 무엇인가요?

네트워크가 감당할 수 있는 만큼만 보내도록 조절하는 것입니다. 흐름 제어가 수신자 보호라면, 혼잡 제어는 네트워크(라우터) 보호입니다. 혼잡 윈도우(cwnd)를 두고, Slow Start(지수적으로 증가) → 혼잡 회피(선형 증가) → 손실 감지 시 윈도우 축소(빠른 재전송/빠른 회복)의 사이클로 동작합니다. 실제 송신량은 min(rwnd, cwnd)로 결정됩니다. 게임에서 TCP의 지연 스파이크가 생기는 원인 중 하나가 손실 시 혼잡 제어로 전송 속도가 급감하기 때문입니다.

11. Sliding Window란 무엇인가요?

ACK를 기다리지 않고 윈도우 크기만큼의 데이터를 연속으로 보낼 수 있게 하는 기법입니다. 매 세그먼트마다 ACK를 기다리면(Stop-and-Wait) 대역폭이 낭비되므로, “보냈지만 아직 ACK 안 받은 데이터”를 윈도우 범위 내에서 여러 개 유지합니다. ACK가 도착하면 윈도우가 앞으로 미끄러지듯 이동하며 새 데이터를 보낼 수 있게 됩니다. 흐름 제어와 혼잡 제어 모두 이 윈도우 크기를 조절하는 방식으로 구현됩니다.

12. 패킷 손실은 어떻게 감지하나요?

두 가지 방법이 있습니다. ① 타임아웃(RTO): 보낸 세그먼트의 ACK가 예상 시간(RTT 기반으로 계산) 내에 오지 않으면 손실로 판단합니다. ② 중복 ACK: 수신자는 순서에 맞지 않는 세그먼트를 받으면 “마지막으로 정상 수신한 위치”의 ACK를 반복해서 보내는데, 같은 ACK가 3번 중복되면 그 다음 세그먼트가 손실됐다고 판단합니다(빠른 재전송). 타임아웃보다 중복 ACK 방식이 훨씬 빨리 반응합니다.

13. 재전송은 어떻게 이루어지나요?

손실이 감지된 세그먼트를 송신 버퍼에서 다시 보냅니다. 타임아웃 기반 재전송은 RTO가 지나면 재전송하고 RTO를 2배로 늘립니다(지수 백오프). 빠른 재전송은 중복 ACK 3개를 받으면 타임아웃을 기다리지 않고 즉시 해당 세그먼트를 재전송합니다. SACK(Selective ACK) 옵션이 있으면 수신자가 “어디어디는 받았다”를 알려줘서 유실된 구간만 골라 재전송할 수 있습니다. 송신 버퍼는 ACK를 받을 때까지 데이터를 보관해야 하므로, 이것이 TCP에 송신 버퍼가 필요한 이유입니다.

14. 순서가 뒤바뀐 TCP 세그먼트는 어떻게 처리하나요?

수신 측 커널이 시퀀스 번호를 기준으로 재조립합니다. 순서보다 앞선 세그먼트가 먼저 도착하면 수신 버퍼에 보관해두고, 빠진 구간이 채워질 때까지 애플리케이션에는 전달하지 않습니다. 이 때문에 중간 패킷 하나가 유실되면 이미 도착한 뒤 데이터까지 모두 대기하게 되는데, 이것이 HOL(Head-of-Line) 블로킹입니다. 실시간 게임에서 TCP가 불리한 핵심 이유로, 최신 위치 데이터가 도착해 있어도 유실된 과거 데이터의 재전송을 기다려야 합니다.

15. UDP가 TCP보다 빠르다고 하는 이유는 무엇인가요?

전송 자체의 물리적 속도가 빠른 게 아니라, TCP가 하는 일을 안 하기 때문에 지연 요소가 없는 것입니다. 연결 수립(handshake)이 없고, ACK 대기·재전송·순서 재조립·혼잡 제어에 의한 전송 지연이 없으며, 헤더도 8바이트로 작습니다(TCP 최소 20바이트). 특히 손실 시 재전송을 기다리며 뒤 데이터까지 막히는 HOL 블로킹이 없다는 점이 실시간 게임에서 결정적입니다.

16. UDP도 반드시 빠른가요?

아닙니다. 같은 네트워크 경로를 지나므로 전파 지연 자체는 동일하고, 손실이 없는 안정적인 환경에서는 TCP와 체감 차이가 거의 없습니다. 오히려 UDP는 혼잡 제어가 없어서 무분별하게 쏘면 라우터 큐를 넘치게 해 손실을 스스로 유발할 수 있고, 애플리케이션에서 신뢰성 계층을 직접 얹으면(재전송, 순서 보장) 결국 TCP와 비슷한 지연 특성이 생깁니다. “UDP가 빠르다”의 정확한 의미는 “손실·혼잡 상황에서 지연 스파이크가 없고, 지연의 제어권이 애플리케이션에 있다”입니다.

17. UDP에서 신뢰성을 구현하려면 무엇이 필요한가요?

TCP가 해주던 것을 애플리케이션 레벨에서 선택적으로 구현해야 합니다. ① 시퀀스 번호(순서 판별, 중복 제거), ② ACK와 재전송 타이머(손실 복구), ③ 필요 시 순서 보장 버퍼, ④ 혼잡/전송량 제어입니다. 핵심은 “채널별로 선택 가능”하다는 점입니다. 예를 들어 위치 패킷은 unreliable-unordered로, 스킬 사용은 reliable로, 채팅은 reliable-ordered로 채널을 나누면, TCP처럼 모든 데이터가 하나의 순서 스트림에 묶여 HOL 블로킹을 겪는 문제를 피할 수 있습니다. 이런 구조가 RUDP이며 상용으로는 KCP, ENet 등이 있습니다.

18. 연결 지향과 비연결 지향의 차이는 무엇인가요?

연결 지향(TCP)은 데이터를 주고받기 전에 handshake로 논리적 연결을 수립하고, 양쪽이 연결 상태(시퀀스 번호, 버퍼, 윈도우)를 유지합니다. 비연결 지향(UDP)은 연결 수립 없이 목적지 주소만 붙여 데이터그램을 개별 전송하며, 상태를 유지하지 않습니다. 그래서 UDP는 연결이라는 개념 자체가 없고, “연결이 끊겼다”는 것도 애플리케이션이 heartbeat 등으로 직접 정의해야 합니다.

19. 포트 번호는 무엇인가요?

한 호스트 안에서 어떤 프로세스(정확히는 소켓)에게 데이터를 전달할지 구분하는 16비트 번호입니다. IP가 “어느 컴퓨터인지”를 찾는다면 포트는 “그 컴퓨터의 어느 프로그램인지”를 찾습니다. 01023은 잘 알려진 포트(HTTP 80 등), 4915265535는 클라이언트가 임시로 할당받는 동적 포트입니다. 통신은 (출발지 IP, 출발지 포트, 목적지 IP, 목적지 포트, 프로토콜) 5-tuple로 유일하게 식별됩니다.

20. Socket은 무엇인가요?

애플리케이션이 네트워크 통신을 하기 위한 OS 제공 인터페이스(추상화된 통신 끝점)입니다. 파일 디스크립터처럼 다뤄지며, 프로그래머는 소켓에 대해 send/recv를 호출하면 커널의 프로토콜 스택이 실제 전송을 처리합니다. TCP 소켓은 연결된 상대와의 스트림 통신 끝점이고, UDP 소켓은 데이터그램을 주고받는 끝점입니다. 소켓 = IP + 포트 + 프로토콜의 조합으로 식별되는 통신 창구라고 요약할 수 있습니다.

21. IP 주소와 MAC 주소의 차이는 무엇인가요?

IP 주소는 네트워크 계층의 논리 주소로, 최종 목적지를 나타내며 네트워크 위치에 따라 바뀝니다. MAC 주소는 데이터링크 계층의 물리 주소로, NIC(랜카드)에 고정되어 있으며 같은 네트워크(같은 링크) 안에서의 다음 홉 전달에 사용됩니다. 패킷이 라우터를 거칠 때마다 목적지 IP는 그대로지만, 프레임의 MAC 주소는 매 구간마다 바뀝니다. IP → MAC 변환은 ARP가 담당합니다. 비유하면 IP는 최종 배송지 주소, MAC은 각 구간 택배 기사의 식별번호입니다.

22. IPv4와 IPv6의 차이는 무엇인가요?

IPv4는 32비트 주소(약 43억 개)로 주소 고갈 문제가 있고, IPv6는 128비트 주소로 사실상 무한한 주소 공간을 제공합니다. IPv6는 헤더 구조가 단순화되었고, NAT 없이 종단 간 직접 통신이 가능하며, IPsec 지원이 기본입니다. 현재는 IPv4가 NAT로 연명하며 두 방식이 공존(듀얼 스택)하는 과도기입니다.

23. 공인 IP와 사설 IP의 차이는 무엇인가요?

공인 IP는 인터넷 전체에서 유일하며 직접 라우팅 가능한 주소이고, 사설 IP는 내부 네트워크에서만 유효한 주소(10.x, 172.16~31.x, 192.168.x 대역)입니다. 사설 IP는 인터넷에서 라우팅되지 않으므로, 외부와 통신하려면 NAT를 통해 공인 IP로 변환되어야 합니다. 게임에서 중요한 함의는, 사설 IP 뒤의 클라이언트에게 외부에서 먼저 연결을 시도할 수 없다는 것입니다. 그래서 P2P 게임은 홀 펀칭 같은 NAT 우회 기법이 필요하고, 대부분의 온라인 게임이 서버 중계(클라이언트가 항상 먼저 접속) 구조를 택합니다.

24. NAT란 무엇인가요?

사설 IP를 공인 IP로 변환해주는 기술(Network Address Translation)입니다. 공유기가 내부 기기의 (사설 IP, 포트)를 (공인 IP, 다른 포트)로 매핑하는 테이블을 유지하며, 나가는 패킷의 주소를 바꾸고 응답이 오면 테이블을 보고 내부 기기로 되돌려줍니다. IPv4 주소 고갈의 현실적 해결책이지만, 외부에서 내부로 먼저 연결할 수 없게 만들어 P2P 통신을 어렵게 합니다. NAT 매핑은 일정 시간 통신이 없으면 사라지므로, UDP 기반 게임에서 주기적인 keep-alive 패킷이 필요한 이유이기도 합니다.

25. DNS는 어떻게 동작하나요?

도메인 이름을 IP 주소로 변환하는 분산 시스템입니다. 클라이언트가 로컬 DNS 서버(리졸버)에 질의하면, 캐시에 없을 경우 루트 → TLD(.com) → 권한 네임서버 순으로 재귀적으로 질의해 IP를 얻고, TTL 동안 캐싱합니다. 게임 클라이언트는 보통 최초 접속 시 로그인/게이트웨이 서버의 도메인을 DNS로 해석하고, 이후 게임 서버 IP는 서버가 직접 내려주는 방식을 많이 씁니다.

26. MTU란 무엇인가요?

한 번에 전송할 수 있는 최대 전송 단위(Maximum Transmission Unit)로, 이더넷 기준 보통 1500바이트입니다. IP 패킷이 MTU보다 크면 단편화되거나 폐기됩니다. 게임에서 UDP를 쓸 때는 IP/UDP 헤더와 VPN 등 경로상 오버헤드를 고려해, 패킷 하나를 보통 1200바이트 이하로 설계해 단편화를 원천 차단하는 것이 관례입니다.

27. IP 단편화는 왜 발생하나요?

패킷 크기가 경로상 링크의 MTU보다 클 때, IP 계층이 패킷을 MTU에 맞는 조각들로 나누어 보내기 때문입니다. 조각들은 목적지에서 재조립되는데, 조각 중 하나라도 유실되면 전체 패킷이 폐기됩니다. UDP에서 단편화는 특히 위험합니다. 재전송이 없으므로 조각 하나 유실 = 패킷 전체 유실이고, 손실률이 조각 수만큼 배가됩니다. 그래서 UDP 게임 프로토콜은 패킷을 MTU 이하로 유지하고, 큰 데이터는 애플리케이션 레벨에서 분할 전송합니다.

28. TCP에서는 메시지 경계가 보장되나요?

보장되지 않습니다. TCP는 바이트 스트림 프로토콜이라, send를 3번 호출해도 수신 측 recv 한 번에 다 들어올 수도 있고, send 한 번의 데이터가 여러 recv로 쪼개져 올 수도 있습니다. 커널이 내부적으로 데이터를 병합(Nagle 등)하거나 분할하기 때문입니다. 따라서 애플리케이션이 메시지 경계를 직접 정의해야 하며, 게임에서는 보통 길이 헤더 방식(패킷 앞에 크기 필드)을 사용합니다.

29. UDP에서는 메시지 경계가 보장되나요?

보장됩니다. UDP는 데이터그램 단위 프로토콜이라 sendto 한 번 = recvfrom 한 번이 정확히 대응됩니다. 데이터그램은 통째로 도착하거나 통째로 유실되지, 쪼개져서 애플리케이션에 전달되지 않습니다. 그래서 UDP는 경계 구분 로직이 필요 없다는 장점이 있고, 대신 순서·도착 자체를 애플리케이션이 책임져야 합니다.

30. 패킷과 메시지의 차이는 무엇인가요?

메시지는 애플리케이션이 정의하는 논리적 데이터 단위(예: “이동 요청”, “채팅 한 줄”)이고, 패킷은 네트워크 계층에서 실제 전송되는 물리적 단위입니다. 하나의 메시지가 여러 패킷으로 쪼개질 수도, 여러 메시지가 한 패킷에 담길 수도 있습니다. 게임 개발 실무에서는 “패킷”을 애플리케이션 프로토콜 메시지의 의미로 관용적으로 쓰는 경우가 많지만(예: 패킷 핸들러), 엄밀히는 구분되는 개념임을 알고 있어야 합니다.


2. 소켓 프로그래밍

31. TCP 서버의 소켓 함수 호출 순서를 설명해주세요.

socket()bind()listen()accept()recv()/send()closesocket() 순입니다. socket으로 소켓을 생성하고, bind로 IP와 포트를 할당하고, listen으로 연결 요청 대기 상태로 전환한 뒤, accept로 클라이언트의 연결을 수락합니다. accept는 연결된 클라이언트와 통신하기 위한 새로운 소켓을 반환하며, 원래의 리스닝 소켓은 계속 다음 연결을 받습니다.

32. TCP 클라이언트의 소켓 함수 호출 순서를 설명해주세요.

socket()connect()send()/recv()closesocket() 순입니다. 클라이언트는 bind를 명시적으로 하지 않아도 connect 시 OS가 임시 포트를 자동 할당합니다. connect 호출 시점에 3-way handshake가 수행되며, 블로킹 소켓이면 연결이 성립되거나 실패할 때까지 대기합니다. 게임 클라이언트에서는 접속 중 UI가 멈추지 않도록 connect를 논블로킹 또는 별도 스레드에서 처리하는 것이 일반적입니다.

33. bind, listen, accept의 역할은 무엇인가요?

bind는 소켓에 로컬 IP 주소와 포트 번호를 부여합니다(“이 포트로 오는 데이터는 내가 받겠다”). listen은 소켓을 연결 요청을 받을 수 있는 수동 대기 상태로 전환하고, 아직 accept되지 않은 연결을 쌓아둘 백로그 큐의 크기를 지정합니다. accept는 그 큐에서 완료된 연결 하나를 꺼내, 해당 클라이언트와 1:1 통신할 새 소켓을 반환합니다. 즉 handshake 자체는 커널이 listen 단계에서 처리하고, accept는 완성된 연결을 가져오는 함수입니다.

34. send 한 번과 recv 한 번이 대응되나요?

TCP에서는 대응되지 않습니다. TCP는 바이트 스트림이므로 send 3번의 데이터가 recv 1번에 몰려 올 수도, send 1번이 recv 여러 번으로 나뉠 수도 있습니다. 반면 UDP는 데이터그램 단위라 sendto와 recvfrom이 1:1 대응됩니다. 그래서 TCP 기반 게임 프로토콜은 반드시 수신 버퍼에 데이터를 누적하고, 길이 헤더 등으로 메시지 단위를 직접 잘라내는 조립 로직이 필요합니다.

35. 부분 송수신이 발생하는 이유는 무엇인가요?

send/recv는 “요청한 크기만큼 처리했다”가 아니라 “커널 버퍼와 주고받은 만큼”을 반환하기 때문입니다. 송신 시 커널 송신 버퍼에 여유가 부족하면 일부만 복사되고, 수신 시 커널 수신 버퍼에 도착해 있는 만큼만 반환됩니다. 네트워크 상황(혼잡 윈도우, MSS 분할)에 따라 데이터가 쪼개져 도착하는 것도 원인입니다. 따라서 실전 코드에서는 반환값을 확인하며 원하는 크기를 다 처리할 때까지 반복하는 루프(또는 누적 버퍼)가 필수입니다.

36. 패킷 경계는 어떻게 구분하나요?

TCP는 경계가 없으므로 애플리케이션 프로토콜로 정의해야 합니다. 대표적으로 ① 고정 길이, ② 구분자(delimiter), ③ 길이 헤더 방식이 있고, 게임에서는 길이 헤더 방식이 표준적입니다. 수신 측은 링 버퍼 등에 데이터를 누적하다가, 헤더를 먼저 읽어 전체 패킷 크기를 확인하고, 그만큼 쌓이면 하나의 완성된 패킷으로 잘라 핸들러에 전달합니다. 아직 덜 도착했으면 다음 recv를 기다립니다.

37. 고정 길이, 구분자, 길이 헤더 방식의 차이는 무엇인가요?

고정 길이는 모든 메시지를 같은 크기로 정하는 방식으로, 구현이 가장 단순하지만 작은 데이터도 최대 크기로 보내야 해 대역폭 낭비가 큽니다. 구분자 방식은 개행 등 특정 바이트로 끝을 표시하는 방식으로, 텍스트 프로토콜에 적합하지만 바이너리 데이터에 구분자와 같은 값이 나올 수 있어 이스케이프 처리가 필요합니다. 길이 헤더 방식은 각 메시지 앞에 크기(+타입 ID)를 붙이는 방식으로, 가변 길이 바이너리 데이터를 효율적으로 다룰 수 있어 게임 프로토콜의 사실상 표준입니다. 단, 헤더의 크기 값은 조작될 수 있으므로 반드시 최대치 검증이 필요합니다.

38. 블로킹 소켓과 논블로킹 소켓의 차이는 무엇인가요?

블로킹 소켓은 요청한 작업이 완료될 때까지 함수가 반환되지 않고 스레드가 대기합니다(recv 시 데이터가 올 때까지 멈춤). 논블로킹 소켓은 즉시 처리할 수 없으면 WSAEWOULDBLOCK 같은 오류 코드와 함께 바로 반환되어, 스레드가 다른 일을 할 수 있습니다. 게임 클라이언트의 메인 루프에서 블로킹 recv를 호출하면 데이터가 없을 때 렌더링까지 멈추므로, 논블로킹 + 이벤트 통지 모델을 쓰거나 수신을 별도 스레드로 분리해야 합니다.

39. select, WSAEventSelect, IOCP의 차이는 무엇인가요?

셋 다 여러 소켓의 I/O를 다루는 모델이지만 방식과 확장성이 다릅니다. select는 소켓 집합을 등록하고 “읽을 수 있는 소켓이 있는지”를 폴링하는 방식으로, 매 호출마다 전체 집합을 검사하고 64개 제한(FD_SETSIZE)이 있어 규모에 약합니다. WSAEventSelect는 소켓 이벤트를 이벤트 객체에 연결해 통지받는 비동기 방식이지만, WaitForMultipleObjects의 64개 제한이 여전히 있습니다. IOCP는 “준비됐는지 알려주는” 게 아니라 완료 통지 모델로, 미리 비동기 I/O를 걸어두면 커널이 완료된 작업을 완료 큐에 넣어주고, 소수의 워커 스레드가 큐에서 꺼내 처리합니다. 소켓 수 제한이 없고 스레드를 CPU 코어 수준으로 유지할 수 있어 대규모 서버의 표준입니다.

40. IOCP가 대규모 서버에 유리한 이유는 무엇인가요?

핵심은 세 가지입니다. ① 완료 기반 모델이라 “확인 작업” 낭비가 없습니다. select처럼 수천 개 소켓을 매번 순회 검사하지 않고, 완료된 작업만 큐에서 꺼내 처리합니다. ② 스레드 수를 접속자 수와 분리할 수 있습니다. 연결당 스레드 1개 모델은 수천 명 접속 시 컨텍스트 스위칭 비용으로 무너지지만, IOCP는 코어 수 정도의 워커 스레드로 수만 연결을 처리합니다. ③ 커널이 완료 큐에서 스레드를 깨우는 방식(LIFO)이 캐시 친화적이고, 동시 실행 스레드 수를 지정해 과도한 경쟁을 막을 수 있습니다.

41. 클라이언트에서 네트워크 수신을 별도 스레드로 처리하는 이유는 무엇인가요?

메인 스레드(게임 루프)가 네트워크 대기 때문에 멈추면 안 되기 때문입니다. 블로킹 수신을 메인 루프에서 하면 패킷이 안 올 때 프레임 전체가 멈추고, 논블로킹 폴링만 하더라도 대량 수신 시 프레임 시간을 잡아먹을 수 있습니다. 수신 스레드가 소켓에서 데이터를 받아 패킷 단위로 조립해 큐에 넣고, 메인 스레드는 매 프레임 큐에서 꺼내 처리하는 구조가 일반적입니다. 이렇게 하면 네트워크 지연·스파이크가 렌더링 프레임에 직접 영향을 주지 않습니다.

42. 네트워크 스레드에서 받은 데이터를 게임 로직에 어떻게 전달하나요?

스레드 안전한 패킷 큐를 사이에 둡니다. 수신 스레드는 완성된 패킷을 큐에 push하고, 메인 스레드는 매 프레임 시작 시 큐를 통째로 비우며(swap 방식이면 락 시간 최소화) 패킷 타입별 핸들러로 분배합니다. 동기화는 뮤텍스로 시작하되, 생산자·소비자가 각각 하나라면 락프리 SPSC 큐로 최적화할 수 있습니다. 중요한 원칙은 게임 상태의 변경은 반드시 메인 스레드에서만 일어나게 하여, 게임 로직 전반에 락을 퍼뜨리지 않는 것입니다. 제 엔진의 멀티스레드 리소스 로딩에서도 같은 원칙(워커는 로드만, 등록은 메인 스레드)을 적용했습니다.

43. 직렬화란 무엇인가요?

메모리상의 객체(구조체, 클래스)를 전송하거나 저장할 수 있는 연속된 바이트 열로 변환하는 것입니다(역과정은 역직렬화). 메모리 객체는 포인터, 패딩, 플랫폼별 표현 차이를 담고 있어 그대로 보낼 수 없으므로, 각 필드를 정의된 순서와 형식으로 버퍼에 기록합니다. 수동으로 read/write 함수를 짜는 방식부터 Protobuf, FlatBuffers 같은 스키마 기반 라이브러리까지 있으며, 게임에서는 성능과 크기 때문에 바이너리 직렬화를 사용합니다.

44. 엔디언이란 무엇인가요?

멀티바이트 데이터를 메모리에 저장하는 바이트 순서입니다. 리틀 엔디언은 낮은 바이트를 낮은 주소에(x86/x64, 대부분의 ARM), 빅 엔디언은 높은 바이트를 낮은 주소에 저장합니다. 예를 들어 0x12345678을 리틀 엔디언은 78 56 34 12 순으로 저장합니다. 서로 다른 엔디언의 기기가 바이트를 그대로 주고받으면 값이 완전히 달라지므로, 네트워크 프로토콜은 바이트 순서를 명시적으로 통일해야 합니다.

45. 호스트 바이트 오더와 네트워크 바이트 오더의 차이는 무엇인가요?

호스트 바이트 오더는 해당 머신의 CPU가 사용하는 순서(x86은 리틀 엔디언)이고, 네트워크 바이트 오더는 프로토콜 표준으로 정해진 빅 엔디언입니다. IP 주소나 포트 번호를 소켓 구조체에 넣을 때 htons/htonl로 변환하고, 받을 때 ntohs/ntohl로 되돌립니다. 다만 게임의 애플리케이션 페이로드는 서버·클라이언트가 모두 리틀 엔디언 플랫폼이라면 리틀 엔디언으로 통일해 변환 비용을 없애는 경우도 많습니다. 핵심은 “프로토콜 문서에 바이트 순서를 명시하고 일관되게 지키는 것”입니다.

46. 패킷 구조체를 그대로 전송하면 어떤 문제가 있나요?

① 구조체 패딩: 컴파일러와 정렬 설정에 따라 멤버 사이에 삽입되는 패딩이 달라, 양쪽의 메모리 레이아웃이 일치하지 않을 수 있습니다. ② 엔디언: 플랫폼 간 바이트 순서가 다르면 값이 깨집니다. ③ 포인터·가상 함수 테이블: 주소는 상대 프로세스에서 무의미하므로 절대 포함하면 안 됩니다. ④ 타입 크기 차이: long 같은 타입은 플랫폼별 크기가 다릅니다. 그래서 #pragma pack(1)과 고정 크기 타입(int32_t 등)으로 레이아웃을 못 박거나, 필드 단위 직렬화를 사용해야 합니다. memcpy 방식은 빠르지만 프로토콜 변경에 취약하므로, 버전 관리까지 고려하면 명시적 직렬화가 더 안전합니다.

47. 구조체 패딩이 네트워크 패킷에 미치는 영향은 무엇인가요?

패딩은 CPU의 정렬된 접근을 위해 컴파일러가 멤버 사이·끝에 넣는 빈 공간인데, 이것이 패킷에 포함되면 두 가지 문제가 생깁니다. 첫째, 송수신 양측의 컴파일러·플랫폼·pack 설정이 다르면 같은 구조체 정의라도 레이아웃이 어긋나 데이터가 깨집니다. 둘째, 의미 없는 패딩 바이트가 대역폭을 낭비하고, 초기화되지 않은 패딩 영역이 그대로 나가면 메모리 내용 유출이 될 수도 있습니다. 그래서 전송용 구조체는 pack(1)로 패딩을 제거하고 멤버를 크기 순으로 배치하거나, 직렬화 함수로 필드만 순서대로 기록합니다.

48. 패킷 크기를 검증해야 하는 이유는 무엇인가요?

패킷 헤더의 크기 필드는 클라이언트(또는 공격자)가 임의로 조작할 수 있는 신뢰할 수 없는 입력이기 때문입니다. 크기 값을 그대로 믿고 버퍼를 할당하거나 복사하면, 거대한 값으로 메모리 고갈을 유발하거나, 실제 데이터보다 큰 값으로 버퍼 오버런/오버리드를 일으킬 수 있습니다. 최소 크기(헤더보다 작은 패킷), 최대 크기(프로토콜 상한), 그리고 패킷 타입별 기대 크기와의 일치 여부까지 검증하고, 어긋나면 해당 연결을 끊는 것이 원칙입니다.

49. 잘못된 패킷이나 악성 패킷을 어떻게 방어하나요?

계층적으로 방어합니다. ① 구조 검증: 크기, 타입 ID 유효 범위, 필드 값 범위(음수 수량, 범위 밖 좌표 등)를 역직렬화 단계에서 검사합니다. ② 상태 검증: 로그인 전에 게임 패킷을 보내는 등 현재 세션 상태에서 불가능한 패킷을 거부합니다. ③ 빈도 제한: 초당 요청 수 제한으로 flooding을 막습니다. ④ 서버 권위: 클라이언트가 보낸 값은 요청일 뿐, 판정과 결과는 서버가 자체 데이터로 계산합니다. ⑤ 위반 시 정책: 프로토콜 위반은 버그가 아니라 공격 신호로 보고 즉시 연결 종료·로깅하는 것이 안전합니다.

50. 연결이 끊겼다는 것을 어떻게 감지하나요?

정상 종료는 상대가 FIN을 보내므로 recv가 0을 반환해 즉시 알 수 있습니다. 문제는 비정상 단절(랜선 뽑힘, 전원 꺼짐, 무선 끊김)로, 이때는 아무 신호도 오지 않아 TCP만으로는 오래 감지하지 못합니다(송신을 시도해야 재전송 실패 끝에 오류가 나고, 수신만 대기 중이면 영원히 모를 수 있음). 그래서 애플리케이션 레벨의 heartbeat로 “일정 시간 내 응답 없음 = 단절”을 직접 정의하는 것이 실무 표준입니다. UDP는 연결 개념 자체가 없으므로 처음부터 heartbeat/타임아웃 방식만 가능합니다.

51. Heartbeat 패킷은 왜 필요한가요?

세 가지 이유입니다. ① 비정상 단절 감지: TCP는 상대가 조용히 사라지면 알 수 없으므로, 주기적 ping-pong으로 N초 무응답 시 끊긴 것으로 판정합니다. ② NAT/방화벽 매핑 유지: 트래픽이 없으면 중간 장비가 매핑을 지워버리므로, keep-alive로 경로를 살려둡니다. ③ RTT 측정: heartbeat 왕복 시간으로 지연을 측정해 지연 보정이나 서버 선택에 활용할 수 있습니다. OS 레벨 TCP keepalive도 있지만 기본 주기가 매우 길어(기본 2시간), 게임은 애플리케이션 레벨에서 수 초 단위로 직접 구현합니다.

52. 재접속 시스템은 어떻게 설계하나요?

핵심은 “소켓 연결”과 “게임 세션”을 분리하는 것입니다. 서버는 연결이 끊겨도 세션(캐릭터 상태, 진행 정보)을 즉시 파괴하지 않고 유예 시간 동안 보존합니다. 클라이언트는 끊김을 감지하면 세션 토큰(로그인 시 발급받은 인증 정보)을 가지고 재접속을 시도하고, 서버는 토큰을 검증해 기존 세션에 새 소켓을 다시 연결합니다. 이후 서버가 현재 상태 스냅샷을 내려보내 클라이언트를 동기화합니다. 유예 시간 초과 시에만 정상 로그아웃 처리하며, 전투 중 악용(불리하면 뽑기) 방지를 위해 재접속 중에도 캐릭터를 게임 세계에 남겨두는 등의 정책이 함께 필요합니다.

53. 클라이언트 요청의 중복 처리는 어떻게 방지하나요?

요청에 고유 식별자(Request ID 또는 시퀀스 번호)를 부여하고, 서버가 처리한 ID를 기억해 같은 ID의 재요청은 무시하거나 이전 응답을 재전송합니다(멱등성 보장). 중복은 재전송 로직, 사용자의 연타, 재접속 후 재시도 등에서 자연스럽게 발생하므로 “중복은 반드시 온다”를 전제로 설계해야 합니다. 결제·아이템 지급처럼 치명적인 요청은 DB 트랜잭션 레벨에서도 유니크 제약으로 이중 방어합니다. 클라이언트 쪽에서도 응답이 올 때까지 버튼 비활성화로 1차 차단을 합니다.

54. 요청과 응답을 어떻게 매칭하나요?

요청에 ID를 담아 보내고, 서버가 응답에 같은 ID를 실어 돌려줍니다. 클라이언트는 “보낸 요청 ID → 완료 콜백” 맵을 유지하다가, 응답 도착 시 ID로 콜백을 찾아 실행하고 맵에서 제거합니다. 이렇게 하면 여러 요청이 동시에 나가 있어도, 응답이 순서 없이 도착해도 정확히 매칭됩니다. 일정 시간 응답이 없으면 타임아웃 처리(재시도 또는 실패 UI)도 이 맵 기반으로 구현합니다. 알림성 패킷(서버가 일방적으로 보내는 상태 갱신)은 매칭 없이 타입별 핸들러로 처리하므로, 요청-응답형과 통지형을 프로토콜 설계 단계에서 구분해두는 것이 좋습니다.

55. Request ID나 Sequence Number는 왜 필요한가요?

역할이 조금 다릅니다. Request ID는 요청-응답 매칭과 중복 처리 방지(멱등성)가 목적입니다. Sequence Number는 패킷 스트림의 순서 판별이 목적으로, UDP에서 패킷의 유실 감지, 중복 제거, 최신/과거 판별(오래된 위치 패킷 폐기)에 사용됩니다. 추가로 시퀀스 번호는 재전송 공격(같은 패킷을 캡처해 다시 쏘는 replay attack) 방어에도 기여합니다. 요약하면 “누구에 대한 응답인가”는 Request ID, “어떤 순서의 데이터인가”는 Sequence Number가 답합니다.


3. 온라인 게임 네트워크

56. 게임에서 TCP와 UDP를 각각 어디에 사용하나요?

기준은 “반드시 도착해야 하는가 vs 최신 값만 중요한가”입니다. TCP는 로그인·인증, 인벤토리/재화 변동, 채팅, 매칭, 상점 등 유실되면 안 되고 순서가 중요한 트랜잭션성 데이터에 씁니다. UDP는 위치·회전 동기화, 이동 입력, 음성처럼 고빈도로 갱신되고 한두 개 유실돼도 다음 데이터로 대체되는 실시간 데이터에 씁니다. 크로스파이어 같은 FPS는 이동·사격의 반응성이 생명이라 인게임 전투는 UDP(+신뢰성 채널), 로비·아웃게임은 TCP 계열로 나누는 구조가 일반적입니다.

57. 채팅, 인벤토리, 위치 동기화에는 각각 어떤 프로토콜이 적합한가요?

채팅은 유실되면 안 되고 순서도 중요하지만 수십 ms 지연은 문제없으므로 TCP(또는 reliable-ordered UDP 채널)가 적합합니다. 인벤토리는 아이템 지급·소모가 유실되거나 중복되면 치명적이므로 반드시 신뢰성 있는 전송 + 서버 권위 + 멱등 처리가 필요해 TCP가 적합합니다. 위치 동기화는 초당 수십 회 갱신되고 최신 값만 의미가 있으므로, 유실된 과거 데이터를 재전송받는 것 자체가 낭비인 unreliable UDP가 적합합니다.

58. 신뢰성이 필요한 데이터와 실시간성이 필요한 데이터를 구분해주세요.

신뢰성 필수 데이터는 “한 번의 이벤트가 상태를 영구히 바꾸는 것”입니다. 아이템 획득, 골드 변동, 스킬 습득, 킬/데스 판정 결과, 매치 시작/종료 등으로, 유실 시 상태 불일치가 발생합니다. 실시간성 우선 데이터는 “계속 덮어써지는 연속적인 상태”입니다. 위치, 시선 방향, 애니메이션 상태 등으로, 하나가 유실돼도 다음 프레임 데이터로 자연히 복구됩니다. 애매한 것도 있는데, 예를 들어 발사 이벤트는 실시간성도 필요하고 유실도 안 되므로 reliable하되 순서 대기 없이(reliable-unordered) 보내는 식으로, 데이터 성격별로 채널을 설계하는 것이 중요합니다.

59. 클라이언트 권위와 서버 권위의 차이는 무엇인가요?

게임 상태의 최종 결정권을 누가 갖느냐의 차이입니다. 클라이언트 권위는 클라이언트가 계산한 결과(“내가 맞췄다”, “내 위치는 여기다”)를 서버가 그대로 신뢰하는 구조로, 구현이 쉽고 반응이 즉각적이지만 조작된 클라이언트에 무방비입니다. 서버 권위는 클라이언트는 입력·요청만 보내고, 이동 가능 여부·명중 판정·데미지 계산 등 모든 판정을 서버가 수행하는 구조입니다. 온라인 경쟁 게임은 서버 권위가 원칙이고, 대신 입력→서버→응답의 왕복 지연을 숨기기 위해 클라이언트 사이드 예측을 함께 사용합니다.

60. 서버 권위 구조가 치트 방지에 유리한 이유는 무엇인가요?

클라이언트는 유저의 손에 있는 프로그램이라 메모리 조작, 패킷 변조, 코드 후킹이 언제든 가능하기 때문입니다. 클라이언트가 보내는 모든 데이터는 신뢰할 수 없는 입력이며, 클라이언트 권위 구조에서는 “체력 9999”, “순간이동”, “벽 관통 명중” 같은 조작 값이 그대로 게임 상태가 됩니다. 서버 권위에서는 클라이언트가 아무리 조작해도 “요청”만 조작할 수 있을 뿐, 서버가 자신이 가진 정답 데이터(위치, 스탯, 맵 지오메트리)로 검증·계산하므로 불가능한 요청은 거부됩니다. 다만 월핵(정보 노출)이나 에임봇(입력 자동화)처럼 서버 권위만으로 못 막는 치트도 있어, 관심 영역 필터링(안 보이는 적 정보는 아예 안 보냄)과 통계적 탐지가 보완적으로 필요합니다.

61. 위치 동기화에서 지연이 발생하면 어떻게 보정하나요?

역할별로 다른 기법을 조합합니다. 내 캐릭터는 클라이언트 사이드 예측으로 입력 즉시 움직이고, 서버 판정과 어긋나면 리콘실리에이션으로 교정합니다. 다른 플레이어는 스냅샷 보간으로, 도착한 위치를 즉시 찍지 않고 약간 과거 시점을 부드럽게 재생합니다. 패킷이 늦어 보간할 데이터가 떨어지면 외삽으로 잠시 추정하되, 오차가 커지기 전에 제한합니다. 서버 쪽에서는 FPS의 경우 랙 컴펜세이션(사격자가 본 과거 시점으로 되감아 명중 판정)까지 더해집니다. 요약하면 “내 것은 예측, 남의 것은 보간, 판정은 서버가 시간을 되감아 보정”입니다.

62. 보간, 외삽, 예측의 차이는 무엇인가요?

보간(interpolation)은 이미 받은 두 스냅샷 사이를 부드럽게 채우는 것으로, 확실한 데이터만 쓰므로 정확하지만 의도적으로 약간 과거를 보여줍니다(보간 지연). 외삽(extrapolation)은 마지막 데이터의 속도·방향으로 미래를 추정하는 것으로, 지연은 없지만 상대가 방향을 틀면 틀린 위치를 보여주고 되돌리는 팝핑이 생깁니다. 예측(prediction)은 주로 내 캐릭터에 쓰는 개념으로, 서버 확정을 기다리지 않고 내 입력의 결과를 로컬에서 즉시 시뮬레이션하는 것입니다. 원격 캐릭터에는 보간을 기본으로 하고 데이터 공백 시에만 짧게 외삽하는 것이 일반적입니다.

63. 클라이언트 사이드 예측이란 무엇인가요?

서버 권위 구조에서 입력 반응성을 살리는 기법입니다. 입력을 서버로 보내고 응답을 기다렸다가 움직이면 RTT만큼 조작이 밀리는데, 예측을 쓰면 클라이언트가 입력을 보냄과 동시에 같은 이동 로직을 로컬에서 실행해 즉시 움직입니다. 이때 각 입력에 시퀀스 번호를 붙여 보관해둡니다. 이후 서버가 “N번 입력까지 처리한 권위적 위치”를 보내오면, 내 예측과 비교해 맞으면 그대로 두고 틀리면 리콘실리에이션으로 교정합니다. 전제 조건은 클라이언트와 서버가 동일한 이동 시뮬레이션 코드를 결정론적으로 실행할 수 있어야 한다는 것입니다.

64. 서버 리콘실리에이션이란 무엇인가요?

예측이 틀렸을 때의 교정 절차입니다. 서버가 보내온 권위적 상태는 이미 과거(내가 보낸 N번 입력 시점)의 것이므로, 단순히 그 위치로 스냅하면 내 캐릭터가 RTT만큼 뒤로 순간이동하는 러버밴딩이 생깁니다. 리콘실리에이션은 ① 서버가 확인한 N번 시점 상태로 되돌린 뒤, ② 보관해둔 N+1번 이후의 미확인 입력들을 다시 적용(재시뮬레이션)해 현재 시점을 복원합니다. 예측이 맞았다면 결과가 동일해 아무 일도 없던 것처럼 보이고, 틀렸다면(서버에서 밀쳐졌거나 판정 차이) 최소한의 어긋남만 보정됩니다.

65. 스냅샷 보간이란 무엇인가요?

서버가 주기적으로 보내는 월드 상태(스냅샷)를 받아, 원격 엔티티들을 의도적으로 약간 과거 시점에서 부드럽게 재생하는 기법입니다. 예를 들어 서버가 20Hz(50ms 간격)로 스냅샷을 보내면, 클라이언트는 현재 시각보다 100ms 정도 뒤의 렌더 타임을 잡고, 그 시점을 감싸는 두 스냅샷 사이를 보간해 그립니다. 버퍼에 항상 스냅샷 1~2개가 여유로 있으므로 패킷 하나쯤 유실되거나 지터가 있어도 움직임이 끊기지 않습니다. 대가는 “남을 보는 화면이 항상 약간 과거”라는 점이고, FPS에서 이 시차는 서버의 랙 컴펜세이션이 감안해줍니다.

66. 패킷 유실과 지터는 게임 플레이에 어떤 영향을 주나요?

유실은 데이터 공백을 만듭니다. TCP라면 재전송+HOL 블로킹으로 지연 스파이크(순간 멈칫 후 몰아서 처리)가 되고, UDP라면 해당 스냅샷이 비어 캐릭터 워프·끊김으로 나타납니다. 지터는 패킷 도착 간격의 흔들림으로, 일정한 재생을 방해해 움직임이 불규칙하게 빨라졌다 느려졌다 하게 만듭니다. 대응책으로 유실에는 스냅샷 보간(다음 데이터로 자연 복구)과 중요 데이터의 신뢰성 채널을, 지터에는 지터 버퍼(약간의 지연을 추가해 도착 간격을 평탄화)를 사용합니다. 평균 핑보다 지터가 체감 품질을 좌우하는 경우도 많습니다.

67. UDP 패킷의 순서를 어떻게 판별하나요?

애플리케이션 헤더에 시퀀스 번호를 넣어 판별합니다. 송신 측이 패킷마다 증가하는 번호를 붙이고, 수신 측은 마지막으로 처리한 번호와 비교해 최신/과거/중복을 구분합니다. 주의할 점은 시퀀스 번호가 유한 비트라 랩어라운드(65535 → 0)가 발생한다는 것으로, 단순 대소 비교가 아니라 차이를 반 범위와 비교하는 순환 비교 로직이 필요합니다. 유실 감지가 필요하면 ACK와 함께 비트필드(최근 32개 수신 여부)를 실어 보내는 방식으로 어떤 패킷이 안 왔는지도 파악할 수 있습니다.

68. 오래된 위치 패킷은 어떻게 처리하나요?

버립니다. 위치는 최신 값만 의미가 있으므로, 시퀀스 번호가 이미 처리한 것보다 과거인 패킷은 적용하지 않고 폐기하는 것이 원칙입니다. 늦게 도착한 과거 위치를 적용하면 캐릭터가 순간적으로 뒤로 튀는 현상이 생깁니다. 다만 스냅샷 보간 구조라면 “재생 시점보다 과거지만 보간 버퍼에는 유용한” 패킷은 버퍼에 넣어 활용할 수 있으므로, 정확히는 “현재 재생에 더 이상 기여할 수 없는 패킷을 폐기”한다고 말하는 것이 맞습니다.

69. 치트 클라이언트가 잘못된 요청을 보내면 서버는 어떻게 해야 하나요?

원칙은 “검증 → 거부 → 기록 → 제재”입니다. 서버는 모든 요청을 자신의 권위 데이터로 검증합니다. 이동이면 속도·이동 가능 지형, 공격이면 사거리·쿨타임·시야, 아이템이면 소지 여부·수량을 확인하고, 불가능한 요청은 처리하지 않습니다. 이때 중요한 판단이 있는데, 단순 거부로 끝낼지(네트워크 오차로 인한 정상 클라이언트의 경계 케이스일 수 있음) 위반으로 기록할지입니다. 물리적으로 불가능한 요청(존재하지 않는 패킷 타입, 음수 수량)은 명백한 조작이므로 즉시 연결 종료 및 로깅하고, 애매한 경우는 누적 카운트로 패턴을 추적해 임계치 초과 시 제재합니다. 클라이언트에게 “무엇이 걸렸는지” 상세히 알려주지 않는 것도 중요합니다. 치트 개발자에게 탐지 로직을 학습시켜주는 셈이 되기 때문입니다.

70. 인벤토리 아이템 지급 요청을 클라이언트가 결정하게 하면 왜 위험한가요?

클라이언트는 조작 가능하므로, “지급”이라는 상태 변경의 결정권을 주면 임의의 아이템을 무한 생성할 수 있게 되기 때문입니다. 예를 들어 “몬스터를 잡았으니 아이템 X를 주세요”를 클라이언트가 보내는 구조라면, 패킷 변조로 몬스터를 잡지 않고도 전설 아이템 지급 요청을 무한히 보낼 수 있습니다. 올바른 구조는 클라이언트가 행동(공격 입력)만 보내고, 몬스터 처치 판정 → 드롭 결정 → 인벤토리 반영까지 전부 서버가 자체적으로 수행한 뒤 결과만 통보하는 것입니다. 이는 게임 내 경제와 직결되므로, 재화·아이템 관련 로직은 서버 권위의 예외가 없어야 하고, 지급 처리는 중복 요청에도 한 번만 반영되도록 멱등하게 설계해야 합니다.