# 客戶端與服務器通信

客戶端連接到服務器後，首先用TCP進行通信。

在此期間,如果通過後臺成功與服務器進行UDP孔穿孔,則可以進行UDP通信,但在此之前,reliable、[**Unreliable 信息**](https://docs.proudnet.com/proudnet.cn/dictionary#unreliable)全部被TCP代替。 但是自從UDP打孔成功以後，[**Reliable 信息**](https://docs.proudnet.com/proudnet.cn/dictionary#reliable)就被UDP代替了。

客戶端用各自的TCP端口、UDP端口進行通信，服務器用1個TCP listening port、1個以上的UDP端口進行通信，客戶端在保持與服務器TCP連接的同時，從服務器側的UDP端口中選擇一個。 即，服務器方均勻地共享給連接多個UDP port的所有客戶端。

例如,如果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 中聲明。

```cpp
class CS2CStub : public S2C::Stub
{
public:
  
// 即使變更Protocol，用戶 
// 無需修改class，在stub內 
// 已處理爲define文。
// 以" DEFRMI_NameSpace_ 函數名" 
// 如果有， 在header 中聲明 。
    DECRMI_S2C_Chat;
};
CS2CStub g_S2CStub;
  
// 'DEFRMI_Protocol分類名_protocol名(
//               繼承的類 name'
// 如果設置爲 ， 則在 cpp 中聲明 。
DEFRMI_S2C_Chat(CS2CStub)
{
    printf( 
         "[Client] HostID:%d, text: %s”, 
         remote, 
         txt);
  
         // 必須回傳 true
    return true;
}
```

'True' Return意味着已經處理完畢。\
如果Return'False'，則判斷爲用戶未對協議進行處理，<mark style="color:orange;">OnNoRmiProcessed Event</mark>將返現。\
在返回<mark style="color:orange;">DEFRMI\_S2C\_Chat</mark>的函數因子中，remote是調用RMI的相對HostID值。\
使用此ID值調用**Proxy**，可以將通信發送給您想要的對方。

&#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 上 **attach** Proxy。

```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**上也使用的地方生成Stub，並在Client上**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>等工具。\
因<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或用戶Return false時呼叫。</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.cn/proudnet-note/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.
