ProudNet 활용하기

ProudNet 서버

ProudNet 서버는 Proud::CNetServer 인스턴스이며 다음 역할을 합니다. 또한 P2P 통신을 위한 relay 서버로서의 역할도 같이 하고 있습니다.

  • ProudNet 클라이언트로부터의 연결 수용 및 관리

  • 클라이언트와의 통신 수행: RMI

  • 클라이언트간 P2P 통신을 위한 그룹 관리: P2P그룹 참고

ProudNet 클라이언트

ProudNet 네트워크 클라이언트는 Proud::CNetClient 인스턴스이며 다음 역할을 합니다.

ProudNet 프로토콜 종류

지원하는 메시징 프로토콜은 다음과 같으며 하나를 선택하여 RMI 함수 호출의 파라미터로 넣을 수 있습니다.

프로토콜 종류설명

Reliable messaging

호스트 간 메시지를 주고 받는 시간이 unreliable 보다 길지만 송수신 순서와 도착은 보장됩니다.

Unreliable messaging

호스트 간 메시지를 주고 받는 시간이 reliable 보다 짧지만 송수신 순서가 꼬일 수 있으며, 도착은 보장되지 않습니다.

ClientServer가 사용하는 통신 프로토콜

상태Reliable P2P RMIUnreliable P2P RMIReliable C-S RMIUnreliable C-S RMI

정상

Reliable UDP

UDP

TCP

UDP

피어 간 UDP 통신 불량

Relay and Reliable UDP or TCP

Relay and TCP

TCP

UDP

클라-서버 간 UDP 통신 불량

Relay and TCP

Relay and TCP

TCP

TCP

  • Reliable UDP는 ProudNet 자체 구현 기능입니다.

  • Relay는 Server에서 담당합니다.

프로토콜 선택 요령 처음 게임을 개발할 때는 Proud.RmiContext.ReliableSend 사용하여 모든 메시지를 Reliable messaging 으로 보내게 합니다. 그리고 모든 RMI 호출 시점을 접근하기를 참고하여 RMI 메시지 송수신 내역을 수집합니다.

일반적으로 게임 프로그램에서 20% 이하의 RMI 메시지 종류가 전체 송수신량의 80% 이상을 차지하기 마련인데, 이들 중 송신 빈도가 매우 높으면서 20% 이하의 유실이 발생해도 별 문제없는 RMI 를 찾아 Unreliable messaging 을 쓰도록 수정합니다.

ProudNet 클라이언트-서버 통신

ProudNet에서 클라이언트와 서버 간 통신은 메시지 단위로 이루어져 각 메시지마다 RMI 호출 1회에 대응하며 reliable, unreliable 통신 모두 가능합니다.

ProudNet P2P 통신 성능

ProudNet은 보편화된 홀 펀칭 및 릴레이 기법 이상의 기능을 보유하고 있어 강력하며, 연결 대기 시간이 없는 TCP와 같은 P2P Reliable 메시징을 지원합니다. 민감하거나 결함이있는 NAT 장치에 대한 내성이 있어 저속 인터넷에서 과부하되는 P2P 송신량을 자체 해결을 하기도 하며, 사용자가 P2P 통신 연결을 요청(Proud.CNetServerCreateP2PGroup 이나 Proud.CNetServer.JoinP2PGroup)한 직후부터 클라이언트들 간의 메시징을 바로 시행합니다.

NAT장치 중 일부는 같은 내부 주소에 대한 외부 주소 매핑을 잘못 관리하여 유실 되기도 하는데, ProudNet은 클라이언트 간 다른 UDP socket을 두고 있어 꼭 필요한 경우에만 홀 펀칭을 시도하여 이러한 매핑 정보 유실을 최소화합니다.

예를 들어 P2P 연결을 맺었지만 정작 P2P 통신을 하지 않는다면 홀 펀칭을 수행하지 않습니다. 그리하여 Proud.CNetServer.CreateP2PGroup , Proud.CNetServer.JoinP2PGroup를 호출한 후에도 여전히 Proud.RmiContext.m_relayed, Proud.CNetClientInfo.m_RelayedP2P, Proud.CNetPeerInfo.m_RelayedP2P 값이 false 지만, P2P 통신을 하기 시작하면 true로 바뀔 것입니다.

ADSL 이나 ADSL2+ 등 다운로드 속도에 비해 업로드가 느린 가정용 컴퓨터에서 홀 펀칭이 되더라도 송신량을 이기지 못하는 경우가 있어 송신하는 컴퓨터나 송신 측에 있는 NAT 장치가 정상 작동을 못하기도 합니다.

이 때 ProudNet은 내장된 해결 기능으로 멀티캐스트하는 클라이언트의 과다한 송신량을 감지하여 멀티캐스트를 릴레이 서버에게 배분합니다.

NAT 홀 펀칭을 위한 전략으로 '포트 예측 기법'이 알려져 있습니다. 이 기법은 symmetric NAT 장치 간 홀 펀칭을 가능하게 해주지만 과다한 포트 매핑으로 인한 부작용이 있습니다. ProudNet은 그 부작용을 최소화하기 위해 초반 과한 홀 펀칭을 자제하다가 서서히 포트 예측 등의 공격적인 방법으로 전환합니다. 따라서 일부 NAT 장치에서는 direct P2P로 경로를 전환하는 시간이 조금 걸리지만 先 홀펀칭 後 릴레이 기법을 사용하기 때문에 문제되진 않습니다.

ProudNet의 UDP 통신 관련 성능

ProudNet은 UDP 프로토콜 사용 시, 통신 성능을 높이기 위해 Coalesce, MTU discovery fail 예방과 같은 기법을 구사합니다.

레이턴시가 1밀리 초 이하인 LAN 환경과 같은 소량 통신에서는 통신 부하가 실제 RMI 데이터 용량보다 많이 나타납니다. 하지만 통신량을 증가 시키거나 레이턴시가 높은 WAN 환경에서는 LAN에서 소량 통신을 할 때보다 통신 부하가 줄어듭니다.

100km 이상 떨어진 인터넷 통신에선 수많은 기종의 Gateway 장치를 패킷이 지나가야 합니다. 각 Gateway는 서로 다른 감당 가능한 MTU 사이즈가 지정되어 있지만 패킷을 송신하는 측에서 정의된 MTU 크기를 초과할 경우 ICMP packet fragment 패킷이 발생합니다.

일부 유저들은 ICMP 공격이 두려워 모든 종류의 ICMP 패킷을 차단하도록 설정하기도 하는데, 이런 경우 ICMP packet fragment 패킷을 처리하지 못해 MTU discovery를 실패하여 결국 두 호스트 간의 UDP 통신이 두절됩니다.

ProudNet은 이를 예방하는 기능을 갖추고 있습니다.

ProudNet의 암호화 통신

ProudNet의 암호화 통신 프로토콜은 매우 강력합니다.

암호화하고자 하는 메시지는 대칭 키로 암호화되며 이 때 사용되는 대칭 키공개 키로 암호화되어 호스트 간 교환됩니다. 이렇게 암호화를 위한 키들은 서버와 클라이언트 뿐만 아니라 클라이언트 간의 P2P 통신에서도 서로 고유 값으로 배정되기 때문에 제3자가 해킹할 수 없습니다.

또한 암호화된 메시지는 타 호스트로 전송될 때마다 내부 내용이 크게 달라져서 패킷 캡처 후, 동일하거나 유사한 메시지를 재전송하는 해킹 시도를 차단합니다. 하지만 비 암호화 메시징보다 처리 속도가 낮으므로 적절히 사용하시기 바랍니다.

ProudNet은 비대칭 키 알고리즘으로 128bit RSA를, 대칭 키 알고리즘으로 AES 또는 Fast를 사용하고 있습니다. RSA만 이용하기엔 계산량이 많기 때문에 대칭 키 알고리즘과 혼용하여 사용합니다.

이를 클라이언트와 서버 간 통신 뿐만 아니라 P2P로 통신에도 암호화 기능을 제공하여 보안에 탁월합니다. 또한 RMI에서 사용되는 대칭 키는 초기 서버 연결 시 RSA 알고리즘 공개 키로 암호화 후 교환되어 신뢰성이 매우 높습니다.

ProudNet 클라이언트가 서버로 연결하는 과정에서 발생하는 Proud::INetServerEvent::OnConnectionRequest 이벤트의 커스텀 필드는 암호화가 되어있지 않습니다. 이를 통해 사용자 정보를 전달하지 마시기 바랍니다.

- ProudNet 암호화 기법

ProudNet은 성능과 보안 수준에 따라 다양한 암호화 기능을 제공합니다. 암호화된 메시지를 전송하려면 RMI 메서드 호출 혹은 SendUserMessage 계열 메서드에 들어가는 인자 중 Proud.RmiContext.m_encryptMode에 원하는 암호화 방식을 선택하시면 됩니다.

Proud::RmiContext rmiContext;
rmiContext.m_encryptMode = Proud::EncryptMode::EM_Secure;
// proxy 함수 예시입니다
Proxy.RequestLogon(Proud::HostID::HostID_Server, rmiContext, m_Name, password);
Proud::RmiContext rmiContext;
rmiContext.m_encryptMode = Proud::EncryptMode::EM_Fast;
Proxy.RequestLogon(Proud::HostID::HostID_Server, rmiContext, m_Name, password);

또는 이렇게 쉽게 사용하실 수도 있습니다.

Proxy.RequestLogon(Proud::HostID::HostID_Server,Proud::RmiContext::SecureReliableSend, m_Name, password); 
// EM_Secure 를 사용
Proxy.RequestLogon(Proud::HostID::HostID_Server,Proud::RmiContext::FastEncryptedReliableSend, m_Name, password); 
// EM_Fast 를 사용

P2P Group에서 암호화 기능을 사용할 경우 CStartServerParameterBase::m_enableP2PEncryptedMessaging 의 값을 true로 설정해야 합니다.

C#에서는 StarSeverParameterBase.enableP2PEncryptedMessaging을 사용하시면 됩니다.

- 암호화 키 길이: 암호화 수준과 성능 사이에서 조율

ProudNet의 암호화 과정에서 내부에서는 호스트 간에 키 교환이 이루어집니다. 이 때 주고받는 키의 길이를 사용자가 설정할 수 있는데, 시스템의 성능과 암호화 수준을 고려하여 설정해야 합니다.

암호화 키의 길이는 서버 시작 시 파라미터 Proud.CStartServerParameter.m_encryptedMessageKeyLengthProud.CStartServerParameter.m_fastEncryptedMessageKeyLength 에서 설정할 수 있습니다.

  • Proud.CStartServerParameter.m_encryptedMessageKeyLength 파라미터

AES방식 암호화를 사용할 때 키의 길이를 뜻하며, Proud.EncryptLevel.EncryptLevel_Low , Proud.EncryptLevel.EncryptLevel_Middle , Proud.EncryptLevel.EncryptLevel_High 의 키 길이를 세팅할 수 있습니다.

  • Proud.CStartServerParameter.m_fastEncryptedMessageKeyLength 파라미터

Fast방식 암호화를 사용할 때 키의 길이를 뜻하며, Proud.FastEncryptLevel.FastEncryptLevel_Low , Proud.FastEncryptLevel.FastEncryptLevel_Middle , Proud.FastEncryptLevel.FastEncryptLevel_High 의 키 길이를 세팅할 수 있습니다.

AES 암호화 방식은 Fast보다 다소 느리지만 수준 높은 암호화를 제공합니다.

그러나 캐릭터의 이동 메시지 같이 중요하지 않은 데이터나 많은 송수신이 이루어지는 메시지 같은 경우에는 빠른 성능을 가지고 있는 Fast를 사용하실 수 있습니다.

- ProudNet의 Fast AES 알고리즘 성능 비교

다음은 ProudNet 의 Fast AES 알고리즘 성능을 비교한 것으로 각 알고리즘을 사용하여 암복호화를 회당 1번씩 10만번 수행한 시간을 측정하였습니다.

테스트 사양

항목스펙

OS

Windows 7 Professional K

CPU

Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz 3.40GHz

RAM

16.0GB

System type

64-bit Operating System

ProudNet 메시지 압축 기능

ProudNet에서는 메시지를 압축하여 전송이 가능하며 호스트의 CPU 사용으로 통신량을 절감하여 효율적입니다. 메시지를 압축하기 위해서는 RMI 메서드를 호출하거나 SendUserMessage 메서드 호출 시 들어가는 인자 Proud.RmiContext.m_compressModeProud.CM_None이외의 값으로 넣습니다.

Proud::RmiContext rmi = Proud::RmiContext::ReliableSend;
rmi.m_compressMode = Proud::CompressMode::CM_Zip;
 
Proxy.SendFileChunk(Proud::HostID::HostID_Server, rmi, dataBlock);

Last updated