WebSocket 의존성 설정하기: WebSocketPP 라이브러리 통합 가이드

서론

현대 웹 애플리케이션에서 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 프로젝트에 통합

  1. 프로젝트 속성으로 이동: 프로젝트 마우스 오른쪽 클릭 → 속성
  2. 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는 두 가지 방식으로 사용할 수 있습니다:

  1. Boost.ASIO: Boost의 일부로 제공되는 ASIO
  2. 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++ 애플리케이션의 성능을 최적화하기 위한 몇 가지 팁:

  1. 메시지 버퍼링: 작은 메시지를 여러 개 보내는 대신 가능한 경우 큰 메시지로 배치 처리합니다.
  2. connection_hdl 캐싱: 자주 사용하는 연결 핸들을 캐시하여 조회 오버헤드를 줄입니다.
  3. 로깅 레벨 최적화: 프로덕션 환경에서는 로깅 레벨을 낮추어 성능 오버헤드를 줄입니다: server.clear_access_channels(websocketpp::log::alevel::all); server.set_access_channels(websocketpp::log::alevel::error);
  4. 재사용 가능한 메시지 버퍼: 메시지 객체를 재사용하여 할당/해제 오버헤드를 줄입니다.
  5. 적절한 스레드 수: 하드웨어 환경에 맞게 스레드 수를 조정합니다.

결론

WebSocket++ 라이브러리는 C++ 애플리케이션에서 WebSocket 프로토콜을 구현하기 위한 강력하고 유연한 도구입니다. 그러나 초기 설정과 종속성 관리에 어려움이 있을 수 있습니다. 이 글에서 제시한 단계와 문제 해결 방법을 따르면, 대부분의 경우 WebSocket++ 라이브러리를 성공적으로 프로젝트에 통합할 수 있을 것입니다.

올바른 소스를 찾고, 필요한 종속성을 설치하며, 프로젝트의 빌드 설정을 적절히 구성하는 것이 중요합니다. 일단 라이브러리가 올바르게 설정되면, WebSocket++ API를 사용하여 확장 가능하고 효율적인 실시간 통신 시스템을 구축할 수 있습니다.

WebSocket++는 PicoTorrent와 같은 실제 프로젝트에서 성공적으로 사용되고 있으며, 이 가이드가 당신의 프로젝트에서도 WebSocket++ 통합을 더 쉽게 만들어 주기를 바랍니다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다