서론
현대 웹 애플리케이션에서 WebSocket은 클라이언트와 서버 간의 양방향 통신을 가능하게 하는 중요한 프로토콜입니다. C++ 프로젝트에서 WebSocket 기능을 구현할 때, WebSocket++ 라이브러리는 가장 널리 사용되는 솔루션 중 하나입니다. 그러나 이 라이브러리를 프로젝트에 통합하는 과정은 종종 예상보다 복잡할 수 있습니다. 이 글에서는 WebSocket++ 라이브러리를 C++ 프로젝트에 성공적으로 통합하는 방법과 일반적인 문제 해결 방법을 자세히 살펴보겠습니다.
WebSocket++ 소개
WebSocket++는 C++로 작성된 헤더 전용(header-only) 라이브러리로, RFC6455 WebSocket 프로토콜 구현을 제공합니다. 이 라이브러리는 다음과 같은 특징을 가지고 있습니다:
- 헤더 전용 구현으로 별도의 컴파일이 필요 없음
- 유연한 네트워킹 계층(Boost.Asio, ASIO standalone 등 지원)
- 비동기 및 동기 통신 모두 지원
- TLS/SSL 보안 연결 지원
- 다양한 플랫폼 지원(Windows, Linux, macOS)
프로젝트에서 발생한 문제
PicoTorrent 프로젝트를 빌드하는 과정에서 다음과 같은 에러가 발생했습니다:
c:\code\picotorrent-develop\picotorrent-develop\plugins\websocket\src\TorrentEventSink.hpp(10): fatal error C1083: 포함 파일을 열 수 없습니다. 'websocketpp/config/asio_no_tls.hpp': No such file or directory
이 오류는 WebSocket++ 라이브러리가 시스템에 설치되어 있지 않거나, 프로젝트의 include 경로에 추가되지 않았기 때문에 발생했습니다.
올바른 WebSocket++ 소스 찾기
처음에는 잘못된 저장소를 찾아 문제가 해결되지 않았습니다:
silicon-websocketpp → X (잘못된 소스)
올바른 WebSocket++ 저장소는 다음과 같습니다:
https://www.zaphoyd.com/websocketpp → O (올바른 소스)
또는 GitHub 저장소를 통해 접근할 수도 있습니다:
https://github.com/zaphoyd/websocketpp
WebSocket++ 통합 단계별 가이드
1. WebSocket++ 라이브러리 다운로드
WebSocket++ 라이브러리를 다운로드하는 방법은 여러 가지가 있습니다:
방법 1: 직접 다운로드
# Git을 사용한 클론
git clone https://github.com/zaphoyd/websocketpp.git
# 또는 ZIP 파일로 다운로드
# https://github.com/zaphoyd/websocketpp/archive/refs/heads/master.zip
방법 2: 패키지 관리자 사용
vcpkg:
vcpkg install websocketpp
Conan:
conan install websocketpp/0.8.2@
2. 프로젝트에 WebSocket++ 통합
Visual Studio 프로젝트에 통합
- 프로젝트 속성으로 이동: 프로젝트 마우스 오른쪽 클릭 → 속성
- C/C++ → 일반 → 추가 포함 디렉터리에 WebSocket++ 헤더 경로 추가
$(ProjectDir)\..\external\websocketpp;$(AdditionalIncludeDirectories)
CMake 프로젝트에 통합
CMakeLists.txt 파일에 다음과 같이 추가:
# WebSocket++ 설정
include_directories(${PROJECT_SOURCE_DIR}/external/websocketpp)
# Boost가 필요한 경우(WebSocket++의 일부 기능에 필요)
find_package(Boost REQUIRED COMPONENTS system thread)
include_directories(${Boost_INCLUDE_DIRS})
3. 종속성 처리
WebSocket++는 ASIO 라이브러리에 의존합니다. ASIO는 두 가지 방식으로 사용할 수 있습니다:
- Boost.ASIO: Boost의 일부로 제공되는 ASIO
- Standalone ASIO: Boost 없이 독립적으로 사용할 수 있는 ASIO
Boost.ASIO 설정
# Boost ASIO 사용 시
find_package(Boost REQUIRED COMPONENTS system thread)
include_directories(${Boost_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES})
Standalone ASIO 설정
# Standalone ASIO 사용 시
include_directories(${PROJECT_SOURCE_DIR}/external/asio/include)
add_definitions(-DASIO_STANDALONE)
4. TLS/SSL 지원 설정
WebSocket++에서 TLS/SSL 암호화를 사용하려면 OpenSSL 라이브러리가 필요합니다:
# OpenSSL 찾기
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} ${OPENSSL_LIBRARIES})
일반적인 문제 해결
문제 1: 헤더 파일을 찾을 수 없음
fatal error C1083: 포함 파일을 열 수 없습니다. 'websocketpp/config/asio_no_tls.hpp'
해결책:
- include 경로에 WebSocket++ 루트 디렉토리가 포함되어 있는지 확인
- 필요한 경우 상대 경로 대신 절대 경로 사용
- 다운로드한 WebSocket++ 버전이 최신인지 확인
문제 2: ASIO 관련 오류
error: 'asio' has not been declared
해결책:
- ASIO가 올바르게 설치되고 include 경로에 추가되었는지 확인
- Boost.ASIO와 Standalone ASIO 중 하나만 사용해야 함
- Standalone ASIO 사용 시
-DASIO_STANDALONE
정의 추가
문제 3: Boost 관련 오류
error: 'boost' has not been declared
해결책:
- Boost 라이브러리가 올바르게 설치되었는지 확인
- Boost include 경로가 프로젝트에 추가되었는지 확인
- Boost 버전이 WebSocket++와 호환되는지 확인(최소 1.54.0 이상 권장)
문제 4: 빌드 오류
error C2039: 'function': is not a member of 'std'
해결책:
- C++11 이상의 표준을 사용하고 있는지 확인
- Visual Studio에서: 프로젝트 속성 → C/C++ → 언어 → C++ 언어 표준을 “ISO C++11 표준(/std:c++11)” 이상으로 설정
- CMake에서:
set(CMAKE_CXX_STANDARD 11)
추가
WebSocket++ 기본 사용 예제
서버 구현 예제
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <iostream>
#include <functional>
typedef websocketpp::server<websocketpp::config::asio> WebSocketServer;
typedef websocketpp::connection_hdl ConnectionHandle;
class WebSocketHandler {
public:
WebSocketHandler() {
// 서버 초기화
server.init_asio();
// 이벤트 핸들러 설정
server.set_open_handler(std::bind(&WebSocketHandler::onOpen, this, std::placeholders::_1));
server.set_close_handler(std::bind(&WebSocketHandler::onClose, this, std::placeholders::_1));
server.set_message_handler(std::bind(&WebSocketHandler::onMessage, this,
std::placeholders::_1, std::placeholders::_2));
}
void run(uint16_t port) {
// 포트 설정 및 서버 시작
server.listen(port);
server.start_accept();
server.run();
}
private:
WebSocketServer server;
void onOpen(ConnectionHandle hdl) {
std::cout << "Connection opened" << std::endl;
}
void onClose(ConnectionHandle hdl) {
std::cout << "Connection closed" << std::endl;
}
void onMessage(ConnectionHandle hdl, WebSocketServer::message_ptr msg) {
std::cout << "Message received: " << msg->get_payload() << std::endl;
// 에코 응답
server.send(hdl, msg->get_payload(), msg->get_opcode());
}
};
int main() {
try {
WebSocketHandler handler;
handler.run(9002);
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
클라이언트 구현 예제
#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/client.hpp>
#include <iostream>
#include <functional>
#include <string>
typedef websocketpp::client<websocketpp::config::asio_client> WebSocketClient;
typedef websocketpp::connection_hdl ConnectionHandle;
class WebSocketClientHandler {
public:
WebSocketClientHandler() {
// 클라이언트 초기화
client.init_asio();
// 이벤트 핸들러 설정
client.set_open_handler(std::bind(&WebSocketClientHandler::onOpen, this, std::placeholders::_1));
client.set_close_handler(std::bind(&WebSocketClientHandler::onClose, this, std::placeholders::_1));
client.set_message_handler(std::bind(&WebSocketClientHandler::onMessage, this,
std::placeholders::_1, std::placeholders::_2));
}
void connect(const std::string& uri) {
websocketpp::lib::error_code ec;
WebSocketClient::connection_ptr con = client.get_connection(uri, ec);
if (ec) {
std::cerr << "Connection error: " << ec.message() << std::endl;
return;
}
connection = con->get_handle();
client.connect(con);
}
void run() {
client.run();
}
void send(const std::string& message) {
websocketpp::lib::error_code ec;
client.send(connection, message, websocketpp::frame::opcode::text, ec);
if (ec) {
std::cerr << "Send error: " << ec.message() << std::endl;
}
}
private:
WebSocketClient client;
ConnectionHandle connection;
void onOpen(ConnectionHandle hdl) {
std::cout << "Connection opened" << std::endl;
}
void onClose(ConnectionHandle hdl) {
std::cout << "Connection closed" << std::endl;
}
void onMessage(ConnectionHandle hdl, WebSocketClient::message_ptr msg) {
std::cout << "Message received: " << msg->get_payload() << std::endl;
}
};
int main() {
try {
WebSocketClientHandler handler;
handler.connect("ws://localhost:9002");
std::thread t([&handler]() { handler.run(); });
std::string input;
while (true) {
std::getline(std::cin, input);
if (input == "exit") break;
handler.send(input);
}
t.join();
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
고급 사용법
TLS/SSL 보안 연결 설정
보안 WebSocket 연결(wss://)을 위해 TLS/SSL을 설정하는 방법:
#include <websocketpp/config/asio.hpp> // TLS 지원 config 사용
#include <websocketpp/server.hpp>
#include <websocketpp/common/thread.hpp>
typedef websocketpp::server<websocketpp::config::asio_tls> SecureWebSocketServer;
// SSL 컨텍스트 설정
websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context> on_tls_init() {
// SSL 컨텍스트 생성
auto ctx = websocketpp::lib::make_shared<websocketpp::lib::asio::ssl::context>(
websocketpp::lib::asio::ssl::context::sslv23);
try {
// 옵션 설정
ctx->set_options(websocketpp::lib::asio::ssl::context::default_workarounds |
websocketpp::lib::asio::ssl::context::no_sslv2 |
websocketpp::lib::asio::ssl::context::no_sslv3 |
websocketpp::lib::asio::ssl::context::single_dh_use);
// 인증서 로드
ctx->use_certificate_chain_file("server.pem");
ctx->use_private_key_file("server.key", websocketpp::lib::asio::ssl::context::pem);
// DH 파라미터 설정(선택 사항)
ctx->use_tmp_dh_file("dh.pem");
} catch (std::exception& e) {
std::cerr << "TLS initialization error: " << e.what() << std::endl;
}
return ctx;
}
멀티스레딩 지원
WebSocket++ 서버를 여러 스레드에서 실행하는 방법:
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <thread>
#include <vector>
typedef websocketpp::server<websocketpp::config::asio> WebSocketServer;
WebSocketServer server;
std::vector<std::thread> threads;
void run_server() {
server.run();
}
int main() {
try {
// 서버 초기화 및 핸들러 설정
server.init_asio();
// ... 핸들러 설정 ...
// 리스닝 시작
server.listen(9002);
server.start_accept();
// 여러 스레드에서 서버 실행
unsigned int thread_count = std::thread::hardware_concurrency();
if (thread_count == 0) thread_count = 4; // 기본값 설정
for (size_t i = 0; i < thread_count; ++i) {
threads.push_back(std::thread(run_server));
}
// 모든 스레드 조인
for (auto& t : threads) {
t.join();
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
성능 최적화 팁
WebSocket++ 애플리케이션의 성능을 최적화하기 위한 몇 가지 팁:
- 메시지 버퍼링: 작은 메시지를 여러 개 보내는 대신 가능한 경우 큰 메시지로 배치 처리합니다.
- connection_hdl 캐싱: 자주 사용하는 연결 핸들을 캐시하여 조회 오버헤드를 줄입니다.
- 로깅 레벨 최적화: 프로덕션 환경에서는 로깅 레벨을 낮추어 성능 오버헤드를 줄입니다:
server.clear_access_channels(websocketpp::log::alevel::all); server.set_access_channels(websocketpp::log::alevel::error);
- 재사용 가능한 메시지 버퍼: 메시지 객체를 재사용하여 할당/해제 오버헤드를 줄입니다.
- 적절한 스레드 수: 하드웨어 환경에 맞게 스레드 수를 조정합니다.
결론
WebSocket++ 라이브러리는 C++ 애플리케이션에서 WebSocket 프로토콜을 구현하기 위한 강력하고 유연한 도구입니다. 그러나 초기 설정과 종속성 관리에 어려움이 있을 수 있습니다. 이 글에서 제시한 단계와 문제 해결 방법을 따르면, 대부분의 경우 WebSocket++ 라이브러리를 성공적으로 프로젝트에 통합할 수 있을 것입니다.
올바른 소스를 찾고, 필요한 종속성을 설치하며, 프로젝트의 빌드 설정을 적절히 구성하는 것이 중요합니다. 일단 라이브러리가 올바르게 설정되면, WebSocket++ API를 사용하여 확장 가능하고 효율적인 실시간 통신 시스템을 구축할 수 있습니다.
WebSocket++는 PicoTorrent와 같은 실제 프로젝트에서 성공적으로 사용되고 있으며, 이 가이드가 당신의 프로젝트에서도 WebSocket++ 통합을 더 쉽게 만들어 주기를 바랍니다.
답글 남기기