# 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 그룹에 새로운 멤버가 추가됐을 때 실행할 로직
netClient.P2PMemberJoinHandler = 
    (memberHostID, groupHostID, memberCount, customField) =>
{
    ...
};

// p2p 멤버 접속이 끊겼을 때 실행할 로직
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
// NetClient 인스턴스에 proxy와 stub 연결
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;

// 서버와 같은 protocol 버전을 입력해야 한다. 아예 입력하지 않을 수도 있음.
cp.m_protocolVersion = g_Version;
cp.m_closeNoPingPongTcpConnections = false;
cp.m_serverPort = g_ServerPort;	

// 서버와 연결 시작
// 이 함수는 즉시 return 됩니다.
// 그동안 백그라운드에서 연결을 시도하고,
// 결과는 OnJoinServerComplete 이벤트에 의해서 알려집니다.
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;

// 서버 연결 시작
// 이 함수는 즉시 리턴됩니다.
// 그동안 백그라운드에서 연결을 시도하고,
// OnJoinServerComplete 이벤트에 의해 접속 완료를 알 수 있습니다.
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
// 클라이언트가 서버에 접속했을 때 실행할 로직을 설정한다.
srv->OnClientJoin = 
    [](CNetClientInfo* clientInfo, ErrorInfo* errorInfo, const ByteArray& comment)
{
    ...
};

// 클라이언트 서버 접속이 끊어졌을 때 실행할 로직을 설정한다.
srv->OnClientLeave = 
    [](CNetClientInfo *clientInfo, ErrorInfo *errorInfo, const ByteArray& comment)
{
    ...
};
```

{% endtab %}

{% tab title="C#" %}

```csharp
// 클라이언트가 서버에 접속했을 때 실행할 로직을 설정한다.
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/pn_reference_ko/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.
