서버 테스트용 Bot 작성¶
이 챕터에서는 게임 클라이언트를 쓰지 않고 Bot 테스트를 할 수 있는 방법들을 소개합니다.
방법1: 아이펀 엔진의 Bot 콤포넌트 이용¶
아이펀엔진은 테스트 목적으로 마치 클라이언트인 것처럼 접속해서 클라이언트-서버 세션을 흉내내는 콤포넌트를 제공합니다. 게임 서버 코드에 이 콤포넌트를 포함시켜 별도의 서버 인스턴스로 띄우게 되면 다수의 bot 처럼 동작시킬 수 있습니다.
Note
Sequence Number Validation 은 지원하지 않습니다.
테스트 기능을 이용하기 위해서는 funapi/test/network.h 를 include 하고,
거기서 지원하는 funtest::Network
클래스와 funtest::Session
클래스를 활용합니다. (C# 의 경우 funtest.Network, funtest.Session)
funtest 의 Network 클래스¶
Network 클래스는 초기화를 담당하며 다음과 같은 메소드를 지원합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | namespace funtest {
class Network {
public:
struct Option {
struct Compression {
Compression() : type("none"), threshold(128) {
}
string type;
string dictionary;
size_t threshold;
};
Option(size_t io_threads_size = 4);
// 네트워크 스레드 수를 입력합니다. 기본 값은 4 입니다.
size_t io_threads_size;
// 아래 항목들은 서버의 MANIFEST/SessionService 에 입력된 값과
// 같아야합니다.
bool use_session_reliability;
bool send_session_id_only_once;
bool disable_tcp_nagle;
Compression tcp_compression;
Compression udp_compression;
Compression websocket_compression;
string server_encryption_public_key;
};
// session 이 열리고 닫힐 때 호출될 callback 과 네트워크 설정을
// 입력합니다.. 이 함수는 반드시 가장먼저 호출해야 합니다.
static void Install(const SessionOpenedHandler &opened_handler,
const SessionClosedHandler &closed_handler,
const Option &option);
// 필요할 경우 TCP 연결이 맺어지고, 끊길 때 호출될 callback 을 지정합니다.
static void RegisterTcpTransportHandler(
const TcpTransportAttachedHandler &tcp_attached_handler,
const TcpTransportDetachedHandler &tcp_detached_handler);
// JSON 타입의 메시지 핸들러를 등록합니다.
static void Register(const string &message_type,
const MessageHandler &message_handler);
// Protobuf 타입의 메시지 핸들러를 등록합니다.
static void Register2(const string &message_type,
const MessageHandler2 &message_handler);
};
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | namespace funtest
public static class Network
{
public struct Option
{
public struct Compression
{
public string Type;
public string Dictionary;
public ulong Threshold;
}
// 네트워크 스레드 수를 입력합니다. 기본 값은 4 입니다.
public ulong IoThreadsSize;
// 아래 항목들은 서버의 MANIFEST/SessionService 에 입력된 값과
// 같아야합니다.
public bool UseSessionReliability;
public bool SendSessionIdOnlyOnce;
public bool DisableTcpNagle;
public Compression TcpCompression;
public Compression UdpCompression;
public Compression WebSocketCompression;
public string ServerEncryptionPublicKey;
}
// session 이 열리고 닫힐 때 호출될 callback 과 io thread 의 수를
// 설정합니다. 이 함수는 반드시 가장먼저 호출해야 합니다.
public static void Install(
SessionOpenedHandler session_opened_handler,
SessionClosedHandler session_closed_handler,
Option option);
// 필요할 경우 TCP 연결이 맺어지고, 끊길 때 호출될 callback 을 지정합니다.
public static void RegisterTcpTransportHandler(
TcpTransportAttachedHandler tcp_attached_handler,
TcpTransportDetachedHandler tcp_detached_handler);
// JSON 타입의 메시지 핸들러를 등록합니다.
public static void RegisterMessageHandler(
string message_type, JsonMessageHandler message_handler);
// Protobuf 타입의 메시지 핸들러를 등록합니다.
public static void RegisterMessageHandler(
string message_type, ProtobufMessageHandler message_handler);
}
}
|
Note
서버에서 AccountManager::RedirectClient*() 로 클라이언트를 다른 서버로 이동시킬 경우 기존 세션이 닫히고 새로 연결된 서버에 새로운 세션이 열립니다. 이 때 Plugin 에서는 세션 닫힘, 세션 열림 핸들러가 불리지 않지만 funtest::Network 에서는 불립니다. 세션 닫힘 핸들러에서는 Reason 이 kClosedForRedirection 인지 확인하여 구분할 수 있으며, 세션 열림 핸들러에서는 Session::IsRedirected() 로 구분할 수 있습니다.
funtest 의 Session 클래스¶
Session 클래스는 하나의 클라이언트 연결을 담당합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | namespace funtest {
class Session {
public:
DECLARE_CLASS_PTR(Session);
enum State {
kOpening,
kOpened,
kClosed
};
// 세션 객체를 생성합니다. 이 함수를 호출한다고 세션이 맺어지는 것은
// 아닙니다. TCP 연결을 위해서는 아래 ConnectTcp(...) 함수를 이용해야됩니다.
static Ptr<Session> Create();
virtual ~Session();
// TCP 로 서버에 접속합니다. 만약 첫 연결이면 세션이 열리게 됩니다.
// 그렇지 않은 경우에는 이미 존재하던 세션을 계속 재사용하게 됩니다.
virtual void ConnectTcp(const string &ip, uint16_t port,
EncodingScheme encoding) = 0;
virtual void ConnectTcp(const boost::asio::ip::tcp::endpoint &endpoint,
EncodingScheme encoding) = 0;
// UDP 로 서버에 접속합니다. 만약 첫 연결이면 세션이 열리게 됩니다.
// 그렇지 않은 경우에는 이미 존재하던 세션을 계속 재사용하게 됩니다.
virtual void ConnectUdp(const string &ip, uint16_t port,
EncodingScheme encoding) = 0;
virtual void ConnectUdp(const boost::asio::ip::tcp::endpoint &endpoint,
EncodingScheme encoding) = 0;
// 현재 버전에선 작동되지 않습니다.
// HTTP 로 서버에 접속합니다. 만약 첫 연결이면 세션이 열리게 됩니다.
virtual void ConnectHttp(const string &url, EncodingScheme encoding) = 0;
virtual void ConnectHttp(const string &ip, uint16_t port,
EncodingScheme encoding) = 0;
virtual void ConnectHttp(const boost::asio::ip::tcp::endpoint &endpoint,
EncodingScheme encoding) = 0;
// 세션 아이디를 반환합니다.
virtual const SessionId &id() const = 0;
// 세션 상태를 반환합니다.
virtual State state() const = 0;
// Transport 의 연결 상태를 확인합니다.
virtual bool IsTransportAttached() const = 0;
virtual bool IsTransportAttached(TransportProtocol protocol) const = 0;
// 서버로 JSON 메시지를 보냅니다.
virtual void SendMessage(const string &message_type, const Json &message,
TransportProtocol protocol) = 0;
virtual void SendMessage(const string &message_type,
const Ptr<FunMessage> &message,
TransportProtocol protocol) = 0;
// Transport 연결을 닫습니다.
// 새로운 트랜스포트가 붙어서 세션을 계속 쓸 수 있기 때문에 세션은 계속 유효한 상태로 남습니다.
virtual void CloseTransport() = 0;
virtual void CloseTransport(TransportProtocol protocol) = 0;
// 세션을 닫습니다.
virtual void Close() = 0;
// 세션 별 Context 를 다루는 인터페이스입니다. 이 컨텍스트는 세션이 살아있는 동안 유효합니다.
// 예)
// boost::mutex::scoped_lock lock(*session); // 필요할 경우 lock 으로 보호
// if (session->GetContext()["my_state"] == ...) {
// ...
// session->GetContext()["my_state"] = ...;
// }
virtual void SetContext(const Json &ctxt) = 0;
virtual Json &GetContext() = 0;
virtual const Json &GetContext() const = 0;
virtual boost::mutex &GetContextMutex() const = 0;
virtual operator boost::mutex &() const = 0;
};
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | namespace funtest {
public class Session
{
public enum SessionState
{
kOpening = 0,
kOpened,
kClosed
}
public enum EncodingScheme
{
kUnknownEncoding = 0,
kJsonEncoding,
kProtobufEncoding
}
// 세션 객체를 생성합니다. 이 함수를 호출한다고 세션이 맺어지는 것은
// 아닙니다. TCP 연결을 위해서는 아래 ConnectTcp(...) 함수를 이용해야됩니다.
public Session();
~Session();
// TCP 로 서버에 접속합니다. 만약 첫 연결이면 세션이 열리게 됩니다.
// 그렇지 않은 경우에는 이미 존재하던 세션을 계속 재사용하게 됩니다.
public void ConnectTcp(string ip, ushort port, EncodingScheme encoding);
public void ConnectTcp(System.Net.IPEndPoint address,
EncodingScheme encoding);
// UDP 로 서버에 접속합니다. 만약 첫 연결이면 세션이 열리게 됩니다.
// 그렇지 않은 경우에는 이미 존재하던 세션을 계속 재사용하게 됩니다.
public void ConnectUdp(string ip, ushort port, EncodingScheme encoding);
public void ConnectUdp(System.Net.IPEndPoint address,
EncodingScheme encoding);
// 현재 버전에선 작동되지 않습니다.
// HTTP 로 서버에 접속합니다. 만약 첫 연결이면 세션이 열리게 됩니다.
public void ConnectHttp(string ip, ushort port, EncodingScheme encoding);
public void ConnectHttp(System.Net.IPEndPoint address,
EncodingScheme encoding);
public void ConnectHttp(string url, EncodingScheme encoding);
// 세션 아이디를 반환합니다.
public System.Guid Id;
// 세션 상태를 반환합니다.
public SessionState State;
// Transport 의 연결 상태를 확인합니다.
public bool IsTransportAttached();
public bool IsTransportAttached(funapi.Session.Transport transport);
// 서버로 JSON 메시지를 보냅니다.
public void SendMessage(string message_type, JObject message,
funapi.Session.Transport transport);
// 서버로 Protocol buffer 메시지를 보냅니다.
public void SendMessage(string message_type, FunMessage message,
funapi.Session.Transport transport);
// Transport 연결을 닫습니다.
// 새로운 트랜스포트가 붙어서 세션을 계속 쓸 수 있기 때문에 세션은 계속 유효한 상태로 남습니다.
public void CloseTransport();
public void CloseTransport(funapi.Session.Transport transport);
// 세션을 닫습니다.
public void Close();
// 세션 별 Context 를 다루는 인터페이스입니다. 이 컨텍스트는 세션이 살아있는 동안 유효합니다.
// 예)
// lock (session) // 필요할 경우 lock 으로 보호
// {
// if (session.Context ["my_state"] == ...) {
// ...
// session.Context ["my_state"] = ...;
// }
// }
public JObject Context;
}
}
|
예제: 300 개의 Bot 이 각각 5000 번씩 echo 전송¶
아래 예제는 funtest 를 이용하여 서버 실행 시 Start()
함수에서 funtest 세션을 300개 생성하여
세션별로 PbufEcho 메시지를 서버로 5000번 전송하는 예제입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | #include <funapi/test/network.h>
static bool Install(const ArgumentMap &arguments) {
// 생략...
// funtest 세션이 열리고 닫힐 때 불릴 콜백함수를 등록합니다.
funtest::Network::Option option;
funtest::Network::Install(OnSessionOpened, OnSessionClosed, option);
// TCP 연결이 맺어지고 끊어질 때 불릴 콜백함수를 등록합니다.
funtest::Network::RegisterTcpTransportHandler(OnTcpAttached, OnTcpDetached);
// 서버로부터 전달받을 protobuf 메시지 핸들러를 등록합니다.
funtest::Network::Register2("pbuf_echo", OnEcho);
return true;
}
static bool Start() {
// 생략...
// 서버에게 메시지를 보낼 funtest 세션을 300개 생성합니다.
for (size_t i = 0; i < 300; ++i) {
Ptr<funtest::Session> session = funtest::Session::Create();
session->GetContext()["count"] = 5000;
session->ConnectTcp("127.0.0.1", 8013, kProtobufEncoding);
}
return true;
}
// funtest 세션이 열릴 때 호출되는 콜백함수 입니다.
static void OnSessionOpened(const Ptr<funtest::Session> &session) {
LOG(INFO) << "[test_client] session created: sid=" << session->id();
// 서버에게 보낼 임의의 문자를 생성합니다.
string message = RandomGenerator::GenerateAlphanumeric(5, 50);
session->GetContext()["sent_message"] = message;
// 서버에게 보낼 메시지를 생성하여 전달합니다.
Ptr<FunMessage> msg(new FunMessage);
PbufEchoMessage *echo_msg = msg->MutableExtension(pbuf_echo);
echo_msg->set_msg(message);
session->SendMessage("pbuf_echo", msg, kTcp);
};
// funtest 세션이 닫힐 때 호출되는 콜백함수 입니다.
static void OnSessionClosed(const Ptr<funtest::Session> &session, SessionCloseReason reason) {
LOG(INFO) << "[test_client] session closed: sid=" << session->id();
};
// TCP 연결이 맺어질 떄 호출되는 콜백함수 입니다.
static void OnTcpAttached(const Ptr<funtest::Session> &session, bool connected) {
if (not connected) {
LOG(ERROR) << "[test_client] failed to connect to the server";
}
};
// TCP 연결이 끊어질 때 호출되는 콜백함수 입니다.
static void OnTcpDetached(const Ptr<funtest::Session> &session) {
LOG(INFO) << "[test_client] tcp transport disconnected: sid=" << session->id();
};
// 서버로부터 PbufEcho 메시지를 받았을 때 불리는 핸들러입니다.
static void OnEcho(const Ptr<funtest::Session> &session, const Ptr<FunMessage> &msg) {
BOOST_ASSERT(msg->HasExtension(pbuf_echo));
const PbufEchoMessage &echo_msg = msg->GetExtension(pbuf_echo);
const string &message = echo_msg.msg();
string sent_message = session->GetContext()["sent_message"].GetString();
BOOST_ASSERT(sent_message == message);
// funtest 세션 생성 시 지정한 횟수만큼 메시지 전송을 반복합니다.
Json &count_ctxt = session->GetContext()["count"];
count_ctxt.SetInteger(count_ctxt.GetInteger() - 1);
if (count_ctxt.GetInteger() == 0) {
LOG(INFO) << "[test_client] complete: sid=" << session->id();
return;
}
{
string message = RandomGenerator::GenerateAlphanumeric(5, 50);
session->GetContext()["sent_message"] = message;
Ptr<FunMessage> msg(new FunMessage);
PbufEchoMessage *echo_msg = msg->MutableExtension(pbuf_echo);
echo_msg->set_msg(message);
session->SendMessage("pbuf_echo", msg, kTcp);
}
};
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | using funapi;
public static bool Install(ArgumentMap arguments)
{
// 생략...
// funtest 세션이 열리고 닫힐 때 불릴 콜백함수를 등록합니다.
funapi.funtest.Network.Option option;
funapi.funtest.Network.Install (
new funapi.funtest.Network.SessionOpenedHandler (OnSessionOpened),
new funapi.funtest.Network.SessionClosedHandler (OnSessionClosed),
option);
// TCP 연결이 맺어지고 끊어질 때 불릴 콜백함수를 등록합니다.
funapi.funtest.Network.RegisterTcpTransportHandler (OnTcpAttached,
OnTcpDetached);
// 서버로부터 전달받을 protobuf 메시지 핸들러를 등록합니다.
funapi.funtest.Network.RegisterMessageHandler ("pbuf_echo", OnEcho));
}
public static void Start()
{
// 서버에게 메시지를 보낼 funtest 세션을 300개 생성합니다.
for (int i = 0; i < 300; ++i)
{
funapi.funtest.Session session = new funapi.funtest.Session ();
session.Context["count"] = 5000;
session.ConnectTcp (
"127.0.0.1",
8013,
funapi.funtest.Session.EncodingScheme.kProtobufEncoding);
}
}
// funtest 세션이 열릴 때 호출되는 콜백함수 입니다.
public static void OnSessionOpened(funapi.funtest.Session session)
{
Log.Info ("[test client] Session opened: session_id={0}", session.Id);
// 서버에게 보낼 임의의 문자를 생성합니다.
string message = RandomGenerator.GenerateAlphanumeric(5, 50);
session.Context ["sent_message"] = message;
// 서버에게 보낼 메시지를 생성하여 전달합니다.
FunMessage msg = new FunMessage();
PbufEchoMessage echo_msg = new PbufEchoMessage();
echo_msg.msg = message;
msg.AppendExtension_pbuf_echo (echo_msg);
session.SendMessage("pbuf_echo", msg, funapi.Session.Transport.kTcp);
}
// funtest 세션이 닫힐 때 호출되는 콜백함수 입니다.
public static void OnSessionClosed(funapi.funtest.Session session,
Session.CloseReason reason)
{
Log.Info ("[test client] Session closed: session_id={0}, reason={1}",
session.Id, reason);
}
// TCP 연결이 맺어질 때 호출되는 콜백함수 입니다.
public static void OnTcpAttached(funapi.funtest.Session session,
bool connected)
{
if (!connected) {
Log.Error ("[test_client] failed to connect to the server");
}
}
// TCP 연결이 끊어질 때 호출되는 콜백함수 입니다.
public static void OnTcpDetached(funapi.funtest.Session session)
{
Log.Info ("[test_client] tcp transport disconnected: sid={0}",
session.Id);
}
// 서버로부터 PbufEcho 메시지를 받았을 때 불리는 핸들러입니다.
public static void OnEcho(funapi.funtest.Session session,
FunMessage msg) {
PbufEchoMessage echo;
if (!msg.TryGetExtension_pbuf_echo (out echo))
{
Log.Error ("OnEchoPbuf: Wrong message.");
return;
}
string message = echo.msg;
Log.Info ("client recv echo.msg. {0}", echo.msg);
string sent_message = (string) session.Context ["sent_message"];
Log.Assert (sent_message == message);
int count = (int) session.Context ["count"];
session.Context ["count"] = count - 1;
// funtest 세션 생성 시 지정한 횟수만큼 메시지 전송을 반복합니다.
if ((int) session.Context ["count"] == 0)
{
Log.Info("[test_client] complete: sid={0}",
session.Id.ToString());
return;
}
{
string message2 = RandomGenerator.GenerateAlphanumeric(5, 50);
session.Context ["sent_message"] = message2;
FunMessage fun_message = new FunMessage ();
PbufEchoMessage echo2 = new PbufEchoMessage();
echo_msg.msg = message2;
fun_message.AppendExtension_pbuf_echo(echo2);
session.SendMessage("pbuf_echo", fun_message,
funapi.Session.Transport.kTcp);
}
}
|
방법2: 유니티 플러그인을 이용¶
서버를 테스트하기 위한 봇 클라이언트는 하나의 호스트에서 동시에 여러개를 실행시킬 수 있도록 적은 리소스로 에디터 없이 실행할 수 있도록 만드는 것이 좋습니다.
아이펀 엔진 의 클라이언트 플러그인은 에디터 없이도 봇 클라이언트를 제작할 수 있도록 샘플 프로젝트를 제공합니다.
Mono C# 라이브러리 이용¶
Mono 라이브러리가 설치되어 있는 리눅스의 커맨드라인에서 Mono 의 C# 컴파일러를 이용해서 클라이언트를 컴파일하고 실행하는 방법에 대해서 설명합니다.
유니티 클라이언트 플러그인은 여기 에서 받으실 수 있으며,
샘플 코드는 유니티 클라이언트 플러그인 내부의 csharp-samples
폴더에 있습니다.
Important
아이펀 엔진 유니티 플러그인을 사용해서 만들었지만 Mono 로 컴파일해야 하므로 유니티 에디터에서 제공하는 라이브러리는 사용할 수 없습니다. 그러므로 실제 유니티 프로젝트의 소스코드를 그대로 사용할 수 없는 것에 주의해 주시기 바랍니다.
Note
샘플 코드에서 미리 정의된 PbufEcho 메시지 외에 사용자가 정의한 Protobuf 메시지를 사용할 수 있습니다.
서버 빌드로 생성된 dll 파일을 csharp-samples
디렉터리 밑에 넣어주시면
샘플 코드에서 사용자가 정의한 Protobuf 메시지를 사용할 수 있습니다.
샘플 코드에서 src/client.cs
파일은 클라이언트 하나에 대한 동작을 정의합니다.
샘플 코드에서 Client 는 TCP 연결을 맺고 미리 정의된 echo 메시지를 보내고
받는 일을 수행합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | class Client
{
public Client (int id)
{
// 클라이언트 고유 아이디
id_ = id;
}
// Connect 함수를 통해 서버와 연결을 시도합니다.
public void Connect (TransportProtocol protocol, FunEncoding encoding)
{
if (session == null)
{
SessionOption option = new SessionOption();
option.sessionReliability = false;
option.sendSessionIdOnlyOnce = false;
// 세션이 없는 경우 서버와 통신할 세션을 생성하고 연결과 관련된 콜백함수를 등록합니다.
session = FunapiSession.Create(address, option);
session.SessionEventCallback += onSessionEvent;
session.TransportEventCallback += onTransportEvent;
session.TransportErrorCallback += onTransportError;
session.ReceivedMessageCallback += onReceivedMessage;
}
session.Connect(protocol, encoding, getPort(protocol, encoding));
}
...
}
|
src/main.cs 파일은 테스트 프로젝트 자체를 실행하는 파일입니다. Thread 를 생성하여 더미 클라이언트를 만들고, 서버에게 메시지를 보내는 함수를 호출합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | class TesterMain
{
// 테스트를 진행할 횟수
const int kTestCount = 10;
// 더미 클라이언트 개수
const int kClientMax = 100;
// 서버 주소
const string kServerIp = "127.0.0.1";
// Main 함수
public static void Main ()
{
Client.address = kServerIp;
// 테스트 시작
new TesterMain().start();
}
void start ()
{
int testCount = 0;
// 지정한 테스트 횟수만큼 반복합니다.
while (testCount < kTestCount)
{
// onTest 함수를 수행할 Thread를 더미 클라이언트 개수만큼 생성합니다.
for (int i = 0; i < kClientMax; ++i)
{
Thread t = new Thread(new ThreadStart(onTest));
t.IsBackground = true;
threads_.Add(t);
t.Start();
}
foreach (Thread t in threads_)
{
t.Join();
}
threads_.Clear();
Thread.Sleep(1000);
}
// 플러그인 업데이터 종료
FunapiMono.Stop();
// 테스트 프로세스 종료
Process.GetCurrentProcess().Kill();
}
void onTest()
{
// 더미 클라이언트를 생성합니다.
Client client = new Client(++client_id_);
// TCP + Protobuf 타입으로 서버와 연결합니다.
client.Connect(TransportProtocol.kTcp, FunEncoding.kProtobuf);
while (!client.Connected)
Thread.Sleep(10);
// 서버에게 Echo Message를 전송합니다.
client.SendEchoMessageWithCount(TransportProtocol.kTcp, 1000);
while (!client.IsDone)
Thread.Sleep(10);
client.Stop();
client = null;
}
...
// 클라이언트 아이디
int client_id_ = 0;
// 클라이언트 목록
List<Thread> threads_ = new List<Thread>();
}
|
Note
전체 코드는 csharp-samples/src 폴더에 있습니다. 경로 수정이나 파일 추가가 필요하다면 csharp-samples/makefile 파일을 수정하면 됩니다.
테스트 코드 구현이 끝났다면 이제 빌드하고 실행하기만 하면 됩니다. 터미널에서 makefile 파일이 있는 폴더로 이동한 후에 다음 명령을 실행하면 빌드 후 실행됩니다.
$ make
$ mono tester.exe
Visual Studio 이용¶
Visual Studio 에서 유니티 플러그인을 이용해 봇 테스트를 할 수 있습니다.
플러그인 폴더 아래 csharp-samples/vs2015
폴더에 Visual Studio 용 솔루션
파일이 있습니다.
Note
샘플 프로젝트는 VS 2015 Community 버전에서 작성되었으나, VS 2019 Community 버전에서도 동작합니다.
프로젝트 설정¶
Visual Studio 로 funapi-plugin-unity.sln
파일을 열면
funapi-plugin-unity 프로젝트가 로드되는데 포함되어 있는 모든 파일은
상위 폴더의 파일을 참조로 링크되어 있습니다.
샘플 코드도 상위 src 폴더 내의 파일을 그대로 사용하므로 샘플 코드에 대한 설명은 앞서 설명한 Mono C# 라이브러리 이용 을 참고해 주시기 바랍니다.
Note
유니티 플러그인의 코드를 Visual Studio 에서 실행하기 위해서는 컴파일 옵션이 필요합니다.
프로젝트 속성->빌드->조건부 컴파일 기호에 NO_UNITY 가 설정되어 있는지 확인해 주세요.
Note
C# Mono와 마찬가지로 UnityEngine 라이브러리는 사용할 수 없음에 주의해주세요.
빌드 및 실행¶
빌드 후 실행하면 콘솔창으로 로그를 확인 할 수 있는데 테스트가 끝나면 창이 자동으로 닫힙니다.
콘솔창이 닫히지 않게 싶은 경우에는 Ctrl + F5 로 실행하면 테스트가 끝나도 창이 닫히지 않습니다.
콘솔의 경우 버퍼 크기가 정해져 있어 최근 로그만 남습니다. 버퍼 크기는 콘솔의 속성창에서 변경할 수 있습니다.