클라이언트-서버 통신
클라이언트가 서버에 연결하면 우선 TCP로 통신이 이루어집니다.
그 동안 백그라운드로 서버와의 UDP 홀펀칭을 성공하게 되면 UDP 통신도 가능하지만 그 전까진 reliable, Unreliable 메시징 모두 TCP로 대체됩니다. 하지만 UDP 홀펀칭 성공 이후부터 Reliable 메시징 은 UDP로 대체됩니다.
클라이언트는 각각의 TCP port, UDP port를, 서버는 1개의 TCP listening port, 1개 이상의 UDP port를 가지고 통신하는데, 클라이언트는 서버와 TCP 연결을 유지하면서 서버 측의 UDP port 중 한 개를 선정합니다. 즉, 서버 측은 여러 개의 UDP port를 접속한 모든 클라이언트들에게 고루 공유합니다.
예를 들어, 4만 개의 클라이언트가 2만 개의 UDP port를 연 서버와 통신한다면, 서버 측의 각 UDP port 당 2개의 클라이언트가 통신하게 됩니다.
Proxy & Stub 통신 객체에 등록 및 사용
서버와 클라이언트에 통신을 추가해 보겠습니다. 편리성을 위해 서버와 클라이언트 모두 사용하는 Common(공용) 프로젝트를 생성 후 PIDL파일을 준비합니다. 준비된 PIDL파일에 서버에서 클라이언트로 통신을 보내기 위한 프로토콜을 정의 합니다.
Global S2C 3000
{
Chat(Proud::StringA txt);
}
위의 PIDL파일을 컴파일하면 Proxy와 Stub객체가 생성됩니다.
Proxy 객체 사용 방법
먼저 사용하실 곳에 Header를 포함시킵니다.
// Server: Server ▶ Client임으로
// Server에서는 호출을 하기 위하여
// Proxy 객체를 포함합니다.
// header file에 선언
// Common 프로젝트를 만드는 것을
// 가정 하였기 때문에Common 프로젝트의
// 폴더로부터 생성된 파일을 포함시킵니다.
#include "../Common/S2C_proxy.h"
// cpp 파일에 선언
#include "../Common/S2C_proxy.cpp"
Proxy를 생성하고 서버 객체에 등록시켜 보겠습니다.
// 객체를 생성합니다.
S2C::Proxy g_S2CProxy;
void main()
{
// Server 설명에서 생성하였던
// Server 객체 입니다.
CNetServer* srv =
ProudNet::CreateServer();
Svr->AttachProxy(&g_S2CProxy);
// 이하 생략
}
AttachProxy라는 함수를 이용하여 생성된 Proxy객체의 포인터를 넘겨주는 방식으로 등록시켰습니다. AttachProxy는 내부에서 배열로 관리하여 여러 종류의 PIDL을 등록 시킬 수 있습니다. 등록 되었다면 Proxy 객체의 함수를 사용하여 통신을 할 수 있습니다.
// HostID와 RmiContext가
// 자동으로 추가 됩니다.
// hostID로 보내고자 하는
// Client의 HostID값을 넣습니다.
g_S2CProxy.Chat(
hostID,
RmiContext::ReliableSend,
“Send Message”);
Proxy와 마찬가지로 stub에도 사용할 곳에 Header를 포함시킵니다.
Client:
Server ▶ Client임으로 Client에서는
호출을 받기 위한 Stub객체를 포함합니다.
// header file에 선언
// Common 프로젝트를 만드는 것을
// 가정 하였기 때문에Common폴더에
// 생성된 파일을 포함시킵니다.
#include "../Common/S2C_stub.h"
// cpp 파일에 선언
#include "../Common/S2C_stub.cpp"
Stub객체의 경우 받을 프로토콜의 정의 함수기 때문에 상속 받은 객체를 생성하여 사용해야 합니다. AttachStub 함수를 사용하여 등록하면, 해당 호출이 왔을 시 콜백 됩니다. 생성된 Stub 객체 안에는 정의(Define)가 만들어지는데, 이를 사용하면 프로토콜을 변경해도 cpp파일과 h파일을 따로 수정할 필요가 없습니다.
#define DECRMI_C2S_Chat bool Chat(
Proud::HostID remote,
Proud::RmiContext &rmiContext,
const Proud::StringA txt)
Stub Class에 명시된 Define문 중 DEFRMI_NameSpace_함수이름은 상속 받은 객체의 Header 파일에, DECRMI_NameSpace_함수이름 (Class_Name) 은 cpp에 선언합니다.
class CS2CStub : public S2C::Stub
{
public:
// Protocol은 변경되어도 사용자가
// class를 수정할 필요 없도록 stub안에
// define문으로 처리되어 있습니다.
// ‘DEFRMI_NameSpace_함수이름’ 으로
// 되어 있으면 header에 선언합니다.
DECRMI_S2C_Chat;
};
CS2CStub g_S2CStub;
// ‘DEFRMI_Protocol분류명_protocol명(
// 상속받은 class name)’
// 으로 되어 있으면 cpp에 선언합니다.
DEFRMI_S2C_Chat(CS2CStub)
{
printf(
"[Client] HostID:%d, text: %s”,
remote,
txt);
// 반드시 true를 return해야 합니다
return true;
}
'True'를 Return 하는 것은 처리가 되었다라는 의미입니다. 'False'를 Return하게 되면 사용자가 프로토콜에 대한 처리를 하지 않은 것으로 판단하여 OnNoRmiProcessed Event가 콜백 됩니다. DEFRMI_S2C_Chat로 콜백된 함수 인자 중 remote는 RMI를 호출한 상대편 HostID 값 입니다. 이 ID값을 사용하여 Proxy를 호출하면 원하는 상대에게 통신을 보낼 수 있습니다.
이제 생성한 Stub객체를 Client객체에 등록시켜 보겠습니다.
CNetClient *client
= ProudNet:CreateClient();
client->AttachStub(&g_S2CStub);
// 이하 생략
AttachStub 함수도 내부에서 배열로 관리되고 있으며, 포인터를 넘겨주는 방식으로 등록됩니다.
NetLimiter 와 같은 Tool은 사용 후 삭제하길 권장합니다. Kernel Hooking 기능으로 인하여 통신 다바이스를 잡고 있는 코어에 본래 속도 20배 이상의 부담을 줍니다.
이벤트
- 클라이언트와 서버 공통
파라미터 errorInfo의 errorInfo -> ToString(); 을 사용하면 쉽게 문제에 대한 정보를 얻으실 수 있습니다.
OnError
ProudNet 내부에서 발생되는 Error나 사용 중 문제로 인한 정보를 콜백합니다.
OnWarnning
심각하진 않으나 잠재적 문제를 가진 정보를 콜백합니다.
OnInformation
내부 상황 및 추적 등에 대한 정보를 콜백합니다.
OnException
내부 Exception 오류 정보를 콜백합니다.
OnNoRmiProcessed
PIDL에 선언하였으나, Stub에서 Event를 상속받지 않았거나 사용자가 false를 Return 하였을 때 호출됩니다.
- 서버
성능 테스트 등에 이용 가능합니다.
OnUserWorkerThreadBegin
User Worker Thread Pool의 Thread가 시작할 때 호출됩니다.
OnUserWorkerThreadEnd
User Worker Thread Pool의 Thread가 끝날 때 호출됩니다.
Last updated
Was this helpful?