# 클라이언트-서버 통신

클라이언트가 서버에 연결하면 우선 TCP로 통신이 이루어집니다.&#x20;

그 동안 백그라운드로 서버와의 UDP 홀펀칭을 성공하게 되면 UDP 통신도 가능하지만 그 전까진 reliable, [**Unreliable 메시징**](/proudnet/pn_reference_ko/dictionary.md#unreliable) 모두 TCP로 대체됩니다. 하지만 UDP 홀펀칭 성공 이후부터 [**Reliable 메시징**](/proudnet/pn_reference_ko/dictionary.md#reliable) 은 UDP로 대체됩니다.

클라이언트는 각각의 TCP port, UDP port를, 서버는 1개의 TCP listening port, 1개 이상의 UDP port를 가지고 통신하는데, 클라이언트는 서버와 TCP 연결을 유지하면서 서버 측의 UDP port 중 한 개를 선정합니다. 즉, 서버 측은 여러 개의 UDP port를 접속한 모든 클라이언트들에게 고루 공유합니다.&#x20;

예를 들어, 4만 개의 클라이언트가 2만 개의 UDP port를 연 서버와 통신한다면, 서버 측의 각 UDP port 당 2개의 클라이언트가 통신하게 됩니다.

## Proxy & Stub 통신 객체에 등록 및 사용

서버와 클라이언트에 통신을 추가해 보겠습니다. 편리성을 위해 서버와 클라이언트 모두 사용하는 Common(공용) 프로젝트를 생성 후 PIDL파일을 준비합니다. \
준비된 PIDL파일에 서버에서 클라이언트로 통신을 보내기 위한 프로토콜을 정의 합니다.

{% tabs %}
{% tab title="C++" %}

```cpp
Global S2C 3000
{
   Chat(Proud::StringA txt);
}
```

&#x20;위의 PIDL파일을 컴파일하면 **Proxy**와 **Stub**객체가 생성됩니다.

&#x20;

### Proxy 객체 사용 방법

먼저 사용하실 곳에 Header를 포함시킵니다.

```cpp
// Server: Server ▶ Client임으로 
// Server에서는 호출을 하기 위하여 
// Proxy 객체를 포함합니다.
// header file에 선언
// Common 프로젝트를 만드는 것을 
// 가정 하였기 때문에Common 프로젝트의 
// 폴더로부터 생성된 파일을 포함시킵니다.
#include "../Common/S2C_proxy.h"
  
// cpp 파일에 선언
#include "../Common/S2C_proxy.cpp"
```

&#x20;

**Proxy**를 생성하고 서버 객체에 등록시켜 보겠습니다.

```cpp
// 객체를 생성합니다.
S2C::Proxy g_S2CProxy;
  
void main()
{
    // Server 설명에서 생성하였던 
    // Server 객체 입니다.
    CNetServer* srv = 
         ProudNet::CreateServer();
    Svr->AttachProxy(&g_S2CProxy);
  
    // 이하 생략
}
```

**AttachProxy**라는 함수를 이용하여 생성된 **Proxy**객체의 포인터를 넘겨주는 방식으로 등록시켰습니다. **AttachProxy**는 내부에서 배열로 관리하여 여러 종류의 PIDL을 등록 시킬 수 있습니다.\
등록 되었다면 **Proxy** 객체의 함수를 사용하여 통신을 할 수 있습니다.

```cpp
// HostID와 RmiContext가 
// 자동으로 추가 됩니다.
// hostID로 보내고자 하는 
// Client의 HostID값을 넣습니다.
g_S2CProxy.Chat(
         hostID, 
         RmiContext::ReliableSend, 
         “Send Message”);
```

&#x20;

**Proxy**와 마찬가지로 **stub**에도 사용할 곳에 Header를 포함시킵니다.

```cpp
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파일을 따로 수정할 필요가 없습니다.

```cpp
#define DECRMI_C2S_Chat bool Chat(
        Proud::HostID remote,
        Proud::RmiContext &rmiContext,
        const Proud::StringA txt)
```

**Stub Class**에 명시된 Define문 중 <mark style="color:orange;">DEFRMI\_NameSpace\_함수이름</mark>은 상속 받은 객체의 Header 파일에, <mark style="color:orange;">DECRMI\_NameSpace\_함수이름 (Class\_Name)</mark> 은 cpp에 선언합니다.&#x20;

```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하게 되면 사용자가 프로토콜에 대한 처리를 하지 않은 것으로 판단하여 <mark style="color:orange;">OnNoRmiProcessed Event</mark>가 콜백 됩니다.\ <mark style="color:orange;">DEFRMI\_S2C\_Chat</mark>로 콜백된 함수 인자 중 remote는 RMI를 호출한 상대편 HostID 값 입니다.\
이 ID값을 사용하여 **Proxy**를 호출하면 원하는 상대에게 통신을 보낼 수 있습니다.&#x20;

&#x20;

이제 생성한 **Stub**객체를 Client객체에 등록시켜 보겠습니다.

```cpp
CNetClient *client 
         = ProudNet:CreateClient();
client->AttachStub(&g_S2CStub);
// 이하 생략
```

**AttachStub** 함수도 내부에서 배열로 관리되고 있으며, 포인터를 넘겨주는 방식으로 등록됩니다.
{% endtab %}

{% tab title="C#" %}

### Proxy 객체 사용 방법

PIDL 컴파일 결과물에 포함된  Proxy, Stub 등의 클래스를 생성합니다.

```csharp
CommonC2C.Proxy c2cProxy = new CommonC2C.Proxy();
CommonC2S.Proxy c2sProxy = new CommonC2S.Proxy();
```

Proxy를 생성하고 Client에 Proxy들을 **attach**합니다.

```csharp
Nettention.Proud.NetClient netClient = new Nettention.Proud.NetClient();
netClient.AttachProxy(c2cProxy);
netClient.AttachProxy(c2sProxy);
```

### Stub객체 사용 방법

```csharp
CommonS2C.Stub s2cStub = new CommonS2C.Stub();
CommonC2C.Stub c2cStub = new CommonC2C.Stub();
```

**Proxy**와 마찬가지로 **stub**에도 사용할 곳에 생성 하고 Client에 Stub들을 **attach**합니다.

c++ 버전과는 다르게, DotNet버전에서는 Stub이 delegate로 되어 있습니다.

<pre class="language-csharp"><code class="lang-csharp">// Stub을 처리할 delegate를 세팅합니다.
s2cStub.NotifyLocalHeroViewersGroupID = NotifyLocalHeroViewersGroupID; //&#x3C;- 아래의 함수를 세팅합니다.

//똑같이 아래 함수들을 만들어 세팅합니다.
s2cStub.RemoteHero_Appear = RemoteHero_Appear;

<strong>s2cStub.RemoteHero_Disappear = RemoteHero_Disappear;
</strong>
c2cStub.P2P_LocalHero_Move = P2P_LocalHero_Move;


...

netClient.AttachStub(s2cStub);

netClient.AttachStub(c2cStub);

...


//delegate에 맞게 함수를 만들어 줍니다.
bool NotifyLocalHeroViewersGroupID(Nettention.Proud.HostID remote, Nettention.Proud.RmiContext rmiContext, Nettention.Proud.HostID localHeroViewersGroupID)
{
    ...
    return true;
}

</code></pre>

\
Nettention.Proud.NetConnectionParam을 초기화  하고, Nettention.Proud.NetClient.Connect()를\
호출하며 서버에 연결합니다.

```cpp
Nettention.Proud.NetConnectionParam param = new Nettention.Proud.NetConnectionParam();

//protocolVersion을 맞춥니다.
param.protocolVersion.Set(new System.Guid("{0x7b7e9c20,0x309c,0x4364,{0xb4,0x9c,0xc6,0xc,0xcd,0x25,0xaf,0xa0}}"));

//접속할 포트를 세팅합니다.
param.serverPort = 32222;

//접속할 IP주소를 세팅합니다.
param.serverIP = serverAddr;

netClient.Connect(param);
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
개발 이후 반드시 트래픽을 확인하셔야 합니다.\
트래픽은 각 클라이언트, (Super peer기능을 사용한다면 Super Peer에서도) 서버 등에서 각각 통신 양을 체크하고 불필요한 트래픽을 제거해주는 작업이 필요합니다.\
\
트래픽 체크를 위해 아래의 방법들을 참고해 주세요.<br>

* <mark style="color:orange;">NetLimiter</mark>와 같은 툴을 사용합니다.
* 한 컴퓨터에 사용할 프로세스 개수만큼 정확히 실행 한 뒤, 작업 관리자의 간격당 주고받은 바이트 수를 확인하는 방법이 있습니다.
* ProudNet의 내부 함수인 <mark style="color:orange;">CNetServer::GetStats(CNetServerStats \&outVal);</mark> 를 사용하여 초당 주고받은 트래픽, 보내거나 받은 수, 등을 실시간으로 얻을 수 있습니다.\
  한 클라이언트의 총 트래픽이 대략 20\~30KB 이상 발생 된다면, 해외 서비스에서 문제가 발생할 수 있습니다.
  {% endhint %}

{% hint style="danger" %} <mark style="color:orange;">NetLimiter</mark> 와 같은 Tool은 사용 후 삭제하길 권장합니다.\ <mark style="color:orange;">Kernel Hooking</mark> 기능으로 인하여 통신 다바이스를 잡고 있는 코어에 본래 속도 20배 이상의 부담을 줍니다.
{% endhint %}

## 이벤트

### - 클라이언트와 서버 공통

파라미터 <mark style="color:orange;">errorInfo</mark>의 <mark style="color:orange;">errorInfo</mark> -> <mark style="color:orange;">ToString();</mark> 을 사용하면 쉽게 문제에 대한 정보를 얻으실 수 있습니다.

<table><thead><tr><th width="203">이벤트</th><th>설명</th></tr></thead><tbody><tr><td>OnError</td><td>ProudNet 내부에서 발생되는 Error나 사용 중 문제로 인한 정보를 콜백합니다.</td></tr><tr><td>OnWarnning</td><td>심각하진 않으나 잠재적 문제를 가진 정보를 콜백합니다.</td></tr><tr><td>OnInformation</td><td>내부 상황 및 추적 등에 대한 정보를 콜백합니다.</td></tr><tr><td>OnException</td><td>내부 Exception 오류 정보를 콜백합니다.</td></tr><tr><td>OnNoRmiProcessed</td><td>PIDL에 선언하였으나, Stub에서 Event를 상속받지 않았거나 사용자가 false를 Return 하였을 때 호출됩니다.</td></tr></tbody></table>

### - 서버

성능 테스트 등에 이용 가능합니다.

<table><thead><tr><th width="261">이벤트</th><th>설명</th></tr></thead><tbody><tr><td>OnUserWorkerThreadBegin</td><td>User Worker Thread Pool의 Thread가 시작할 때 호출됩니다.</td></tr><tr><td>OnUserWorkerThreadEnd</td><td>User Worker Thread Pool의 Thread가 끝날 때 호출됩니다.</td></tr></tbody></table>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.proudnet.com/proudnet/pn_reference_ko/notes/client_server_communication.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
