게임 서버 프로그래밍 교과서(배현직 저)를 보고 공부하면서 정리한 내용입니다. 제 뇌피셜도 있고, 도서에서 알기 힘든 부분은 검색을 통해 보충한 부분도 있으므로 잘못된 정보가 있다면 언제든지 댓글 남겨주세요!!


네트워크를 구성하는 기기

네트워크 구조

컴퓨터 네트워크는 크게 단말기(terminal, endpoint)와 네트워크 기기로 구성됨.
네트워크는 연결 형태에 따라 다음과 같이 나눌 수 있음

  • one to one
    가장 단순한 구조. 두 단말이 하나의 케이블로 연결되어 있다.
  • ring tolpology
    여러 단말이 환형으로 연결되어있는 구조
  • start topology
    스위치를 가운데에 두고 여러 단말이 이 스위치와 연결되어 있는 구조

어떠한 형태의 네트워크 구조라도 OSI 모델 표준만 지켜주면 네트워킹이 가능하다. LAN을 구축하고 싶은 경우, OSI 모델의 2계층을 지켜서 구축하면 된다.

OSI 모델

OSI 모델에는 1계층 ~ 7계층이 존재한다.

  • Physics layer
    하드웨어단. 데이터를 보낼 때 어떤 파형으로 보낼지 등을 정의
  • Data link layer
    두 개의 직접 연결된 노드 사이에서 데이터를 안전하게 전송하는 역할(LAN을 가능케 함)
  • Network layer
    네트워크 상에서 패킷의 라우팅 및 전달을 담당(WAN)
  • Transport layer
    데이터의 흐름 제어, 종단 간 통신 관리
  • Session layer, Presentation layer, Application layer
    데이터 통신과 응용프로그램 간 서비스 제공 및 데이터 표현과 처리 담당. 게임 서버 개발자가 정의할 수 있는 부분이다.

2계층에서 데이터는 프레임(헤더 + 페이로드) 단위로 보내짐. 헤더에는 송수신 주소 정보가 있고, 페이로드에는 보내고자 하는 데이터가 담겨 있음.

PreambleSFDDASALen/TypeData(+padding)FCS
7 bytes1 byte6 bytes6 bytes2 bytes45 ~ 1500 bytes4 bytes

이렇게 구축한 LAN은
1. 모든 단말기마다 고유 주소를 만들기 어려움
2. 스위치 당 연결 가능한 단말기 수가 정해져 있음
때문에 LAN과 LAN을 연결하여 광역 통신망; WAN을 구축하는 것

LAN 안에서 각 단말은 서로 구별되는 고유 주소를 담고 있지만, 다른 LAN에 속한 단말기와는 효과적으로 통신할 수 없음. 이에 대한 규약을 정의한 것이 3계층이다.

3계층 Network layer

3계층은 IP(Internet protocol)와 라우터(계층적 구성)를 이용해 데이터를 패킷 단위로 전송한다.

참고) IPv4, IPv6, ICMP

인터넷

무수히 많은 라우터와 스위치, 다른 종류의 통신 회선(랜선, 광섬유, 무선, 전화선 등)이 3계층의 IP에 따라 온 지구를 덮고 있다.
이것을 인터넷이라 한다.

네트워크 데이터

2계층은 프레임, 3계층은 패킷단위로 데이터를 송수신한다. 그러나 응용프로그래머 입장에서는 이를 직접 다루는 일은 흔하지 않다. 응용프로그래머는 다음과 같은 두 가지의 데이터 전송 형식(스트림, 메시지)을 주로 사용한다.

스트림 형식

데이터를 일련의 연속된 바이트 스트림으로 취급하고 이를 보내는 형식. 데이터의 크기나 경계에 대한 제한이 없다.(데이터를 중간에 구분하지 않는다.) 따라서 이 형식으로 데이터를 주고받을 때에는 다음과 같은 방식을 사용하는 경우가 많다.

  • 헤더에 크기 정보를 넣기
    송신 : 헤더에 보낼 데이터의 크기정보를 넣고 이후에 데이터를 넣는다.
    수신 : 헤더에서 읽은 크기 정보만큼의 데이터를 이후 스트림에서 불러온다.
  • 구분자를 넣기
    송신 : 보낼 데이터를 넣고 이후에 구분자를 넣는다.
    수신 : 구분자가 들어올 때까지 데이터를 읽는다.(이때, 주고받은 데이터에는 구분자와 동일한 부분이 없어야 함!)

메시지 형식

메시지 단위로 데이터를 주고받은 형식. 크기와 경계에 제한이 있기에 각각의 패킷은 독립적으로 처리된다.
게임 서버에서는 매번 스트림을 열고 닫고 수신대기하는 것보다 여러 클라이언트에게 정해진 길이의 메시지를 뿌리는 일이 많기 때문에 보통 메시지 형식으로 데이터를 전달한다.

IP 패킷의 구조

앞서 살펴본 스트림과 메시지에서는 데이터의 최대 크기가 정해져있지 않다. 그러나 3계층 네트워크 레이어에서는 IP(인터넷 프로토콜)에 따라 패킷의 최대 크기가 결정되어 있는데, OS는 이를 프레그멘테이션(Fragmentation)하여 (데이터를 MTU;최대전송단위에 맞게 나누고 헤더 붙이기) IP 패킷들을 만들고 이를 보낸다.

한편 IP 패킷(v4)의 구조는 다음과 같다.

위와 같이 IP 패킷에는 패킷의 총 길이, 체크섬, 송수신지의 ip 주소, 실제로 보낼 데이터들이 포함되어 있다.

네트워크 식별자

IPv4

255.255.255.255 형태로 나타나는 IP 주소 형식. 최대 256^4 ≈ 43억개의 주소를 나타낼 수 있다.(1바이트 숫자 4개)

IPv6

ffff:ffff:ffff:ffff:0000:0000:0000:0000형태로 나타나는 IP 주소 형식(연속으로 이어지는 0의 경우 생략 가능). 2바이트 숫자 8개로 이루어져 있는 주소체계로, 최대 2(2 * 8 * 8)개의 주소를 나타낼 수 있다.

출처

포트

한 단말 안에서 여러 프로세스 간 구별하기 위해 사용하는 주소. 2바이트 정수 공간을 가지며, IP주소 뒤에 ':'과 함께 붙여 사용한다.

윈도우에서 사용 중인 포트들을 보고싶다면 netstat -ano 명령어를 입력하면 된다.

host name과 DNS

일반적으로 IP주소:포트로 다른 엔드포인트에 접속하는 것은 사용자 입장에서는 매우 불편하다. 이때 사용되는 것이 host name과 DNS 서버이다.

네트워크의 품질과 특성

품질 저해 요소

라우터는 한번에 처리할 수 있는 수 이상의 패킷이 들어오면

  • 처리할 수 있는 수 이상의 패킷들을 버린다; 패킷 유실
  • 아직 처리하지 못한 패킷을 매모리에 누적시킨다.

후자의 경우 장시간 지속 시 멈춰버리거나 재부팅되는 경우가 있기 때문에, 대부분의 라우터는 전자의 전략을 택한다. 모든 유저의 네트워크 품질을 저하시키는 것보다 일부 사용자에 대해서만 품질을 떨어뜨리는 것이 낫기 때문.

라우터 말고 유/무선 회선에서 일어나는 일련의 과정 때문에 품질이 저하되기도 한다. 수신측에서의 디지털 신호는 OSI 1계층에 의해 아날로그 신호로 바뀌며, 이후 다시 송신측에서 디지털 신호로 바뀐다. 이 과정에서 잡음(노이즈)가 섞이거나, 신호가 약해질 수 있다. 2계층과 3계층에서는 체크섬 검사 등의 방법을 통해 데이터를 수정할 수 있는데, 수정조차 어려운 경우에는 프레임 혹은 패킷을 버린다.

정리하자면

  • 네트워크 기기가 처리할 수 있는 한계를 넘어가면 패킷 유실이 발생할 수 있다.
  • 회선 신호가 약하거나 잡음이 섞이면 패킷 유실이 발생할 수 있다.

전송 속도와 전송 지연 시간

스루풋과 레이턴시 역시 네트워크의 성능을 판단할 때 사용될 수 있다.

  • 스루풋(전송 속도) : 단위시간 당 전송되는 데이터의 양. bps(bit per second), Kbps, Mbps 등의 단위를 사용한다.
  • 대역폭(bandwidht) : 특정 시간 내 전송될 수 있는 데이터의 최대 용량. 최대 스루풋을 결정하는 요소 중 하나이다.
  • 레이턴시 : 1패킷 당 단말 간 걸리는 시간의 총합. ms 단위를 자주 쓴다.

위 설명에 따라 다음 두 명제가 성립한다.

  • 두 단말 간 레이턴시는 두 단말 사이에 있는 네트워크 기기 레이턴시들의 총합이다.
  • 두 단말 간 대역폭은 두 단말 사이를 잇는 네트워크 기기 중 최소 대역폭이다. (병목; bottle neck)

따라서 좋은 네트워크란,
1. 스루풋이 우수해야한다.
2. 패킷 유실률이 적어야 한다.
3. 레이턴시가 낮아야 한다.

무선 네트워크의 품질

일반적인 상황에서 무선네트워크를 통한 데이터 송수신을 하면 레이턴시가 불균형하게 나타난다. 그 이유는 CSMA(Carrier Sense Multiple Access) 프로토콜을 따르기 때문이다.

  • Carrier Sense(캐리어 감지)
    데이터 전송 전 통신 매체가 사용중인지 확인. 감지되는 전파가 없으면 데이터를 보내고 있으면 잠시 기다렸다가 이후에 전송한다.
  • Multiple Access : 다중 접속

한편 CSMA 프로토콜은 여러 변형이 있으며, 그중 CSMA/CD와 CSMA/CA 두 프로토콜이 자주 사용된다.

  • CSMA/CD(Collision Detection) - 주로 유선에 사용
  • CSMA/CA(Collision Avoidance) - 주로 무선에 사용

네트워크에서의 데이터 송수신

UDP 네트워킹

User Datagram Protocol은 사용자가 정의한 데이터그램을 상대방에게 보낼 수 있도록 하는 프로토콜이다. Connectionless하기 때문에 스트림을 열지 않으며, 이에 따라 전송 순서가 뒤바뀌거나 일부 패킷이(데이터그램이) 유실될 수도 있다.
게임 서버에서는 캐릭터의 이동 정보(위치 등)를 보낼 때 UDP를 사용할 수 있다. 유실이 되더라도 이후에 들어온 정보로 쉽게 보정할 수 있기 때문이다.

UDP 소켓은 ip 주소(혹은 도메인 네임)과 포트 번호만 있으면 쉽게 데이터를 송수신할 수 있다. 또한 다대다 연결이 가능하다.

다만 데이터 유실 또는 순서 뒤바뀜 문제가 생길 수 있기에 이러한 문제가 중요한 곳에는 적용하기 힘들다는 단점이 있다.

// 송신 측 예시
main()
{
	s = socket(udp)
    
    s.bind(any_port)
    s.sendTo("수신 ip addr:1212", "data")
    
    s.close()
}
// 수신 측 예시
main()
{
	s = socket(udp)
    
    s.bind(1212)
    result = s.recv()
    
    s.close()
}

TCP 네트워킹

Transmission Control Protocol은 송신 측 데이터와 수신 측 데이터가 완전 동일함을 보장해주는 프로토콜이다. UDP와 달리 연결이 필요하기에 데이터를 stream 형태로 송수신한다.

송신 데이터는 세그먼트 단위로 쪼개져 3계층 IP 패킷에 담기게 된다. 수신측에서는 IP 패킷에서 세그먼트를 꺼낸 후 세그먼트를 받았다는 응답을 송신자에게 반송한다. ack 응답이 없을 경우, 수신자는 세그먼트를 재전송한다.(OS 단)

TCP 소캣은 UDP와 달리 일대일 연결만 가능하다.

// 송신 측 예시
main()
{
	s = socket(tcp)
    s.bind(any_port)
    s.connect("수신 ip addr:1212")
    s.send("data")
    
    s.close()
}
// 수신 측 예시
main()
{
	conn_s = socket(tcp)
    conn_s.listen(1212)
    s = conn_s.accept()
    while(true)
    {
    	result = s.recv()
        if(r.length == 0) break
    }
    
    s.close()
}

UDP와 TCP에서의 패킷 유실

앞서 설명했듯, UDP의 데이터그램과 TCP의 세그먼트는 3계층 Network Layer의 IP MTU(Maximun Transmission Unit)에 맞춰 패킷으로 분할되어 전달되는데,
1~3계층에서 패킷이 유실될 수 있음에 따라 UDP와 TCP는 이에 각각 다른 전략을 취한다.

UDP는 분할된 패킷 중 1개의 패킷만 유실되어도 수신자 측은 전체 데이터그램이 유실된 것으로 판단한다(따라서 보내고자 하는 데이터그램의 크기가 클수록 유실될 확률도 높다). 반면 TCP는 유실된 패킷의 ack이 도착하지 않기 때문에 해당 패킷을 재전송한다. 수신측에서는 다른 패킷들이 다 도착하더라도 재전송된 패킷이 도착하기 전까지 세그먼트를 재조립하지 못한다.

정리하자면

  • UDP는 패킷이 유실될 경우 해당 데이터그램을 그냥 버려버리기 때문에,
    UDP 레이턴시 = 네트워크의 기대 레이턴시
  • TCP는 패킷을 재전송하고 수신자는 이를 기다리므로,
    TCP 레이턴시 = 네트워크의 기대 레이턴시 + 패킷 유실률 * 재전송 대기시간

따라서 네트워크 게임에서는 레이턴시에 민감하고, 패킷 유실이 있어도 괜찮은 곳에서 UDP를 주로 사용하고, 그 외의 모든 경우에는 TCP를 사용한다.
UDP를 사용하기에 적합한 예시에는 캐릭터 이동, 음성 및 화상데이터 전송 등이 있다.

게임에서 주로 사용되는 메시지 형식

게임 서버에서의 메시지 형식은 크게 사람이 이해할 수 있는 텍스트 형식과 이해하기 힘든 바이너리 형식으로 나눌 수 있다.

텍스트

텍스트 형식은 다음과 같이 사람이 이해할 수 있는 메시지 형식이다.

BuyItem<LF>
Sword<LF>
1<LF>
<0x00>

예전에는 위와 같이 게임 개발자가 자체적으로 정의하는 경우가 많았지만, 요즘에는 JSON, XML 등 표준화된 형식을 쓰는 것이 일반적이다. WAS로 게임서버를 구현하고자 하는 경우, HTTP restful api를 사용하기도 한다.

정해져 있는 형식에 맞춰 Parser를 동원하는 것이 일반적이다.

바이너리

이진 형식은 다음과 같이 사람이 이해하기 힘든 형식이다.

0x01 | 0x0023 | 0x0001
// 각각의 필드는 BuyItem, Sword, 1을 의미한다고 가정

별도의 Parser가 필요 없기 때문에 성능 면에서 우수하고 통신량도 적다. 하지만 디버깅이 까다롭다는 단점이 존재한다.

주소 변환

Network Address Translation이란 다른 단말로 전송되던 패킷의 송수신 주소를 다른 것으로 변환하는 것을 말한다. 이를 담당하는 기기를 NAT 라우터라고 하며, 공유기가 이 역할을 수행하기도 한다.

  • Port Mapping Entry
    NAT 라우터 안에 있는 송수신자의 주소 맵핑 테이블
  • Hole Punching(=Port Punching)
    두 단말이 직접 통신할 수 있도록 NAT 장치의 포트를 열도록 하는 과정. 내부의 Port Mapping Entry에 새로운 맵핑 정보가 들어가는 과정이 포함됨.

4G, LTE, 5G같은 모바일 셀룰러 통신은 가정집 공유기보다 매우 많은 수의 기기를 지원해야 한다. 따라서 대부분의 ISP는 대규모 사용자용 NAT 라우터인 Carrier-grade NAT를 사용하고 있다.

더 읽을거리

Data communications and networking 도서 네트워크 교과서

IPv4와 IPv6 모두를 잘 지원하는 프로그램 개발
IPv4와 IPv6 모두를 잘 지원하는 프로그램 개발
Dual Stack과 NAT64/DNS64에 대한 이해

RUDP 구현 예시 : 프라우드넷, RakNet

홀펀칭 기술 : Full cone NAT, uPNP

클라우드 서비스에서의 NAT : L4 스위치와 로드밸런서

원격 프로시저 호출, 원격 메서드 호출(Remote Procedure Call, Remote Method Invocation)

  • 아파치, gRPC
  • 프라우드넷의 RMI
  • 게임엔진 내 RPC

Game Programming Gems 도서

0개의 댓글

Powered by GraphCDN, the GraphQL CDN