[카테고리:] 미분류

  • 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++ 통합을 더 쉽게 만들어 주기를 바랍니다.