# 性能小貼士

## 節省流量

最好不要發送或接收不必要的訊息。\
例如，在向遊戲玩家顯示任務的情況下，最好顯示遊戲用戶端中預先儲存的資源，而不是發送整個任務字串。 然而，在具有多語言支援的遊戲中，它可能變得至關重要。

{% hint style="success" %}
[**推測導航 (dead reckoning)**](/proudnet.cn/proudnet-note/dictionary.md#dead-reckoning) 有助於減少收發的信息數量。\
[**數據量化**](/proudnet.cn/proudnet-note/dictionary.md#undefined) 有助於減小一則訊息的大小。
{% endhint %}

## Multicast 優化

使用 RMI 進行多播時，最好透過單一 RMI 函數呼叫同時傳送到多個主機。MMO遊戲服務器維護費用中佔很大比重的是服務器運行地點(如IDC)的線路速度。\
ProudNet 有以下設備來對此進行最佳化。

### - Multicast NPC 模擬結果

通常NPC(Nonplayer character)的模擬是在服務器上進行的。 將NPC的模擬結果向客戶端進行多播,並利用連接各客戶端的P2P連接進行傳送的方式。

### - Multicast PC(Player character) 模擬結果

通常，PC 的模擬是在每個客戶端完成的。 用戶端將 PC 的位置和速度資訊傳輸到伺服器，伺服器接收該資訊並將其多播到 PC 周圍的用戶端。

然而，ProudNet的P2P創建、銷毀、加固、縮減處理速度非常快。 使用此功能，每台 PC 的位置多播可以透過點對點通訊直接傳輸到其他用戶端，其中每個用戶端的 PC 都位於其可見區域中，而無需透過伺服器。<br>

主要方法如下。

> * 在某些時候，每台PC可見區域內的客戶端會形成一個P2P組。
> * 每個客戶端將模擬PC的位置和速度傳輸給伺服器和P2P群組中的客戶端。
> * 伺服器根據伺服器收到的訊息，不斷創建、銷毀、增加、減少P2P組。
> * 客戶端根據收到的資訊更新 PC 的位置。

{% hint style="success" %}
**參考**

[**在服務器上向多個客戶端發送 routed multicast**](/proudnet.cn/proudnet/using_pn/p2p/using_p2p.md#routed-multicast)\
[**Multicast**](/proudnet.cn/proudnet-note/dictionary.md#undefined-1)
{% endhint %}

## 遊戲服務器操作系統(OS)

同時在線人數(CCU)在100以下時,不管使用什麼樣的操作系統,但如果超過100,就要使用服務器專用操作系統。\
ProudNet 支援的僅伺服器作業系統是 Windows 2003 Server 或更高版本。 Linux 也受支持，並已在 CentOS 6 和 Ubuntu 12 及更高版本上進行了測試。

## 伺服器如何使用UDP連接埠

當客戶端連接伺服器時，必須分配1個TCP端口，但ProudNet使用1個UDP端口連接客戶端。\
（但是，在客戶端無法使用UDP連接埠的環境下，只能使用TCP連接埠。）

<mark style="color:orange;">Server</mark>對每個客戶端連線的UDP連接埠分配策略劃分如下。

> * <mark style="color:orange;">per-client assign mode</mark>: 為每個客戶端分配不同的 UDP 連接埠。
> * <mark style="color:orange;">static assign mode</mark>: 為所有客戶端分配一定數量的預先準備的UDP連接埠。

### - Static Assign Mode

除了伺服器要使用的 UDP 端口號列表之外，不使用其他 UDP 端口，但使用 UDP 端口號列表中的一個端口。 這意味著兩個或多個客戶端將共用相同的 UDP 連接埠。\
（這些客戶端不會出現訊息流問題。）

如果客戶端數量較多，則少量的UDP套接字必須處理與多個客戶端的通信，因此如果UDP套接字的內部緩衝區容量受到限制，則存在丟包的風險。\
但是，您必須小心，因為從一開始就準備太多的 UDP 套接字可能會增加記憶體和 CPU 使用率，因為伺服器對如此多的 UDP 套接字的處理負載。

{% hint style="warning" %}
由於對<mark style="color:orange;">ICMP host unreachable packet</mark>的容忍能力較弱，因此必須[**建立**](/proudnet.cn/proudnet-note/notes/setting_firewall.md#icmp)[**ICMP 防火牆**](/proudnet.cn/proudnet-note/notes/setting_firewall.md#icmp)。
{% endhint %}

### - Per-client Assign Mode

每個連接到服務器的客戶端分配不同的UDP端口，除了服務器使用的UDP端口列表外，還使用任意端口編號。 一般來說,<mark style="color:orange;">Per-client assign mode</mark>比<mark style="color:orange;">static assign mode</mark>優點多,性能好,建議使用<mark style="color:orange;">Per-client assign mode</mark>,但也有注意事項。 當客戶端訪問多於服務器使用的 UDP 端口列表時， 將會分配任意的 UDP 端口， 如果此時分配的端口是服務器防火牆中不允許的號碼， UDP 通信可能不暢。

有些服務器防火牆具有允許端口使用的功能，僅當檢測到outbound分組時。 如果利用該技術,在使用<mark style="color:orange;">Per-client assign mode</mark>的同時,即使關閉UDP端口,也能實現安全、順暢的通信。

要配置服務器使用哪種 assign mode， 請在調用 <mark style="color:orange;">Proud.CNetServer.Start</mark> 時設置 <mark style="color:orange;">Proud.CStartServerParameter.m\_udpAssignMode</mark> 。 此外， 服務器可以在 <mark style="color:orange;">Proud.CStartServerParameter.m\_udpPorts</mark> 中配置 UDP 端口的列表 。

使用ProudNet時，assign mode、UDP端口列表長度、防火牆設置推薦類型如下。

<table data-full-width="true"><thead><tr><th width="120" align="center">建議水平</th><th width="129" align="center">Assign Mode</th><th width="188">UDP 端口列表長度</th><th width="214">防火牆設定</th><th>適用條件</th></tr></thead><tbody><tr><td align="center">強烈推薦</td><td align="center">Per-client</td><td>(per-client 無視) -</td><td>檢測到outbound分組時暫時允許</td><td>某些實體伺服器（而非雲端伺服器）可以開啟/關閉「檢測出outbound分組時暫時允許」功能</td></tr><tr><td align="center">推薦</td><td align="center">Per-client</td><td>-</td><td>始終允許在 UDP 連接埠清單中註冊的號碼</td><td></td></tr><tr><td align="center">推薦</td><td align="center">Static</td><td>同時在線人數的1/10</td><td>總是允許 UDP 端口列表中的編號/ ICMP host unreachable 屏蔽</td><td></td></tr><tr><td align="center">危險</td><td align="center">Per-client</td><td>-</td><td>始終允許在 UDP 連接埠清單中註冊的號碼</td><td></td></tr><tr><td align="center">危險</td><td align="center">Static</td><td>無關</td><td>不管 UDP 端口允許範圍如何/ 不屏蔽 ICMP host unreachable</td><td></td></tr></tbody></table>

{% hint style="success" %}
**參考**

[**ICMP 防火牆設定**](/proudnet.cn/proudnet-note/notes/setting_firewall.md#icmp)

[**設置防禦**](/proudnet.cn/proudnet-note/notes/setting_firewall.md#ddos) [**DDOS 攻擊的屏障**](/proudnet.cn/proudnet-note/notes/setting_firewall.md#ddos)
{% endhint %}

## 伺服器執行緒池中的執行緒數

當執行<mark style="color:orange;">Proud.CNetServer.Start</mark>伺服器時，您可以指定執行緒池中的執行緒數量。 根據伺服器的角色和功能，可以在伺服器電腦上啟動的伺服器進程數或建立的執行緒數會有所不同。

下面介紹幾個具有代表性的指南。

首先，假設執行緒數與伺服器上的CPU核心數一樣多。

如果使用ProudNet服務器的服務器不純粹佔用CPU burst time，而是佔用device burst time，則會增加線程數。 在遊戲服務器中佔據代表性device burst time的情況是在接近用戶DB時。

如果遊戲伺服器邏輯被設計為由多個執行緒同時訪問，則在伺服器電腦上僅執行一個遊戲伺服器進程是可以的。 然而，如果遊戲伺服器邏輯受到critical section或mutex的保護，使其始終只能在一個執行緒中執行，則建議執行多個遊戲伺服器程序。

## Speed Hack 檢測功能和伺服器效能

ProudNet具有內部Speed Hack探測功能。 通過檢查客戶端與服務器之間交換的ping信息週期的方式，以數秒的間隔將ping從客戶端發送到服務器。 服務器認爲ping信息到達的週期爲間隔的30%以上，誤差範圍相差太大，ping到達20次以上時，就視爲使用Speed Hack。

這樣檢測到Speed Hack後,針對該客戶端的服務器活動<mark style="color:orange;">Proud.INetServerEvent.OnClientHackSuspected</mark>已回電，可收集或驅逐不良用戶名單<mark style="color:orange;">(Proud.CNetServer.CloseConnection)</mark>。

{% hint style="warning" %}
在以下情況下檢測可能會很困難。

* 如果您以低於 20% 的速度進行電腦速度駭客攻擊，則不會被偵測到。
* 通訊狀況不佳的用戶端可能會被誤認為使用<mark style="color:orange;">Speed Hack</mark>。
  {% endhint %}

另一方面，遊戲玩家可能會有意地非常短暫地使用速度駭客。\
為了偵測這一點，伺服器會比較客戶端首次連線到伺服器後交換的「**客戶端內部時間值**」和「**伺服器時間值**」。

如果自從客戶端首次連線以來不斷發送到伺服器的「**客戶端內部時間值**」與「**伺服器測量的時間值**」之間的差異太大，則認為正在使用速度駭客。\
然而，這種檢查方法是在客戶端連接到伺服器後數十秒才執行的。

<table data-full-width="true"><thead><tr><th width="410">C++ 函數</th><th>C# 函數</th><th>註釋</th></tr></thead><tbody><tr><td>Proud.INetServerEvent.OnClientHackSuspected</td><td>Nettention.Proud.NetServer.ClientHackSuspectedHandler</td><td>檢測到Speed Hack的客戶端的回撥服務器事件</td></tr><tr><td>Proud.CNetServer.CloseConnection</td><td>Nettention.Proud.NetServer.CloseConnection</td><td>用戶驅逐</td></tr><tr><td>Proud.CNetServer.SetSpeedHackDetectorReckRatioPercent</td><td>Nettention.Proud.NativeNetServer.SetSpeedHackDetectorReckRatioPercent</td><td>調節Speed Hack探測的迅速性和準確性</td></tr><tr><td>Proud.CNetServer.EnableSpeedHackDetector</td><td>Nettention.Proud.NativeNetServer.EnableSpeedHackDetector</td><td>Speed Hack 檢測器 on/off</td></tr></tbody></table>

速度駭客偵測功能會產生一定的流量，因為客戶端每秒向伺服器發送 1 到 2 個 UDP 封包，並且流量與連接到伺服器的用戶端數量成比例增加。\
因此，除非必要，否則避免使用它是有效的。

{% hint style="info" %}
Xeon E312XX（Sandy Bridge）內存4G的服務器PC上僅連接10000個客戶端時，如不使用速度核探測器，則CPU使用量爲0\~3%，\
使用時，測得CPU使用率為25%到35%。
{% endhint %}

## 接收處理例程的最佳化

接收處理例程花費很長時間會對伺服器效能產生重大影響。\
首先,通過接近接收方(stub)呼叫時間點,找出性能下降的接收處理程序,並加以解決是非常重要的。

然後要找出慢接收處理例程佔據的是<mark style="color:orange;">device burst time</mark>還是<mark style="color:orange;">CPU burst time</mark>。

* <mark style="color:orange;">device burst time</mark> 較長時\ <mark style="color:orange;">critical section lock</mark>必須設計為最小化，並且必須提高device burst原因（資料庫等）的效能。
* <mark style="color:orange;">CPU burst time</mark> 較長時， \
  計算例程必須經過最佳化、並行化或分發到其他伺服器。 例）NPC AI

{% hint style="success" %}
**參考**

[**Burst Time**](/proudnet.cn/proudnet-note/dictionary.md#burst-time)
{% endhint %}

## 使用保持連接功能

在行動環境中，蜂窩網路和 Wi-Fi 之間的切換很頻繁。\
即使在這些環境中，ProudNet 也會在引擎層級自動處理網路切換。

{% tabs %}
{% tab title="C++" %}
用戶只需添加以下程式碼即可連接伺服器。

```cpp
Proud::CNetConnectionParam cp;
cp.m_enableAutoConnectionRecovery = true;
 
client.Connect(cp);
```

伺服器上切換過程中的事件函數可以被 <mark style="color:orange;">INetServerEvent</mark> 中的下列函數覆寫。

```cpp
virtual void OnClientOffline(CRemoteOfflineEventArgs &args) {}
virtual void OnClientOnline(CRemoteOnlineEventArgs &args) {}
```

同樣，客戶端上的事件函數會覆寫 <mark style="color:orange;">INetClientEvent</mark> 中的下列函數。

```cpp
virtual void OnServerOffline(CRemoteOfflineEventArgs &args) {}
virtual void OnServerOnline(CRemoteOnlineEventArgs &args) {}
virtual void OnP2PMemberOffline(CRemoteOfflineEventArgs &args) {}
virtual void OnP2PMemberOnline(CRemoteOnlineEventArgs &args) {}
```

{% endtab %}

{% tab title="C#" %}
啟用保活功能可以透過DotNet NetClient中的<mark style="color:orange;">Nettention.Proud.NetConnectionParam.enableAutoConnectionRecovery</mark>變數進行設置，而在NetServer中無需單獨設置。

```csharp
Nettention.Proud.NetConnectionParam cp = new Nettention.Proud.NetConnectionParam();
cp.protocolVersion.Set(SimpleCSharp.Vars.m_Version);

cp.serverIP = "127.0.0.1";
cp.serverPort = (ushort)SimpleCSharp.Vars.m_serverPort;
cp.enableAutoConnectionRecovery = true;

Nettention.Proud.ErrorInfo errorInfo = new Nettention.Proud.ErrorInfo();

if (Client.Connect(cp, errorInfo) == false)
{
    Console.WriteLine("Failed to connect client ~!!\n");
}
...

```

關於連接維護功能的回撥活動如下。

| NetClient                                          | NetServer                                       |
| -------------------------------------------------- | ----------------------------------------------- |
| Nettention.Proud.NetClient.ServerOfflineHandler    | Nettention.Proud.NetServer.ClientOfflineHandler |
| Nettention.Proud.NetClient.ServerOnlineHandler     | Nettention.Proud.NetServer.ClientOnlineHandler  |
| Nettention.Proud.NetClient.P2PMemberOfflineHandler | -                                               |
| Nettention.Proud.NetClient.P2PMemberOnlineHandler  | -                                               |

```csharp
...
Client.ServerOfflineHandler = OnServerOffline;
Client.ServerOnlineHandler = OnServerOnline;

Client.P2PMemberOfflineHandler = OnP2PMemberOffline;
Client.P2PMemberOnlineHandler = OnP2PMemberOnline;

public void OnServerOffline(Nettention.Proud.RemoteOfflineEventArgs args){}
public void OnServerOnline(Nettention.Proud.RemoteOnlineEventArgs args){}
public void OnP2PMemberOffline(Nettention.Proud.RemoteOfflineEventArgs args){}
public void OnP2PMemberOnline(Nettention.Proud.RemoteOnlineEventArgs args){}
...
```

```csharp
...
server.ExceptionHandler = OnException;
server.ClientOnlineHandler = OnClientOnline;
server.ClientOfflineHandler = OnClientOffline;
server.ReceiveUserMessageHandler = OnReceiveUserMessage;

...

public void OnClientOnline(Nettention.Proud.RemoteOnlineEventArgs args){}
public void OnClientOffline(Nettention.Proud.RemoteOfflineEventArgs args){}
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
客戶端網路斷開回呼<mark style="color:orange;">Offline</mark>後，如果一定時間內沒有重連，則會回呼<mark style="color:orange;">INetClientEvent.OnLeaveServer</mark>, <mark style="color:orange;">INetServerEvent.OnClientLeave</mark>。
{% endhint %}

{% hint style="info" %}
在<mark style="color:orange;">1.7.36365</mark>及更高版本中，伺服器可以為每個客戶端設定保活時間。

```cpp
NetServer.SetAutoConnectionRecoveryTimeoutTimeMs(HostID, int)
NetServer.SetDefaultAutoConnectionRecoveryTimeoutTimeMs(int)
```

{% endhint %}

## Networker Thread

ProudNet的客戶端具有內部處理網絡I/O的worker thread。 即使用戶不按一定時間呼叫<mark style="color:orange;">Proud.CNetClient.FrameMove()</mark> 方法，它也可以促進網絡連接和 ping latency 測量。

然而，在一些低配置硬件中，如iPhone 3GS，建議兩個或多個線程不能同時工作，因爲多個線程的運行會對性能產生不利影響。 相反，Networker thread 的操作將在用戶應用程序中的 main thread 中執行。

> * 呼叫 <mark style="color:orange;">Proud.CNetClient.UseNetworkerThread\_EveryInstance(false)</mark>。 那麼網頁執行緒將不再工作。
> * 經常呼叫 <mark style="color:orange;">Proud.CNetClient.NetworkerThreadHeartbeat\_EveryInstance()</mark>。\
>   如果不可避免地長時間無法呼叫此函數，例如載入遊戲數據，請提前呼叫 <mark style="color:orange;">UseNetworkerThread\_EveryInstance(true)</mark>，並在作業完成後呼叫 <mark style="color:orange;">UseNetworkerThread\_EveryInstance(false)</mark>。

## 服務器安裝多個LAN卡(NIC)時

有時候遊戲服務器安裝了多個LAN卡(NIC)。 爲了安全起見,他們中只有一部分允許與玩家通信。 在這種情況下， 您必須指定在服務器啓動時使用哪種 NIC 。 如果未指定，客戶端可能會連接到服務器，但UDP通信或P2P通信可能無法正常工作。

可以通過 <mark style="color:orange;">Proud.CNetServer.Start</mark> 的輸入參數 <mark style="color:orange;">Proud.CStartServerParameter.m\_localNicAddr</mark> 指定要使用的 NIC。 爲了獲得要使用的NIC的本地地址列表，使用<mark style="color:orange;">Proud.CNetUtil.GetLocalIPAddresses</mark>會有幫助。

<br>

## 配置路由器( NATrouter) 或 L4 switch 後面的服務器

在網絡路由器後面的開發機器上啓動服務器時,想要將服務器放在網絡路由器(NATrouter)後面,或者服務器需要在L4開關後面的服務器機器上啓動時 \
或者， 如果您的服務器在雲服務器上運行， 需要端口轉發， 路由器的端口轉發將被配置 。

此時需要在CNetServer上追加設置,如果沒有這種設置,[**Unreliable 信息**](/proudnet.cn/proudnet-note/dictionary.md#unreliable)總是會出現使用TCP的問題。 P2P消息會經常出現使用TCP relay的問題。

假設Server1和Server2位於路由器後面，請查看下面的圖片。\
每個服務器都有一個名爲10010.0.1、10010.0.2的專用地址，路由器爲每個服務器提供11.22.33.44:5555和11.22.33.44:5556的端口轉發。

<figure><img src="/files/hYh4QBS5N9gPwvd5DJur" alt=""><figcaption><p>Port forward</p></figcaption></figure>

請輸入調用 <mark style="color:orange;">CNetServer.Start</mark> 時進入的參數 <mark style="color:orange;">Proud.CStartServerParameter.m\_serverAddrAtClient</mark> 。\
對於上圖，應加入NATrouter的公開地址11.22.33.44，並將在NATrouter上轉發UDP端口。

<figure><img src="/files/jI0OxAA5jTKTfUoFNCYQ" alt=""><figcaption><p>UDP port forward</p></figcaption></figure>

如上圖所示，如果端口轉發範圍爲6,000-6500，則<mark style="color:orange;">CNetServer</mark>應設置UDP端口6,000-6500，爲此將<mark style="color:orange;">UDP port assign mode</mark>設置爲static。 然後讓<mark style="color:orange;">CNetServer</mark>使用的端口爲6,000-6500。

**例子**

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

<pre class="language-cpp"><code class="lang-cpp">Proud::CNetServer* s;

...

<strong>Proud::CStartServerParameter p;
</strong>
...

p.m_serverAddrAtClient = "11.22.33.44";
p.m_udpAssignMode = Proud::ServerUdpAssignMode::ServerUdpAssignMode_Static;
for(int i=6000;i&#x3C;=6500;i++)
    p.m_udpPorts.Add(i);
 
s->Start(p);
</code></pre>

{% endtab %}

{% tab title="C#" %}

<pre class="language-csharp"><code class="lang-csharp">Nettention.Proud.NetServer s = new Nettention.Proud.NetServer();

...

<strong>Nettention.Proud.StartServerParameter p = new Nettention.Proud.StartServerParameter();
</strong>
...

p.serverAddrAtClient = "11.22.33.44";
p.udpAssignMode = Nettention.Proud.ServerUdpAssignMode.ServerUdpAssignMode_Static;
for(int i = 6000; i &#x3C;= 6500; i++)
    p.udpPorts.Add(i);
 
s.Start(p);
</code></pre>

{% endtab %}
{% endtabs %}

操作確認，兩個客戶端在建立P2P組後，在收發信息時確認是否進入P2P。 由於ProudNet的特性，來往於P2P的信息需要幾秒鐘才能變更爲direct P2P。

用P2P接收信息時，請確認參數<mark style="color:orange;">RmiContext</mark>的<mark style="color:orange;">m\_relayed</mark>值是否變爲false。 或者<mark style="color:orange;">OnChangeP2PRelayState</mark>被呼叫，請查看參數中是否包含OK。

如果沒有得到想要的結果，可以利用<mark style="color:orange;">CNetServer.EnableLog</mark>通過日誌內容找出問題的原因。

{% hint style="danger" %}
與路由器或開關後面不同，負載平衡器後面有服務器時\
連接維護功能可能不起作用。
{% endhint %}


---

# 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/usage_pn/tips.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.
