# Sample 例題

以下示例是根據<mark style="color:orange;">git 樣本項目</mark>創建的。 詳細內容請參考以下鏈接。

:open\_file\_folder: [**C++ 下載示例**](https://github.com/Nettention/ProudNet_Sample/tree/main/Simple)&#x20;

:open\_file\_folder: [**C# 下載示例**](https://github.com/Nettention/ProudNet_Sample/tree/main/SimpleCSharp)

## 客戶

### - Proxy & Stub

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

<pre class="language-cpp"><code class="lang-cpp"><strong>// 客戶端-> 服務器 RMI Proxy 實例
</strong><strong>Simple::Proxy g_SimpleProxy;
</strong>
// 與RMI proxy不同,在函數重疊後使用。
class SimpleStub : public Simple::Stub
{
public:
	DECRMI_Simple_ShowChat;
	DECRMI_Simple_SystemChat;

	DECRMI_Simple_P2PChat;
};

// 接收消息的 RMI stub 實例
SimpleStub g_SimpleStub;
</code></pre>

{% endtab %}

{% tab title="C#" %}

```csharp
using namespace Nettention.Proud;

// RMI proxy是用來發送信息的。
// 函數調用在不同的流程中運行。
static Simple.Proxy g_Proxy = new Simple.Proxy();

// RMI stub是用來接收信息的。
static Simple.Stub g_Stub = new Simple.Stub();
```

{% endtab %}
{% endtabs %}

### - 定義 RMI 函數

{% tabs %}
{% tab title="C++" %}
RMI函數爲了便於命名，使用以下規則。

\=> <mark style="color:orange;">DEFRMI\_GlobalName\_FunctionName</mark>

```cpp
DEFRMI_Simple_P2PChat(SimpleStub)
{
    ...

    // 沒有特別的意義， 但必須返回 true 。
    return true;
}

DEFRMI_Simple_ShowChat(SimpleStub)
{
    ...
    return true;
}

DEFRMI_Simple_SystemChat(SimpleStub)
{
    ...
    return true;
}
```

{% endtab %}

{% tab title="C#" %}

```csharp
// 定義接收各Stub函數時運行的功能的函數
g_Stub.P2PChat = (...) =>
{
    ...
    return true;
}
    
g_Stub.ShowChat = (...) =>
{
    ...
    return true;
}
    
g_Stub.SystemChat = (...) =>
{
    ...
    return true;
}
```

{% endtab %}
{% endtabs %}

### - 創建 NetClient 對象&#x20;

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

```cpp
std::shared_ptr<Proud::CNetClient> netClient(Proud::CNetClient::Create());
```

{% endtab %}

{% tab title="C#" %}

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

{% endtab %}
{% endtabs %}

### - 事件鏈接&#x20;

{% tabs %}
{% tab title="C++" %}
\
在服務器連接活動中設計所需的邏輯後使用即可。

```cpp
// 服務器連接完成後調用的事件
netClient->OnJoinServerComplete = 
   [&](ErrorInfo *info, const ByteArray &replyFromServer)
{
    ...
}
    
// 服務器斷開後要執行的事件
netClient->OnLeaveServer = [&](ErrorInfo *errorInfo)
{
    ...
}
    
// 新建 p2p 連接時發生的事件
// memberHostID : 連接到 p2p 的客戶端用戶名
// groupHostID : 連接到 p2p 的組用戶名
netClient->OnP2PMemberJoin = 
    [&](HostID memberHostID, HostID groupHostID,int memberCount, const ByteArray &customField)
{
    ...
}
    
// p2p 連接中斷時發生的事件 
netClient->OnP2PMemberLeave = 
    [](HostID memberHostID, HostID groupHostID,int memberCount)
{
    ...
}
```

{% endtab %}

{% tab title="C#" %}

```csharp
// 服務器連接完成後調用的事件
netClient.JoinServerCompleteHandler = (info, replyFromServer) =>
{
    ...
};

// 服務器斷開時運行的邏輯
netClient.LeaveServerHandler = (errorInfo) =>
{
    ...
};

// 在p2p組中添加新成員時執行的logic
netClient.P2PMemberJoinHandler = 
    (memberHostID, groupHostID, memberCount, customField) =>
{
    ...
};

// p2p成員連接中斷時執行的logic
netClient.P2PMemberLeaveHandler = (memberHostID, groupHostID, memberCount) =>
{
    ...
};
```

{% endtab %}
{% endtabs %}

### - 註冊 Proxy & Stub

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

```cpp
// 在CNetClient註冊用戶創建的Proxy和Stub
netClient->AttachProxy(&g_SimpleProxy);
netClient->AttachStub(&g_SimpleStub);
```

{% endtab %}

{% tab title="C#" %}

```csharp
// 將 proxy 和 stub 連接到 NetClient 實例
netClient.AttachProxy(g_Proxy);	    // Client-to-server => 從客戶端到服務器
netClient.AttachStub(g_Stub);	   // server-to-client => 從服務器到客戶端
```

{% endtab %}
{% endtabs %}

### - 服務器連接

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

```cpp
// 配置啓動服務器所需的 Parameter
Proud::CNetConnectionParam cp;

// 需要輸入像服務器一樣的協議版本。 也有可能乾脆不輸入。
cp.m_protocolVersion = g_Version;
cp.m_closeNoPingPongTcpConnections = false;
cp.m_serverPort = g_ServerPort;	

// 開始連接服務器
// 此函數立即return 。
// 之前在後臺嘗試連接，
// 結果將通過OnJoin Server Complete活動告知。
netClient->Connect(cp);
```

此後， 調用每幀的函數都會調用 <mark style="color:orange;">netClient</mark> -> <mark style="color:orange;">FrameMove()</mark> 方法。

```cpp
// 例子
while (true)
{
    netClient->FrameMove();
}
```

{% endtab %}

{% tab title="C#" %}

```csharp
// 配置啓動服務器所需的 Parameter
Nettention.Proud.NetConnectionParam cp = 
    new Nettention.Proud.NetConnectionParam();

// 與服務器相同的protocol version
cp.protocolVersion.Set(SimpleCSharp.Vars.m_Version);

// server address
cp.serverIP = "localhost";

// server port
cp.serverPort = (ushort)SimpleCSharp.Vars.m_serverPort;

// 服務器連接開始
// 這個函數立即返回。
// 之前在後臺嘗試連接，
// 通過OnJoin Server Complete活動可以知道連接完成。
netClient.Connect(cp);
```

隨後， 調用 <mark style="color:orange;">netClient.FrameMove()</mark> 方法來調用每幀調用函數 。

```csharp
// 例子
while (true)
{
    netClient.FrameMove();
}
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
基本上，您必須執行 <mark style="color:orange;">FrameMove</mark> 才能使事件發生。
{% endhint %}

## 伺服器

### - Proxy & Stub

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

```cpp
// 用於從服務器傳輸到客戶端的 RMI Proxy
Simple::Proxy g_SimpleProxy;


// 與RMI proxy不同,在函數重疊後使用。
class SimpleStub : public Simple::Stub
{
public:
	DECRMI_Simple_Chat;
};

// 服務器 -> 客戶端 RMI Stub 實例
SimpleStub g_SimpleStub;
```

{% endtab %}

{% tab title="C#" %}

```csharp
using namespace Nettention.Proud;

// 用於接收來自客戶端的消息的 RMI Stub
static Simple.Stub g_Stub = new Simple.Stub();

// RMI proxy
static Simple.Proxy g_Proxy = new Simple.Proxy();
```

{% endtab %}
{% endtabs %}

### - 定義 RMI 函數

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

<pre class="language-cpp"><code class="lang-cpp"><strong>DEFRMI_Simple_Chat(SimpleStub)
</strong>{
    ...
    return true;
}
</code></pre>

{% endtab %}

{% tab title="C#" %}

<pre class="language-csharp"><code class="lang-csharp">// 定義客戶端收到聊天消息時運行的邏輯 。
<strong>g_Stub.Chat = (...) =>
</strong>{
    ...
    return true;
};
</code></pre>

{% endtab %}
{% endtabs %}

### - 創建 NetServer 對象

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

```cpp
std::shared_ptr<Proud::CNetServer> srv(Proud::CNetServer::Create());
```

{% endtab %}

{% tab title="C#" %}

```csharp
Nettention.Proud.NetServer srv = new Nettention.Proud.NetServer();
```

{% endtab %}
{% endtabs %}

### - 事件鏈接

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

```cpp
// 設置客戶端連接服務器時運行的logic。
srv->OnClientJoin = 
    [](CNetClientInfo* clientInfo, ErrorInfo* errorInfo, const ByteArray& comment)
{
    ...
};

// 設置客戶端服務器斷開時運行的邏輯 。
srv->OnClientLeave = 
    [](CNetClientInfo *clientInfo, ErrorInfo *errorInfo, const ByteArray& comment)
{
    ...
};
```

{% endtab %}

{% tab title="C#" %}

```csharp
// 設置客戶端連接服務器時運行的logic。
srv.ClientJoinHandler = (clientInfo) =>
{
    ...
};

// 設置客戶端服務器斷開時運行的邏輯 。
srv.ClientLeaveHandler = (clientInfo, errorInfo, comment) =>
{
    ...
};
```

{% endtab %}
{% endtabs %}

### - 註冊 Proxy & Stub

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

```cpp
// 在生成的CNetServer實例上註冊proxy和stub。
srv->AttachStub(&g_SimpleStub);
srv->AttachProxy(&g_SimpleProxy);
```

{% endtab %}

{% tab title="C#" %}

```csharp
// 在生成的CNetServer實例上註冊proxy和stub。
srv.AttachStub(g_Stub);
srv.AttachProxy(g_Proxy);
```

{% endtab %}
{% endtabs %}

### 啓動服務器

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

```cpp
Proud::CStartServerParameter p1;

// This must be the same to the client. => 必須和客戶端一樣 。
p1.m_protocolVersion = g_Version; 

// TCP listening endpoint => tcp 連接進來 port
p1.m_tcpPorts.Add(g_ServerPort); 

/* 啓動服務器
該函數在失敗時會產生exception。
如果不把threading model具體化的話
通過信息發送的RMI函數和活動回撥是從分開的thread pool中呼叫的。
可以單獨指定thread model,相關部分可以參考幫助內容。 https://guide.nettention.com/cpp_ko#thread_pool_sharing
*/
srv->Start(p1);
```

{% endtab %}

{% tab title="C#" %}

```csharp
var p1 = new Nettention.Proud.StartServerParameter();

// This must be the same to the client. => 必須和客戶端一樣 。
p1.protocolVersion = new Nettention.Proud.Guid(Vars.m_Version); 

// TCP listening endpoint => tcp 連接進來 port
p1.tcpPorts.Add(Vars.m_serverPort);

/* 啓動服務器
該函數在失敗時會產生exception。
如果不把threading model具體化的話
通過信息發送的RMI函數和活動回撥是從分開的thread pool中呼叫的。
可以單獨指定thread model,相關部分可以參考幫助內容。 https://guide.nettention.com/cpp_ko#thread_pool_sharing
*/
srv.Start(p1);
```

{% endtab %}
{% endtabs %}

## 共通

### - vars.h

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

```cpp
extern Proud::Guid g_Version;
extern int g_ServerPort;
```

{% endtab %}
{% endtabs %}

### - vars.cpp, vars.cs

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

```cpp
// 定義協議版本
// 服務器和客戶端都必須具有相同的值。
PNGUID guid = { 0x3ae33249, 0xecc6, 0x4980, { 0xbc, 0x5d, 0x7b, 0xa, 0x99, 0x9c, 0x7, 0x39 } };
Guid g_Version = Guid(guid);

// TCP listening port number.
int g_ServerPort = 33334;
```

{% endtab %}

{% tab title="C#" %}

```csharp
namespace SimpleCSharp
{
    public class Vars
    {
        // 同樣適用於服務器和客戶端的protocol version
        public static System.Guid m_Version = new System.Guid("{ 0x3ae33249, 0xecc6, 0x4980, { 0xbc, 0x5d, 0x7b, 0xa, 0x99, 0x9c, 0x7, 0x39 } }");
        public static int m_serverPort = 33334;
    }
}
```

{% endtab %}
{% endtabs %}


---

# 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/sample.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.
