JSON-RPC는 JSON(JavaScript Object Notation)을 기반으로 한 경량 원격 프로시저 호출(RPC) 프로토콜입니다. 이 프로토콜을 사용하면 클라이언트와 서버 간의 통신을 효율적으로 처리할 수 있으며, RESTful API보다 단순한 구조로 요청과 응답을 주고받을 수 있습니다.
C++ 기반의 게임 서버에서 JSON-RPC를 활용하면 클라이언트와의 상호작용을 더욱 원활하게 처리할 수 있습니다. 특히, JSON의 경량성 덕분에 네트워크 트래픽을 최소화하면서도 명확한 데이터 구조를 유지할 수 있습니다. 또한, JSON-RPC는 요청과 응답을 비동기적으로 처리할 수 있어 대규모 트래픽이 발생하는 게임 서버 환경에서도 유용하게 활용될 수 있습니다.
본 기사에서는 C++에서 JSON-RPC를 활용하여 클라이언트와 통신하는 방법을 소개하고, 서버 및 클라이언트 구현 과정, 예외 처리, 보안 고려 사항, 성능 최적화 기법까지 단계별로 설명합니다. 이를 통해 JSON-RPC를 활용한 효율적인 게임 서버 구축 방법을 익힐 수 있습니다.
JSON-RPC 개요 및 특징
JSON-RPC는 JSON(JavaScript Object Notation) 형식을 기반으로 하는 경량 원격 프로시저 호출(RPC) 프로토콜입니다. 클라이언트가 서버에 원격 함수 호출을 요청하면, 서버는 해당 요청을 처리한 후 JSON 형식의 응답을 반환하는 방식으로 동작합니다.
JSON-RPC의 기본 개념
JSON-RPC는 매우 단순한 프로토콜로, 기본적으로 다음과 같은 특징을 갖습니다.
- JSON 기반: 요청과 응답 모두 JSON 형식으로 표현되므로 사람이 쉽게 읽고 이해할 수 있습니다.
- 비상태(Stateless) 프로토콜: 각 요청은 독립적으로 처리되며, 별도의 세션 관리가 필요하지 않습니다.
- 메서드 호출 방식: 클라이언트는 원격 서버의 특정 메서드를 호출하며, 서버는 해당 요청을 처리한 후 결과를 반환합니다.
- 비동기 처리 지원: 요청을 순차적으로 처리할 필요 없이, 여러 요청을 동시에 보낼 수 있으며 응답도 개별적으로 처리할 수 있습니다.
- 배치 요청(Batch Request) 가능: 여러 개의 요청을 하나의 JSON 배열로 묶어서 서버에 전송할 수 있습니다.
JSON-RPC 메시지 구조
JSON-RPC 메시지는 크게 요청(request)과 응답(response)으로 구성됩니다.
요청(Request) 예제
{
"jsonrpc": "2.0",
"method": "getPlayerInfo",
"params": { "playerId": 12345 },
"id": 1
}
"jsonrpc": "2.0"
→ JSON-RPC 버전(현재 2.0이 최신)"method": "getPlayerInfo"
→ 호출할 서버 측 메서드 이름"params": { "playerId": 12345 }
→ 메서드에 전달할 인자"id": 1
→ 응답을 구별하기 위한 요청 ID
응답(Response) 예제
{
"jsonrpc": "2.0",
"result": {
"name": "PlayerOne",
"level": 42,
"score": 98765
},
"id": 1
}
"result"
→ 요청한 메서드의 실행 결과"id": 1
→ 요청과 응답을 매칭하는 ID
만약 요청 처리 중 오류가 발생하면 "result"
대신 "error"
필드가 포함됩니다.
오류 응답(Error Response) 예제
{
"jsonrpc": "2.0",
"error": {
"code": -32601,
"message": "Method not found"
},
"id": 1
}
"error.code"
→ 오류 코드(예: -32601은 “메서드 없음” 오류)"error.message"
→ 오류 설명
JSON-RPC의 장점과 단점
✅ 장점
- 단순하고 가벼운 프로토콜로, 빠르게 구현할 수 있음
- RESTful API보다 데이터 오버헤드가 적고, 직관적인 호출 방식 제공
- 언어 독립적이며, 다양한 프로그래밍 언어에서 지원됨
- 배치 요청과 비동기 요청을 지원하여 성능 최적화 가능
❌ 단점
- REST API에 비해 URL 기반 접근이 불가능하고, 호출하는 메서드명을 직접 전달해야 함
- 인증 및 보안 기능이 기본적으로 포함되어 있지 않아 별도로 구현해야 함
JSON-RPC는 이러한 특징 덕분에 게임 서버와 같은 실시간 통신이 필요한 환경에서 널리 사용됩니다. 다음 섹션에서는 C++ 기반 서버에서 JSON-RPC를 구현하는 방법을 살펴보겠습니다.
JSON-RPC를 활용한 클라이언트-서버 통신 모델
JSON-RPC는 클라이언트와 서버 간의 원격 프로시저 호출을 가능하게 하며, 간결한 데이터 구조와 효율적인 요청-응답 처리를 제공합니다. 특히, 게임 서버에서는 클라이언트와의 빠른 데이터 교환이 필수적이므로 JSON-RPC를 사용하면 효과적인 비동기 처리가 가능합니다.
JSON-RPC의 요청-응답 패턴
JSON-RPC에서 클라이언트와 서버 간의 통신은 기본적으로 다음과 같은 패턴으로 동작합니다.
- 클라이언트가 JSON-RPC 요청을 생성하여 서버로 전송
- 서버가 요청을 처리하고, 결과를 포함한 응답을 반환
- 클라이언트가 응답을 수신하고, 결과를 활용
이 과정에서 클라이언트는 여러 개의 요청을 한꺼번에 전송하는 배치 요청(batch request)을 사용할 수도 있으며, 응답을 기다리지 않고 다른 요청을 보낼 수 있는 비동기 처리도 가능합니다.
JSON-RPC 통신 흐름 예제
다음은 JSON-RPC를 활용한 클라이언트-서버 통신 흐름을 보여주는 예제입니다.
1. 클라이언트가 서버에 요청을 보냄
클라이언트가 특정 플레이어 정보를 요청한다고 가정하면, 다음과 같은 JSON 메시지를 서버로 전송합니다.
{
"jsonrpc": "2.0",
"method": "getPlayerInfo",
"params": { "playerId": 12345 },
"id": 1
}
2. 서버가 요청을 처리하고 응답을 반환
서버는 getPlayerInfo
메서드를 실행하고, 해당 플레이어의 정보를 클라이언트에 반환합니다.
{
"jsonrpc": "2.0",
"result": {
"name": "PlayerOne",
"level": 42,
"score": 98765
},
"id": 1
}
3. 클라이언트가 응답을 수신하고 데이터 활용
클라이언트는 서버로부터 받은 데이터를 파싱하여, UI 업데이트나 게임 로직에 활용할 수 있습니다.
비동기 요청과 배치 요청
JSON-RPC는 비동기 처리를 지원하며, 여러 요청을 동시에 전송하는 배치 요청 기능도 제공합니다.
배치 요청 예제
클라이언트가 동시에 두 개의 요청을 보낼 수 있습니다.
[
{
"jsonrpc": "2.0",
"method": "getPlayerInfo",
"params": { "playerId": 12345 },
"id": 1
},
{
"jsonrpc": "2.0",
"method": "getLeaderboard",
"params": { "gameMode": "ranked" },
"id": 2
}
]
서버는 요청을 개별적으로 처리한 후, 두 개의 응답을 반환합니다.
[
{
"jsonrpc": "2.0",
"result": {
"name": "PlayerOne",
"level": 42,
"score": 98765
},
"id": 1
},
{
"jsonrpc": "2.0",
"result": [
{ "name": "PlayerTwo", "score": 120000 },
{ "name": "PlayerOne", "score": 98765 }
],
"id": 2
}
]
JSON-RPC의 클라이언트-서버 통신 방식 정리
방식 | 설명 |
---|---|
동기 요청 | 클라이언트가 요청을 보내고 응답을 받을 때까지 대기 |
비동기 요청 | 클라이언트가 요청을 보내고 즉시 다른 작업 수행 가능 |
배치 요청 | 여러 개의 요청을 하나의 JSON 배열로 묶어 처리 |
이제 다음 섹션에서는 실제 C++에서 JSON-RPC 서버를 구현하는 방법을 알아보겠습니다.
C++에서 JSON-RPC 서버 구현하기
C++에서 JSON-RPC 서버를 구현하려면 JSON을 처리하는 라이브러리와 네트워크 통신을 담당할 HTTP 또는 WebSocket 서버가 필요합니다. 본 섹션에서는 JSON-RPC 라이브러리를 사용하여 기본적인 JSON-RPC 서버를 구축하는 방법을 설명합니다.
JSON-RPC 서버 구축을 위한 라이브러리
C++에서 JSON-RPC 서버를 구축하기 위해 다음과 같은 라이브러리를 사용할 수 있습니다.
- JSON-RPC 2.0을 지원하는 C++ 라이브러리
- HTTP, WebSocket, TCP 소켓을 통한 통신 지원
- 클라이언트와 서버 모두 구현 가능
- Boost.Beast + nlohmann/json
- Boost.Beast를 이용한 HTTP 서버
- nlohmann/json을 활용한 JSON 처리
- Crow (경량 C++ 웹 프레임워크)
- Express.js와 유사한 API 제공
- JSON-RPC 구현 가능
본 예제에서는 json-rpc-cpp 라이브러리를 사용하여 JSON-RPC 서버를 구현합니다.
JSON-RPC 서버 기본 구현
우선, json-rpc-cpp
라이브러리를 설치해야 합니다.
Ubuntu 설치 명령어:
sudo apt install libjsonrpccpp-dev
1. JSON-RPC 서버 코드 작성
다음은 C++에서 JSON-RPC 서버를 구축하는 기본 코드입니다.
#include <jsonrpccpp/server/connectors/httpserver.h>
#include <jsonrpccpp/server.h>
#include <json/json.h>
#include <iostream>
using namespace jsonrpc;
using namespace std;
// JSON-RPC 서버 클래스 정의
class GameServer : public AbstractServer<GameServer> {
public:
GameServer(HttpServer &server) : AbstractServer<GameServer>(server) {
this->bindAndAddMethod(Procedure("getPlayerInfo", PARAMS_BY_NAME, "playerId", Json::integerValue, NULL), &GameServer::getPlayerInfo);
}
void getPlayerInfo(const Json::Value &request, Json::Value &response) {
int playerId = request["playerId"].asInt();
cout << "Received request for Player ID: " << playerId << endl;
// 가상의 플레이어 정보 반환
response["name"] = "PlayerOne";
response["level"] = 42;
response["score"] = 98765;
}
};
int main() {
HttpServer server(8080); // 8080 포트에서 HTTP 서버 실행
GameServer gameServer(server);
cout << "JSON-RPC 서버 실행 중..." << endl;
gameServer.StartListening();
// 서버를 종료하려면 Enter 키 입력
getchar();
gameServer.StopListening();
return 0;
}
2. 코드 설명
HttpServer server(8080);
→ HTTP 서버를 8080 포트에서 실행bindAndAddMethod
→getPlayerInfo
메서드를 JSON-RPC 엔드포인트로 등록getPlayerInfo
→ 클라이언트가 특정playerId
를 요청하면 해당 정보를 반환gameServer.StartListening();
→ JSON-RPC 서버 시작- 클라이언트 요청을 처리한 후 응답 반환
3. JSON-RPC 서버 실행 및 테스트
코드를 빌드한 후 실행하면 JSON-RPC 서버가 8080 포트에서 실행됩니다.
실행 후, 터미널에서 curl
명령어를 사용하여 서버가 정상적으로 응답하는지 테스트할 수 있습니다.
curl -X POST http://localhost:8080 -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0",
"method": "getPlayerInfo",
"params": { "playerId": 12345 },
"id": 1
}'
서버 응답 예제:
{
"jsonrpc": "2.0",
"result": {
"name": "PlayerOne",
"level": 42,
"score": 98765
},
"id": 1
}
JSON-RPC 서버 확장
위의 기본 서버 구현을 확장하여 추가 기능을 추가할 수 있습니다. 예를 들어, 게임 서버에서 다음과 같은 추가 메서드를 구현할 수 있습니다.
- 랭킹 조회 (
getLeaderboard
) - 아이템 구매 (
purchaseItem
) - 게임 매치메이킹 (
matchmaking
)
다음 코드에서는 랭킹 조회 기능을 추가하는 예제입니다.
this->bindAndAddMethod(Procedure("getLeaderboard", PARAMS_BY_NAME, NULL), &GameServer::getLeaderboard);
void getLeaderboard(const Json::Value &request, Json::Value &response) {
Json::Value players(Json::arrayValue);
Json::Value player1;
player1["name"] = "PlayerOne";
player1["score"] = 98765;
Json::Value player2;
player2["name"] = "PlayerTwo";
player2["score"] = 120000;
players.append(player2);
players.append(player1);
response["leaderboard"] = players;
}
이제 클라이언트가 getLeaderboard
요청을 보낼 수 있으며, 랭킹 정보를 JSON 응답으로 받을 수 있습니다.
정리
- C++에서 JSON-RPC 서버를 구현하려면 json-rpc-cpp 같은 라이브러리를 사용하면 편리합니다.
- 요청 메서드를
bindAndAddMethod
로 등록하고, JSON 응답을 반환하는 구조입니다. - 테스트 시
curl
을 사용하여 JSON-RPC 요청을 보낼 수 있으며, 서버에서 응답을 확인할 수 있습니다.
다음 섹션에서는 C++에서 JSON-RPC 클라이언트를 구현하는 방법을 살펴보겠습니다.
C++에서 JSON-RPC 클라이언트 구현하기
C++에서 JSON-RPC 클라이언트를 구현하면 서버에 JSON-RPC 요청을 보내고 응답을 받을 수 있습니다. 클라이언트는 HTTP, WebSocket, TCP를 통해 서버와 통신하며, 이를 위해 JSON-RPC 라이브러리와 HTTP 요청 처리를 위한 라이브러리를 활용합니다.
본 섹션에서는 json-rpc-cpp
라이브러리를 사용하여 JSON-RPC 클라이언트를 구현하는 방법을 설명합니다.
1. JSON-RPC 클라이언트 구축을 위한 환경 설정
json-rpc-cpp 라이브러리를 설치해야 합니다.
Ubuntu 설치 명령어:
sudo apt install libjsonrpccpp-dev
Windows 사용자의 경우, vcpkg
를 통해 설치할 수 있습니다.
vcpkg install json-rpc-cpp
2. JSON-RPC 클라이언트 코드 구현
다음 코드는 C++에서 JSON-RPC 클라이언트를 구현하는 예제입니다.
#include <jsonrpccpp/client/connectors/httpclient.h>
#include <jsonrpccpp/client.h>
#include <json/json.h>
#include <iostream>
using namespace jsonrpc;
using namespace std;
int main() {
// JSON-RPC 서버 주소 지정
HttpClient httpClient("http://localhost:8080");
Client client(httpClient, jsonrpc::JSONRPC_CLIENT_V2);
try {
// getPlayerInfo 요청 생성
Json::Value params;
params["playerId"] = 12345;
// 서버로 요청을 전송하고 응답을 받음
Json::Value response = client.CallMethod("getPlayerInfo", params);
// 응답 출력
cout << "Player Name: " << response["name"].asString() << endl;
cout << "Level: " << response["level"].asInt() << endl;
cout << "Score: " << response["score"].asInt() << endl;
} catch (const JsonRpcException &e) {
cerr << "JSON-RPC 오류 발생: " << e.GetMessage() << endl;
}
return 0;
}
3. 코드 설명
HttpClient httpClient("http://localhost:8080");
- 서버 주소(
localhost:8080
)를 설정하여 JSON-RPC 요청을 보낼 준비를 합니다. Client client(httpClient, jsonrpc::JSONRPC_CLIENT_V2);
- JSON-RPC 클라이언트 객체를 생성합니다.
Json::Value params;
- 요청을 위한 파라미터를 설정합니다. 여기서는
"playerId": 12345
를 요청합니다. Json::Value response = client.CallMethod("getPlayerInfo", params);
getPlayerInfo
메서드를 호출하고, 결과를 JSON 형식으로 반환받습니다.- 예외 처리 (
catch
) - 요청 처리 중 오류가 발생하면
JsonRpcException
을 통해 오류 메시지를 출력합니다.
4. JSON-RPC 클라이언트 실행 및 테스트
클라이언트를 실행하면 서버에 getPlayerInfo
요청을 보내고 응답을 출력합니다.
서버 응답 예제:
Player Name: PlayerOne
Level: 42
Score: 98765
테스트 시, 서버가 실행 중인지 확인하세요.
서버(C++ JSON-RPC 서버
)가 8080
포트에서 실행 중이어야 정상적으로 응답을 받을 수 있습니다.
5. 배치 요청 (Batch Request) 구현
JSON-RPC는 여러 요청을 하나의 JSON 배열로 묶어 전송하는 배치 요청(Batch Request) 기능을 지원합니다.
배치 요청을 통해 성능을 향상시키고 서버의 응답을 최적화할 수 있습니다.
try {
Json::Value batchRequests(Json::arrayValue);
Json::Value request1;
request1["jsonrpc"] = "2.0";
request1["method"] = "getPlayerInfo";
request1["params"]["playerId"] = 12345;
request1["id"] = 1;
batchRequests.append(request1);
Json::Value request2;
request2["jsonrpc"] = "2.0";
request2["method"] = "getLeaderboard";
request2["id"] = 2;
batchRequests.append(request2);
Json::Value response = client.CallMethod("batch", batchRequests);
cout << "Batch Response: " << response.toStyledString() << endl;
} catch (const JsonRpcException &e) {
cerr << "JSON-RPC 오류 발생: " << e.GetMessage() << endl;
}
6. 비동기 요청 처리
JSON-RPC는 비동기 요청을 지원하며, 이를 통해 응답을 기다리지 않고 다른 작업을 수행할 수 있습니다.
비동기 요청을 사용하면 성능이 향상되며, 특히 네트워크 응답 시간이 중요한 게임 서버에서는 필수적인 기능입니다.
비동기 요청을 구현하려면 멀티스레딩을 활용할 수 있습니다.
#include <thread>
void asyncRequest(HttpClient &httpClient) {
Client client(httpClient, jsonrpc::JSONRPC_CLIENT_V2);
Json::Value params;
params["playerId"] = 12345;
try {
Json::Value response = client.CallMethod("getPlayerInfo", params);
cout << "[Async] Player Name: " << response["name"].asString() << endl;
} catch (const JsonRpcException &e) {
cerr << "[Async] 오류 발생: " << e.GetMessage() << endl;
}
}
int main() {
HttpClient httpClient("http://localhost:8080");
// 별도 스레드에서 JSON-RPC 요청 실행
thread asyncThread(asyncRequest, ref(httpClient));
// 메인 스레드에서 다른 작업 수행
cout << "메인 스레드는 다른 작업을 수행 중..." << endl;
asyncThread.join(); // 비동기 스레드 종료 대기
return 0;
}
비동기 요청 코드 설명
std::thread
를 사용하여 별도 스레드에서 JSON-RPC 요청을 실행- 메인 스레드는 JSON-RPC 요청과 무관한 작업 수행 가능
- 비동기 요청을 처리한 후
join()
을 호출하여 스레드 종료 대기
7. 정리
✅ C++에서 JSON-RPC 클라이언트를 구현하는 방법을 배웠습니다.
✅ 기본적인 요청-응답 패턴을 구현하는 방법을 익혔습니다.
✅ 배치 요청(Batch Request) 및 비동기 요청(Async Request) 처리 방법을 학습했습니다.
이제 다음 섹션에서는 JSON-RPC 메시지 처리 및 예외 처리 기법을 살펴보겠습니다.
JSON-RPC 메시지 처리 및 예외 처리
JSON-RPC를 사용하면 클라이언트와 서버 간의 원격 프로시저 호출을 간편하게 수행할 수 있지만, 예상치 못한 오류가 발생할 수 있습니다. 본 섹션에서는 JSON-RPC 메시지의 처리 과정과 예외 처리 기법을 설명합니다.
1. JSON-RPC 요청 및 응답 구조
JSON-RPC 메시지는 표준화된 형식을 따릅니다. 클라이언트가 요청을 보내고, 서버는 요청을 처리한 후 응답을 반환합니다.
요청(Request) 예제:
{
"jsonrpc": "2.0",
"method": "getPlayerInfo",
"params": { "playerId": 12345 },
"id": 1
}
"jsonrpc": "2.0"
→ JSON-RPC 버전"method": "getPlayerInfo"
→ 호출할 서버 측 메서드"params"
→ 요청에 필요한 인자"id"
→ 요청과 응답을 매칭하기 위한 고유 ID
응답(Response) 예제:
{
"jsonrpc": "2.0",
"result": {
"name": "PlayerOne",
"level": 42,
"score": 98765
},
"id": 1
}
"result"
→ 요청한 메서드의 실행 결과
오류 응답(Error Response) 예제:
{
"jsonrpc": "2.0",
"error": {
"code": -32601,
"message": "Method not found"
},
"id": 1
}
"error"
→ 요청 처리 중 발생한 오류 정보
2. JSON-RPC 서버의 예외 처리 구현
C++ 서버에서 예외 처리를 구현하여 예상치 못한 상황에서도 안정적으로 응답할 수 있도록 해야 합니다.
예제: 요청된 메서드가 존재하지 않을 경우 오류 처리
void unknownMethod(const Json::Value &request, Json::Value &response) {
throw JsonRpcException(-32601, "Method not found");
}
서버가 처리할 수 없는 요청이 들어오면 위와 같은 예외를 발생시켜 JSON-RPC 표준 오류 응답을 반환할 수 있습니다.
C++ JSON-RPC 서버 예외 처리 코드
#include <jsonrpccpp/server/connectors/httpserver.h>
#include <jsonrpccpp/server.h>
#include <json/json.h>
#include <iostream>
using namespace jsonrpc;
using namespace std;
class GameServer : public AbstractServer<GameServer> {
public:
GameServer(HttpServer &server) : AbstractServer<GameServer>(server) {
this->bindAndAddMethod(Procedure("getPlayerInfo", PARAMS_BY_NAME, "playerId", Json::integerValue, NULL), &GameServer::getPlayerInfo);
}
void getPlayerInfo(const Json::Value &request, Json::Value &response) {
try {
if (!request.isMember("playerId")) {
throw JsonRpcException(-32602, "Invalid params: 'playerId' is required");
}
int playerId = request["playerId"].asInt();
if (playerId <= 0) {
throw JsonRpcException(-32000, "Invalid player ID");
}
// 가상의 플레이어 정보 반환
response["name"] = "PlayerOne";
response["level"] = 42;
response["score"] = 98765;
} catch (const JsonRpcException &e) {
throw e; // JSON-RPC 오류 응답 반환
} catch (...) {
throw JsonRpcException(-32603, "Internal server error");
}
}
};
int main() {
HttpServer server(8080);
GameServer gameServer(server);
cout << "JSON-RPC 서버 실행 중..." << endl;
gameServer.StartListening();
getchar();
gameServer.StopListening();
return 0;
}
3. JSON-RPC 클라이언트의 예외 처리
클라이언트에서 서버 요청 시 발생할 수 있는 예외를 처리해야 합니다.
C++ JSON-RPC 클라이언트 예외 처리 코드
#include <jsonrpccpp/client/connectors/httpclient.h>
#include <jsonrpccpp/client.h>
#include <json/json.h>
#include <iostream>
using namespace jsonrpc;
using namespace std;
int main() {
HttpClient httpClient("http://localhost:8080");
Client client(httpClient, jsonrpc::JSONRPC_CLIENT_V2);
try {
Json::Value params;
params["playerId"] = -1; // 잘못된 ID
Json::Value response = client.CallMethod("getPlayerInfo", params);
cout << "Player Name: " << response["name"].asString() << endl;
} catch (const JsonRpcException &e) {
cerr << "JSON-RPC 오류 발생: " << e.GetMessage() << endl;
} catch (const exception &e) {
cerr << "일반 예외 발생: " << e.what() << endl;
} catch (...) {
cerr << "알 수 없는 오류 발생" << endl;
}
return 0;
}
테스트 결과
잘못된 playerId
(-1)를 입력했을 경우:
JSON-RPC 오류 발생: Invalid player ID
4. JSON-RPC 오류 코드 및 의미
JSON-RPC 표준 오류 코드는 다음과 같습니다.
오류 코드 | 설명 |
---|---|
-32700 | 잘못된 JSON 형식 |
-32600 | 잘못된 요청 형식 |
-32601 | 존재하지 않는 메서드 호출 |
-32602 | 유효하지 않은 매개변수 |
-32603 | 내부 서버 오류 |
-32000~32099 | 사용자 정의 서버 오류 |
예제: 요청 형식 오류
{
"jsonrpc": "2.0",
"error": {
"code": -32600,
"message": "Invalid Request"
},
"id": null
}
5. JSON-RPC의 예외 처리 정리
✅ 서버에서 요청을 검증하고, 잘못된 요청에는 오류 응답을 반환해야 합니다.
✅ 클라이언트에서도 JSON-RPC 오류를 적절히 처리하여 프로그램이 예기치 않게 종료되지 않도록 해야 합니다.
✅ JSON-RPC 표준 오류 코드를 활용하면 보다 직관적인 오류 처리가 가능합니다.
다음 섹션에서는 비동기 JSON-RPC 서버 구현에 대해 살펴보겠습니다.
비동기 JSON-RPC 서버 구현
JSON-RPC 서버는 기본적으로 동기 방식으로 요청을 처리하지만, 멀티스레딩과 비동기 처리를 적용하면 높은 부하에서도 성능을 유지할 수 있습니다. 본 섹션에서는 C++ 기반의 비동기 JSON-RPC 서버를 구현하는 방법을 설명합니다.
1. 비동기 JSON-RPC 서버가 필요한 이유
기본적인 JSON-RPC 서버는 요청을 순차적으로 처리하는 동기(Synchronous) 방식입니다. 하지만 다수의 클라이언트가 동시에 요청을 보낼 경우, 서버의 응답 지연이 발생할 수 있습니다.
비동기(Asynchronous) JSON-RPC 서버를 구현하면 다음과 같은 장점이 있습니다.
✅ 다중 클라이언트 요청을 병렬로 처리 가능
✅ 서버 응답 시간이 단축되어 높은 성능 유지
✅ 긴 실행 시간이 필요한 작업(예: 데이터베이스 조회)도 대기 없이 처리 가능
비동기 서버는 주로 멀티스레딩 또는 이벤트 루프를 활용하여 구현됩니다.
2. C++에서 멀티스레딩을 활용한 비동기 JSON-RPC 서버
비동기 처리를 위해 std::thread 및 std::async를 활용할 수 있습니다.
아래 예제에서는 JSON-RPC 요청을 별도의 스레드에서 처리하는 비동기 서버를 구현합니다.
#include <jsonrpccpp/server/connectors/httpserver.h>
#include <jsonrpccpp/server.h>
#include <json/json.h>
#include <iostream>
#include <thread>
#include <future>
using namespace jsonrpc;
using namespace std;
class AsyncGameServer : public AbstractServer<AsyncGameServer> {
public:
AsyncGameServer(HttpServer &server) : AbstractServer<AsyncGameServer>(server) {
this->bindAndAddMethod(Procedure("getPlayerInfo", PARAMS_BY_NAME, "playerId", Json::integerValue, NULL),
&AsyncGameServer::getPlayerInfo);
}
void getPlayerInfo(const Json::Value &request, Json::Value &response) {
int playerId = request["playerId"].asInt();
cout << "Received request for Player ID: " << playerId << endl;
// 비동기 처리를 위해 std::async 사용
auto futureResult = async(launch::async, [playerId]() {
Json::Value result;
this_thread::sleep_for(chrono::seconds(2)); // 2초 동안 지연(데이터베이스 조회 시뮬레이션)
result["name"] = "PlayerOne";
result["level"] = 42;
result["score"] = 98765;
return result;
});
response = futureResult.get(); // 비동기 결과를 받아 응답
}
};
int main() {
HttpServer server(8080);
AsyncGameServer gameServer(server);
cout << "비동기 JSON-RPC 서버 실행 중..." << endl;
gameServer.StartListening();
getchar(); // 엔터 키 입력 시 종료
gameServer.StopListening();
return 0;
}
3. 코드 설명
std::async
를 사용하여getPlayerInfo
메서드를 비동기 실행this_thread::sleep_for(chrono::seconds(2));
- 실제 환경에서는 데이터베이스 조회나 네트워크 요청과 같은 작업이 포함될 수 있음
- 비동기적으로 데이터를 처리하고,
futureResult.get()
을 사용하여 최종 응답 반환
비동기 JSON-RPC 서버 실행 후 클라이언트 요청 테스트:
curl -X POST http://localhost:8080 -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0",
"method": "getPlayerInfo",
"params": { "playerId": 12345 },
"id": 1
}'
결과:
서버가 2초 동안 데이터를 처리한 후 응답을 반환합니다.
{
"jsonrpc": "2.0",
"result": {
"name": "PlayerOne",
"level": 42,
"score": 98765
},
"id": 1
}
4. 멀티스레드(Thread Pool)를 활용한 JSON-RPC 서버
대량의 요청을 효율적으로 처리하기 위해 스레드 풀(Thread Pool)을 활용할 수도 있습니다.
Boost.Asio를 활용한 멀티스레드 JSON-RPC 서버 예제:
#include <boost/asio.hpp>
#include <jsonrpccpp/server/connectors/httpserver.h>
#include <jsonrpccpp/server.h>
#include <json/json.h>
#include <iostream>
#include <thread>
using namespace jsonrpc;
using namespace std;
using namespace boost::asio;
class ThreadPoolGameServer : public AbstractServer<ThreadPoolGameServer> {
public:
ThreadPoolGameServer(HttpServer &server, io_context &ioc)
: AbstractServer<ThreadPoolGameServer>(server), ioc_(ioc) {
this->bindAndAddMethod(Procedure("getPlayerInfo", PARAMS_BY_NAME, "playerId", Json::integerValue, NULL),
&ThreadPoolGameServer::getPlayerInfo);
}
void getPlayerInfo(const Json::Value &request, Json::Value &response) {
int playerId = request["playerId"].asInt();
cout << "Processing request for Player ID: " << playerId << endl;
// 스레드 풀에서 비동기 처리
post(ioc_, [this, playerId, &response]() {
this_thread::sleep_for(chrono::seconds(2)); // 데이터 조회 시뮬레이션
response["name"] = "PlayerOne";
response["level"] = 42;
response["score"] = 98765;
});
}
private:
io_context &ioc_;
};
int main() {
HttpServer server(8080);
io_context ioc;
ThreadPoolGameServer gameServer(server, ioc);
// 스레드 풀 생성 (4개 스레드 사용)
vector<thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back([&ioc]() { ioc.run(); });
}
cout << "멀티스레드 JSON-RPC 서버 실행 중..." << endl;
gameServer.StartListening();
getchar(); // 엔터 키 입력 시 종료
gameServer.StopListening();
ioc.stop();
for (auto &t : threads) {
t.join();
}
return 0;
}
5. 비동기 JSON-RPC 서버의 성능 최적화
비동기 JSON-RPC 서버의 성능을 높이려면 다음과 같은 최적화 기법을 적용할 수 있습니다.
✅ 스레드 풀(Thread Pool) 활용
std::thread
를 직접 사용하기보다는,boost::asio::io_context
같은 비동기 프레임워크 활용
✅ 비동기 I/O (Asynchronous I/O) 적용
- 네트워크 요청, 데이터베이스 조회 등을
std::async
또는std::future
를 활용하여 비동기 실행
✅ 이벤트 기반(Event-driven) 아키텍처 적용
- JSON-RPC 요청을 큐에 추가하고, 이벤트 루프에서 처리
✅ Batch Request(배치 요청) 활용
- 여러 개의 JSON-RPC 요청을 하나의 JSON 배열로 묶어 보내 성능 향상
6. 정리
✅ C++에서 비동기 JSON-RPC 서버를 구현하는 방법을 배웠습니다.
✅ std::async
, std::thread
, boost::asio
등을 활용하여 성능을 최적화할 수 있습니다.
✅ 비동기 처리를 통해 다중 클라이언트 요청을 병렬로 처리하여 서버 응답 속도를 개선할 수 있습니다.
다음 섹션에서는 JSON-RPC 보안 고려 사항을 살펴보겠습니다.
JSON-RPC 보안 고려 사항
JSON-RPC는 원격 프로시저 호출(RPC) 방식으로 클라이언트와 서버 간 데이터를 주고받기 때문에 보안 취약점에 노출될 가능성이 있습니다. 특히, C++ 기반의 게임 서버에서 JSON-RPC를 활용할 경우 인증, 데이터 암호화, 무결성 검증 등을 고려해야 합니다.
본 섹션에서는 JSON-RPC에서 발생할 수 있는 주요 보안 위협과 이를 방지하기 위한 보안 기법을 설명합니다.
1. JSON-RPC 보안 위협
JSON-RPC를 사용하면서 발생할 수 있는 보안 위협은 다음과 같습니다.
보안 위협 | 설명 |
---|---|
인증 우회 | 클라이언트가 서버에 허가되지 않은 요청을 보낼 수 있음 |
데이터 변조 | 중간 공격자가 JSON 메시지를 변조하여 서버에 전달 가능 |
재전송 공격 (Replay Attack) | 이전에 유효했던 JSON-RPC 요청을 다시 보낼 수 있음 |
DoS 공격 (Denial of Service) | 대량의 JSON-RPC 요청을 보내 서버를 마비시킬 수 있음 |
데이터 유출 | JSON-RPC 요청 및 응답이 암호화되지 않으면 중요 데이터가 노출될 수 있음 |
이러한 보안 위협을 방지하기 위해 JSON-RPC 통신에 보안 계층을 추가해야 합니다.
2. 인증 및 권한 관리
서버는 JSON-RPC 요청을 처리하기 전에 클라이언트가 신뢰할 수 있는 사용자인지 확인해야 합니다.
이를 위해 API 키, JWT(JSON Web Token), OAuth 2.0 등의 인증 방법을 적용할 수 있습니다.
1) API 키 인증
API 키를 JSON-RPC 요청의 params
또는 HTTP 헤더에 포함시켜 서버에서 이를 검증하는 방식입니다.
클라이언트 요청 예제:
{
"jsonrpc": "2.0",
"method": "getPlayerInfo",
"params": {
"playerId": 12345,
"apiKey": "secret_api_key"
},
"id": 1
}
서버에서 API 키 검증 코드 (C++ json-rpc-cpp):
void getPlayerInfo(const Json::Value &request, Json::Value &response) {
if (!request.isMember("apiKey") || request["apiKey"].asString() != "secret_api_key") {
throw JsonRpcException(-32001, "Invalid API Key");
}
int playerId = request["playerId"].asInt();
response["name"] = "PlayerOne";
response["level"] = 42;
response["score"] = 98765;
}
✅ 장점: 간단하고 빠른 인증 방식
❌ 단점: API 키가 노출되면 악용될 위험이 있음 → HTTPS 및 제한된 키 사용 권장
2) JWT(JSON Web Token) 인증
JWT는 API 키보다 보안성이 높은 방식으로, 사용자의 로그인 정보를 암호화하여 인증할 수 있습니다.
- 클라이언트가 로그인하면 서버에서 JWT 토큰을 생성하여 반환
- 이후 모든 JSON-RPC 요청에서
Authorization
헤더에 JWT 포함 - 서버는 요청을 처리하기 전에 JWT의 유효성을 검증
JWT 인증 요청 예제:
{
"jsonrpc": "2.0",
"method": "getPlayerInfo",
"params": { "playerId": 12345 },
"id": 1
}
HTTP 요청 헤더:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
✅ 장점: 토큰 기반 인증 방식으로 보안성이 뛰어남
❌ 단점: 서버에서 토큰을 검증해야 하므로 약간의 오버헤드 발생
3. 데이터 암호화 및 무결성 보장
JSON-RPC는 기본적으로 평문(Plaintext) 전송이므로 데이터가 노출될 위험이 있습니다. 이를 방지하기 위해 TLS/SSL을 적용하여 JSON-RPC 통신을 암호화해야 합니다.
1) HTTPS 적용
기본적으로 JSON-RPC는 HTTP를 사용하지만, 보안 강화를 위해 HTTPS를 활성화해야 합니다.
C++ json-rpc-cpp 서버에서 HTTPS 사용 설정:
#include <jsonrpccpp/server/connectors/httpsserver.h>
HttpsServer httpsServer(8443, "server-cert.pem", "server-key.pem");
GameServer gameServer(httpsServer);
gameServer.StartListening();
✅ 장점: 통신 데이터 암호화 가능
❌ 단점: HTTPS 적용을 위한 SSL/TLS 인증서 필요
2) 요청 무결성 검증 (HMAC)
HMAC(Hash-based Message Authentication Code)를 사용하면 JSON-RPC 메시지가 변조되지 않았는지 검증할 수 있습니다.
예제: 요청에 HMAC 해시 추가
{
"jsonrpc": "2.0",
"method": "getPlayerInfo",
"params": {
"playerId": 12345,
"hmac": "f2d6c69a36d1dfc24e60e90a348d78b2"
},
"id": 1
}
서버에서 요청을 처리하기 전에 HMAC 해시 값을 검증하여 데이터 무결성을 확인합니다.
✅ 장점: 메시지가 변조되지 않았는지 검증 가능
❌ 단점: 서버와 클라이언트가 동일한 HMAC 키를 공유해야 함
4. JSON-RPC 요청 제한 (Rate Limiting)
DoS(Denial of Service) 공격을 방지하기 위해 요청 속도 제한(Rate Limiting)을 적용해야 합니다.
- IP별 요청 제한: 동일 IP에서 일정 시간 내 과도한 요청이 발생하면 차단
- 사용자별 요청 제한: 클라이언트 API 키 또는 JWT 기반으로 초당 요청 수 제한
C++에서 IP별 요청 제한 구현 예제:
#include <map>
#include <chrono>
map<string, int> requestCount;
const int MAX_REQUESTS_PER_MIN = 100;
bool isRateLimited(string ip) {
auto now = chrono::system_clock::now();
requestCount[ip]++;
if (requestCount[ip] > MAX_REQUESTS_PER_MIN) {
return true;
}
return false;
}
✅ 장점: 서버 부하 방지 및 DoS 공격 차단 가능
❌ 단점: 공격자가 여러 IP를 사용할 경우 완전한 방어는 어려움
5. 정리
✅ JSON-RPC에서 API 키 또는 JWT를 사용하여 인증을 강화해야 합니다.
✅ HTTPS(TLS/SSL) 적용을 통해 데이터 암호화를 수행해야 합니다.
✅ HMAC을 활용하여 데이터 변조를 방지하고 무결성을 보장할 수 있습니다.
✅ Rate Limiting(속도 제한) 기법을 사용하여 DoS 공격을 차단해야 합니다.
이제 다음 섹션에서는 JSON-RPC 최적화 및 성능 튜닝을 살펴보겠습니다.
JSON-RPC 최적화 및 성능 튜닝
JSON-RPC를 활용한 C++ 게임 서버는 고성능과 낮은 지연시간을 요구합니다.
서버의 성능을 극대화하고 최적화하는 방법을 설명합니다.
1. JSON-RPC 요청 처리 성능 최적화
게임 서버에서 JSON-RPC 요청을 빠르게 처리하려면 비동기 I/O, 캐싱, 배치 요청을 활용해야 합니다.
1) 비동기 요청 처리 (Asynchronous Handling)
기본적으로 JSON-RPC 서버는 요청을 동기적으로 처리하지만,
비동기 방식으로 전환하면 높은 동시성을 지원할 수 있습니다.
C++에서 std::async
를 활용한 비동기 처리 예제:
#include <future>
void getPlayerInfoAsync(const Json::Value &request, Json::Value &response) {
auto futureResult = std::async(std::launch::async, [&]() {
Json::Value result;
this_thread::sleep_for(chrono::milliseconds(50)); // 데이터 조회 시뮬레이션
result["name"] = "PlayerOne";
result["level"] = 42;
result["score"] = 98765;
return result;
});
response = futureResult.get();
}
✅ 장점: 클라이언트 요청을 병렬로 처리하여 성능 향상
❌ 단점: 비동기 처리 시 동기화 이슈 발생 가능
2) 배치 요청 (Batch Request) 활용
JSON-RPC는 배치 요청(Batch Request)을 지원하며,
여러 개의 요청을 하나의 JSON 배열로 묶어 전송할 수 있습니다.
클라이언트 요청 예제 (Batch Request)
[
{
"jsonrpc": "2.0",
"method": "getPlayerInfo",
"params": { "playerId": 12345 },
"id": 1
},
{
"jsonrpc": "2.0",
"method": "getLeaderboard",
"id": 2
}
]
✅ 장점: 요청 수를 줄여 네트워크 부하 감소
❌ 단점: 일부 요청이 실패할 경우 에러 핸들링 필요
2. JSON-RPC 서버 성능 향상 기법
1) 멀티스레딩 (Multi-threading)
단일 스레드 JSON-RPC 서버는 동시 요청 처리에 한계가 있습니다.
멀티스레드를 사용하면 성능을 개선할 수 있습니다.
C++에서 std::thread
를 활용한 멀티스레딩 예제:
#include <thread>
void handleRequest(int playerId) {
cout << "Processing request for Player ID: " << playerId << endl;
this_thread::sleep_for(chrono::milliseconds(100)); // 데이터 조회 시뮬레이션
}
int main() {
vector<thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back(handleRequest, 12345 + i);
}
for (auto &t : threads) {
t.join();
}
return 0;
}
✅ 장점: 다중 클라이언트 요청을 병렬로 처리 가능
❌ 단점: 동기화 문제 발생 가능
2) 네트워크 I/O 최적화
JSON-RPC 서버에서 네트워크 병목을 해결하기 위해 비동기 네트워크 처리가 필요합니다.
✅ 방법 1: Boost.Asio 비동기 네트워크 사용
✅ 방법 2: WebSocket을 활용하여 지속적인 연결 유지
Boost.Asio를 활용한 비동기 서버 예제
#include <boost/asio.hpp>
using namespace boost::asio;
io_service service;
ip::tcp::socket socket(service);
✅ 장점: 네트워크 지연을 최소화
❌ 단점: 추가 라이브러리 필요
3. 데이터 처리 최적화
1) JSON 파싱 속도 향상
기본적인 JSON 라이브러리는 느릴 수 있습니다.
빠른 JSON 처리를 위해 nlohmann/json 또는 RapidJSON을 사용할 수 있습니다.
nlohmann/json 활용 예제:
#include <nlohmann/json.hpp>
using json = nlohmann::json;
json j = {{"playerId", 12345}, {"level", 42}};
cout << j.dump() << endl;
✅ 장점: 속도 최적화된 JSON 처리 가능
❌ 단점: 추가 라이브러리 필요
2) 데이터 캐싱 적용
JSON-RPC 요청을 처리할 때 매번 데이터베이스를 조회하면 성능이 저하됩니다.
자주 사용하는 데이터를 캐싱하면 성능이 향상됩니다.
C++에서 unordered_map
을 활용한 캐싱 예제
#include <unordered_map>
unordered_map<int, Json::Value> playerCache;
Json::Value getCachedPlayerInfo(int playerId) {
if (playerCache.find(playerId) != playerCache.end()) {
return playerCache[playerId];
}
// DB 조회 후 캐싱
Json::Value player;
player["name"] = "PlayerOne";
player["level"] = 42;
player["score"] = 98765;
playerCache[playerId] = player;
return player;
}
✅ 장점: 데이터베이스 조회 부담 감소
❌ 단점: 캐시 만료 정책 필요
4. JSON-RPC 로깅 및 모니터링
서버의 성능을 실시간으로 모니터링하면 오류 감지 및 성능 개선이 가능합니다.
✅ 로그 저장 (Logging)
- 요청 및 응답을 로그 파일에 기록
spdlog
또는Boost.Log
라이브러리 활용
spdlog를 활용한 JSON-RPC 로깅 예제
#include <spdlog/spdlog.h>
spdlog::info("Received JSON-RPC request: {}", request.toStyledString());
✅ 성능 모니터링 (Monitoring)
Prometheus
또는Grafana
를 활용하여 성능 시각화- CPU 사용량, 응답 시간, 요청 수 등을 추적
✅ 오류 감지 및 알림
- 특정 오류 발생 시 Slack 또는 Email 알림 설정
5. JSON-RPC 성능 최적화 요약
최적화 기법 | 설명 |
---|---|
비동기 요청 처리 | std::async 사용하여 요청을 비동기 실행 |
배치 요청 활용 | 여러 요청을 한 번에 처리하여 성능 개선 |
멀티스레딩 적용 | std::thread 또는 Boost.Asio 사용 |
빠른 JSON 라이브러리 | nlohmann/json 또는 RapidJSON 활용 |
데이터 캐싱 | 자주 사용되는 데이터를 unordered_map 에 저장 |
네트워크 I/O 최적화 | Boost.Asio 및 WebSocket 적용 |
로깅 및 모니터링 | spdlog 및 Prometheus 로 실시간 성능 분석 |
✅ 이러한 최적화 기법을 적용하면 JSON-RPC 서버의 성능을 대폭 향상시킬 수 있습니다.
다음 섹션에서는 JSON-RPC의 전체 요약을 다루겠습니다.
요약
본 기사에서는 C++ 게임 서버에서 JSON-RPC를 활용한 클라이언트-서버 통신 방법을 설명했습니다. JSON-RPC는 경량 원격 프로시저 호출(RPC) 프로토콜로, 간결한 데이터 구조와 효율적인 요청-응답 처리를 제공합니다.
주요 내용 정리
- JSON-RPC 개요 및 특징
- JSON 형식 기반의 경량 RPC 프로토콜
- 동기 및 비동기 요청, 배치 요청(Batch Request) 지원
- 클라이언트-서버 통신 모델
- 클라이언트는 JSON 형식으로 요청을 전송하고, 서버는 해당 요청을 처리한 후 JSON 형식의 응답을 반환
- 비동기 요청 및 배치 요청을 활용하여 성능 최적화 가능
- C++에서 JSON-RPC 서버 및 클라이언트 구현
json-rpc-cpp
라이브러리를 활용하여 C++ JSON-RPC 서버 구축- HTTP 또는 WebSocket을 통한 클라이언트-서버 통신 구현
- JSON-RPC 메시지 처리 및 예외 처리
- 요청 메시지 검증, 예외 처리, 표준 오류 코드(-32600, -32601 등) 활용
- 비동기 JSON-RPC 서버 구현
std::async
,Boost.Asio
등을 활용하여 멀티스레딩 및 비동기 처리 구현- 네트워크 I/O 최적화를 통해 대량 요청을 효과적으로 처리
- JSON-RPC 보안 고려 사항
- API 키 또는 JWT(JSON Web Token)를 활용한 인증 방식 적용
- HTTPS(TLS/SSL) 암호화 및 데이터 무결성 검증(HMAC) 적용
- 요청 속도 제한(Rate Limiting) 적용하여 DoS 공격 방어
- JSON-RPC 최적화 및 성능 튜닝
- 비동기 요청 처리, 배치 요청 활용, 네트워크 I/O 최적화
- 데이터 캐싱 적용(
unordered_map
), 빠른 JSON 파싱(nlohmann/json
) - 성능 모니터링 및 로깅(
spdlog
,Prometheus
) 활용
결론
JSON-RPC는 C++ 기반 게임 서버에서 가볍고 효율적인 클라이언트-서버 통신을 가능하게 합니다.
최적화 및 보안 기법을 적절히 적용하면 고성능 및 안정적인 JSON-RPC 서버를 구축할 수 있습니다.
✅ C++로 JSON-RPC 서버를 구현하려는 개발자는 본 기사의 가이드를 참고하여, 성능과 보안을 고려한 최적화된 서버를 구축할 수 있습니다.