> For the complete documentation index, see [llms.txt](https://docs.proudnet.com/proudnet.cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.proudnet.com/proudnet.cn/proudnet-note/notes/client_server_communication.md).

# 客戶端與服務器通信

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

在此期間,如果通過後臺成功與服務器進行UDP孔穿孔,則可以進行UDP通信,但在此之前,reliable、[**Unreliable 信息**](/proudnet.cn/proudnet-note/dictionary.md#unreliable)全部被TCP代替。 但是自從UDP打孔成功以後，[**Reliable 信息**](/proudnet.cn/proudnet-note/dictionary.md#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>
