19.1개념
Client 통신 또는 peer-to-peer 네트워킹(이하 P2P 통신)은 Server를 경유하지 않고 Client 호스트끼리 직접 통신을 하는 것을 일컫습니다. 다음과 같은 경우 유용합니다.
Server를 경유해서 통신하기에는 Data량이 너무 많은 경우
Client와 Server간에 지리적으로 너무 먼 경우
보다 적은 Latency를 추구할 경우
Layer간 이동 동기화를 할 때 P2P 기능을 사용하여 Server의 Traffic을 줄일 수 있습니다.
그림 19-1P2P사용하지 않고 한 케릭터의 시야 그룹내 동기화를 관리 하는 예
그림 19-2P2P사용하여 한 케릭터의 시야 그룹의 동기화를 관리 하는 예
하지만 반대로 P2P Connection의 수가 많아지면 Client의 Traffic이 과도해 질 수 있음을 인지하셔야 합니다. 또한 항시 해킹의 위험이 있기 때문에 공격 판정 여부 같은 중요한 Data는 Server에서도 확인해주어야 합니다. ProudNet에서는 해킹에 대한 안정성을 높이기 위하여 P2P의 그룹 생성과 해제 등의 모든 것을 Server에서 관리 해 주고 있습니다.
19.2사용법
Server에서 CreateP2PGroup 함수 호출을 통하여 P2P그룹을 만듭니다.
Client는 INetClientEvent::OnP2PMemberJoin을 통하여 P2P그룹이 생성 되었음을 Callback받게 됩니다.
OnP2PMemberJoin 함수를 통하여 그룹에 들어온 Client들과 Member의 HostID값을 받습니다.
아래는 Server에서 P2P그룹의 변화를 주기 위한 함수 호출과 Client에서 받게 될 Callback입니다.
P2P 그룹에 새로운 member 추가
Server Callback: CNetServer::JoinP2PGroup
Client Callback: INetClientEvent::OnP2PMemberJoin
P2P 그룹에 새로운 member 제거
Server Callback : CNetServer::LeaveP2PGroup
Client Callback: INetClientEvent::OnP2PMemberLeave
P2P 그룹을 제거
Server Callback : CNetServer::DestroyP2PGroup
Client Callback: INetClientEvent::LeaveP2PGroup
사용 예
게임 중 시야 그룹을 처리할 때
Client가 접속했을 때 CreateP2PGroup
다른 유저가 시야 안에 들어올 때 JoinP2PGroup
다른 유저가 시야 밖으로 나갈 때 LeaveP2PGroup
Client 접속 해제 DestroyP2PGroup
채팅
채팅 그룹을 만들 때 CreateP2PGroup
새 유저가 방에 들어올 때 JoinP2PGroupp
기존 유저가 방에서 나갈 때 LeaveP2PGroupbr
채팅 그룹이 제거될 때 DestroyP2PGroup
간단한 코드 추가로 P2P통신을 할 수 있습니다.
//File Name C2C – Client간 //Protocol의 정의파일 입니다. global C2C 4000 // client-to-client RMI, // 최초메시지ID = 4000 { P2PChat ([in] Proud::StringA txt); }
Δ PIDL 추가
Server
CNetServer *srv = Proud:: CreateServer(); HostID groupHostID;
Δ 객체
// 주의! 실 Server에서는 // GetClientHostIDs를 이용하지 말고, // 따로 관리하세요. HostID list[100]; int listCount = srv->GetClientHostIDs( list, 100); groupHostID = srv->CreateP2PGroup( list, listCount, ByteArray());
Δ P2P그룹 생성
g_S2CProxy.P2PChat(
groupHostID,
RmiContext::ReliableSend,
L"Hello~~~!");
Δ P2P그룹에 통신 보내기
srv->DestroyP2PGroup( groupHostID);
Δ P2P Group 파괴하기
Client
추가적으로 사용되는 부분 외에는 생략합니다.
// Header File 추가 #include “../C2C_Stub.h” // CPP File 추가 #include “../C2C_Stub.cpp”
Δ Header
client ->AttachProxy( &g_C2CProxy); client ->AttachStub( &g_C2CStub);
Δ Server & Client 구조에서 추가 되어야 할 부분
Class CClientEventSink : public INetClientEvent { // 새로 추가되는 Group의 // member수 만큼 연속으로 // 호출됩니다. virtual void OnP2PMemberJoin( HostID memberHostID, HostID groupHostID, int memberCount, const ByteArray &customField) {} // 제거되는 Member수 만큼 // 연속으로 호출됩니다. virtual void OnP2PMemberLeave( HostID memberHostID, HostID groupHostID, int memberCount) {} // 기타 생략 }
Δ Event 객체 - 추가로 사용되는 부분
Class C2CStub
: public C2C::Stub
{
DECRMI_C2C_P2PChat;
}
DEFRMI_C2C_P2PChat(C2CStub)
{
Printf(
“[client] %d, %s”,
remote,
txt);
}
C2CStub g_C2CStub;
Δ C2C간 통신을 받기 위한 객체
C2C::Proxy g_C2CProxy;
Δ C2C간 통신을 보내기 위한 객체
Tip!
Unreliable P2P로 보낼 수 있는 수는 무제한 인가?
P2P의 peer의 수가 증가하면 각 peer에서 다른 모든 peer간에 통신하기 때문에 Client에 발생되는 Traffic의 양은 (n-1)^2으로 늘어납니다. 따라서 Client 측의 traffic을 신경 쓰지 못하면 통신이 끊기거나 불안정해 질 수 있습니다.
마을같이 좁은 공간에 수많은 플레이어가 몰리고, 속도가 크게 중요하지 않은 장소에서 P2P 동기화 방식을 사용하시려면 Traffic을 줄이기 위한 많은 노력을 해야 합니다.
Move Packet과 같이 자주 보내고 손실되어도 괜찮은 정보에는 Unreliable을 사용하는 것이 성능상 이득입니다.
Hole-Punching이 완료된 Client
내부적으로 Reliable UDP를 사용합니다. (Reliable UDP는 재전송이 일어 납니다. 그래서 Traffic이 증가 할 수 있습니다.)
Hole-Punching이 완료되지 않은 Client
서버를 통하여 Relay로 통신하게 됩니다. 이때, Unreliable은 서버와 UDP를 사용하여 통신 하며, Reliable은 TCP를 통하여 통신합니다.