콘텐츠로 건너뛰기

C++와 Python 연동하여 데이터 주고받기 – ZeroMQ로 프로세스간 통신 2가지 예제

지난 포스팅에서는 LCM(Lightweight Communication Marshalling)을 이용해 서로 다른 프로세스간 데이터를 주고받는 방법을 공유해보았는데, 이번엔 ZeroMQ 라이브러리를 이용하는 방법을 적어보고자 합니다. PyTorch 혹은 Tensorflow와 같이 Python 기반으로 만들어진 머신러닝 프로그램을 C++로 개발된 다른 프로그램과 연동하고자 한다면 프로세스간 통신기능은 필수적 입니다. 이렇기에 로보틱스 분야에서도 종종 필요로 하는 기능입니다.

컴퓨터의 세계에서, 서로 다른 프로세스 간의 소통은 마치 인간 관계처럼 중요합니다. 복잡한 시스템이 원활하게 작동하기 위해서는 프로세스 간 통신(IPC)이 필수적인 요소입니다. IPC는 서로 다른 프로세스가 데이터를 공유하고 협력하는 방법을 말하며, 이는 소프트웨어의 성능과 안정성에 직접적으로 영향을 미칩니다. 특히, 현대의 분산 시스템과 클라우드 기반 서비스에서 IPC의 역할은 더욱 중요해졌습니다.

이러한 맥락에서, ZeroMQ는 주목할 만한 IPC 솔루션으로 자리잡았습니다. 이번 포스팅에서는, 산업에서도 종종 사용되는 이 통신 프레임워크를 이용해 다른 프로세스간 데이터를 주고받는 방법에 대해 소개드리도록 하겠습니다. 이 라이브러리의 기본 개념부터 그 특징과 활용 사례까지 살펴볼 예정입니다. 더 나아가, LCM(Lightweight Communications and Marshalling)ROS(Robot Operating System) 같은 다른 라이브러리들과 비교하며, 이들 간의 차이점과 각각의 환경에서의 적합성에 대해 분석해보겠습니다.



zeromq

ZeroMQ란

ZeroMQ는 고성능의 프로세스 간 통신(IPC)을 가능하게 하는 라이브러리입니다. 이는 소프트웨어 개발자들에게 분산 컴퓨팅 및 메시지 큐 시스템을 손쉽게 구현할 수 있는 방법을 제공합니다. 이 라이브러리는 다양한 프로그래밍 언어와 플랫폼을 지원하며, 그 사용이 매우 간편합니다.

여러 특징 중 하나를 먼저 꼽자면 빠른 속도와 낮은 지연 시간입니다. 이는 고성능을 요구하는 애플리케이션에 적합하며, 대규모 데이터 처리와 실시간 시스템에서 빛을 발합니다. 또한, 여러 가지 메시지 전달 패턴을 제공합니다. 가장 널리 사용되는 패턴은 Publish/Subscribe, Request/Reply, Push/Pull 등이 있습니다.

또, 내부적으로 복잡한 네트워크 작업을 추상화하여 개발자가 네트워크 프로토콜이나 소켓 관리에 신경 쓸 필요 없이 메시지 전송에 집중할 수 있도록 해줍니다. 이는 개발 시간을 단축시키고, 더 직관적인 API를 통해 개발자의 부담을 줄여줍니다.

또 다른 장점으로, 이 라이브러리는 매우 가볍고, 필요한 최소한의 기능만을 제공하여 리소스의 효율적인 사용을 가능하게 합니다. 이러한 특성은 특히 임베디드 시스템이나 리소스가 제한된 환경에서 유리합니다.

이러한 특징들은 그것을 현대 소프트웨어 개발, 특히 대규모 분산 시스템, 실시간 데이터 처리, 고성능 컴퓨팅 등의 영역에서 필수적인 도구로 만들어주었습니다. 간단한 구성과 강력한 기능으로, 다양한 프로젝트와 애플리케이션의 효율적인 메시징 요구 사항을 충족시키는 데 이상적인 선택입니다.

ZeroMQ의 기능과 장점

ZeroMQ는 고성능 메시징 라이브러리로, 프로세스 간 통신(IPC)과 네트워크 통신을 쉽게 구현할 수 있게 해줍니다. 이는 소켓 스타일의 API를 제공하여, 다양한 메시지 전달 패턴을 사용하여 통신할 수 있습니다.

  1. 메시지 패턴: 이 라이브러리의 강력한 특징 중 하나는 다양한 메시지 패턴을 지원한다는 것입니다. 이 패턴들은 다음과 같습니다:
    • Publish/Subscribe (Pub/Sub): 이 패턴은 데이터를 여러 수신자에게 효율적으로 전달하는 데 사용됩니다. 발행자(publisher)는 메시지를 전송하고, 구독자(subscriber)는 관심 있는 메시지만을 선택적으로 수신합니다.
    • Request/Reply (Req/Rep): 이 패턴은 클라이언트-서버 통신에 사용됩니다. 클라이언트는 요청(request)을 보내고, 서버는 응답(reply)을 제공합니다.
    • Push/Pull: 이 패턴은 작업 분배에 사용됩니다. 생산자(push)는 작업을 생성하고, 소비자(pull)는 이를 받아 처리합니다.
  2. 비동기 I/O 모델: 이 라이브러리는 비동기 I/O 모델을 사용하여 높은 처리량과 낮은 지연 시간을 제공합니다. 이 모델을 통해 메시지는 내부 큐에 저장되며, 네트워크 상태나 트래픽에 관계없이 빠르게 전송됩니다.
  3. 확장성과 유연성: 이 라이브러리는 매우 가볍고, 확장 가능한 구조를 가지고 있습니다. 이를 통해 소규모 애플리케이션에서 대규모 분산 시스템에 이르기까지 다양한 환경에서 사용할 수 있습니다.
  4. 언어와 플랫폼의 독립성: 이 라이브러리는 C++, Python, .NET, Java 등 다양한 프로그래밍 언어로 구현될 수 있으며, 다양한 운영 체제에서 실행될 수 있습니다. 이는 광범위한 개발 환경에 적용될 수 있음을 의미합니다.
  5. 소켓 레벨의 옵션: 이 라이브러리는 다양한 소켓 옵션을 제공하여, 메시지 크기, 큐 길이, 타임아웃 등을 조절할 수 있습니다. 이를 통해 세밀한 통신 제어가 가능합니다.
  6. 보안: 이 라이브러리는 기본적으로 암호화나 인증 기능을 제공하지 않지만, 이를 추가적으로 구현할 수 있는 여지가 있습니다. 일반적으로, 이 라이브러리는 기존의 보안 계층(예: SSL/TLS) 위에서 동작하도록 구성될 수 있습니다.

ZeroMQ 통신 예제

지금부터는 ZeroMQ를 이용해 Python 프로그램과 C++ 프로그램간 데이터를 주고받는 통신 예시를 소개드리도 하겠습니다. 아래 예시는 메세지 전달만 고려한 간단한 예시이지만, 필요에 따라 다른 Python 혹은 C++ 프로그램에 적용하여 사용할 수 있습니다.

C++용 패키지 설치하기

Windows

윈도우즈에서의 설치는 vcpkg를 이용하실 것을 권장합니다. vcpkg를 사용한 적이 없는 환경이라면 아래 링크를 참고하시어 첫 셋업을 완료 후 사용하시면 됩니다. 간단하게 커맨드만 골라 적어보면 아래와 같습니다.

#Clone vcpkg from github
git clone https://github.com/microsoft/vcpkg

#locate vcpkg folder
cd vcpkg

#install vcpkg
.\bootstrap-vcpkg.bat
.\vcpkg integrate install

vcpkg가 준비되어 있다면 아래와 같이 설치합니다. 혹시 사용하는 환경이 64비트 CPU 환경이 아니라면 cppzmq 까지만 적이시면 됩니다.

.\vcpkg install zeromq:x64-windows

Ubuntu

아래 커맨드로 패키지를 설치할 수 있습니다.

sudo apt-get install libzmq3-dev

Python용 패키지 설치하기

Python 특성상 OS 구분 없이 아래 커맨드로 패키지를 설치할 수 있습니다.

pip install pyzmq

C++ 서버 예시

우선 서버역할을 하는 C++ 프로그램을 만들어 보겠습니다. 이번 예시에서는 CMake를 이용해 빌드하겠습니다.

# CMakeList.txt : CMake project for zmq_cpp_server_ex, include source and define
# kongineer.com
#

cmake_minimum_required(VERSION 3.8)
project(zeromq_cpp_server_ex)

# Find ZeroMQ
find_package(ZeroMQ REQUIRED)

# Add your source files
add_executable(${PROJECT_NAME} main.cpp)

# Include directories
target_include_directories(${PROJECT_NAME} PRIVATE ${ZeroMQ_INCLUDE_DIR})

# Link the Library
target_link_libraries(${PROJECT_NAME} PRIVATE ${ZeroMQ_LIBRARIES})

만약, CMake에서 아래와 같이 ZeroMQ를 찾지 못하는 문제가 발생되면, 직접 경로를 지정해줄 수 있습니다.

CMake Error at CMakeLists.txt:12 (find_package):
  
  ...

  Could not find a package configuration file provided by "ZeroMQ" with any
  of the following names:

    ZeroMQConfig.cmake
    zeromq-config.cmake

프로젝트 폴더에 cmake 폴더를 만들고, 아래 링크와 같이 FindZeroMQ.cmake 파일을 만들어 이링크의 코드를 붙여넣고 저장합니다. 그리고 나서 아래 코드를 CMakeLists.txt에 추가합니다.

# Add this if cmake fails to find the package
list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

다음으로, 메인이 되는 cpp 파일을 만들겠습니다. 이미 자동으로 만들어져 있다면 모두 지우고 다시 작성하면 됩니다. 제가 수정하기 전 원본은 이곳에서 확인하실 수 있습니다.

// Original Code from http://kr.zeromq.org/cpp:hwserver
// kongineer.com


#include <zmq.hpp>
#include <string>
#include <iostream>

#ifdef _WIN32
#include<Windows.h>
#elif defined __unix__
#include <unistd.h>
#endif

void delay(int msec)
{
#ifdef _WIN32
    Sleep(msec);
#elif defined __unix__
    sleep(msec);
#endif
}

int main() {
    // Initialize the context
    zmq::context_t context(1);

    // Create a socket of type REP (reply)
    zmq::socket_t socket(context, ZMQ_REP);

    // Bind the socket to a TCP address
    std::cout << "Starting the server on port 5555..." << std::endl;
    socket.bind("tcp://*:5555");

    while (true) {
        zmq::message_t request;

        // Wait for the next request from a client
        socket.recv(&request);
        std::string received_message(static_cast<char*>(request.data()), request.size());
        std::cout << "Received: " << received_message << std::endl;

        // Simulate some work
        delay(1);

        // Send a reply back to the client
        std::string reply_message = "Hi from ZeroMQ C++ Server";
        zmq::message_t reply(reply_message.size());
        memcpy(reply.data(), reply_message.data(), reply_message.size());
        socket.send(reply, zmq::send_flags::none);
    }

    return 0;
}

Python Client 예시

이번엔 클라이언트 역할을 하는 Python 프로그램을 만들어 보겠습니다. 참고한 원본 코드는 여기서 확인할 수 있습니다.

"""
Original code from https://zeromq.org/languages/python/
"""

import zmq

context = zmq.Context()

#  Connect to the server
print("Connecting to cpp server…")
socket = context.socket(zmq.REQ)
socket.connect("tcp://localhost:5555")

#Send data
message_out_str = "Hi from Python ZeroMQ Client"
message_out_bytes = message_out_str.encode("utf-8")

print("Sending request …")
socket.send(message_out_bytes)

#Get the server response
message_in_bytes = socket.recv()
# Convert message bytes to string
message_in_str = message_in_bytes.decode("utf-8")
print("Received [ %s ]" % message_in_str)

두 프로그램간 통신 실행하기

위에 적은 두 프로그램을 실행하여 통신을 주고받는 과정을 확인해 보겠습니다. 먼저, C++ 서버부터 실행 후 Python Client를 실행하면 아래와 같이 통신 결과를 확인할 수 있습니다.

zeromq communication
두 프로세스의 Terminal. 왼쪽이 Client, 오른쪽이 Server.

이렇게 다른 언어로 작성되었을 뿐 아니라 다른 운영체제에 있는 프로그램 사이에서도 이 라이브러리를 이용해 같은 방식으로 데이터를 주고받을 수 있습니다.

위 예시에서는 문자열만 주고받았지만, OpenCV mat과 같이 더 큰 파일도 어렵지않게 주고받을 수 있습니다. 이에 대해서는 별도 포스팅에서 다뤄보도록 하겠습니다.

마무리

이번 포스팅에서는 ZeroMQ, LCM, ROS 각각의 특징을 알아보고 ZeroMQ를 이용한 프로그램간 통신의 예시를 적어보았습니다. 이들 각각은 프로세스 간 통신의 필요성을 충족시키는 독특한 접근 방식을 제공합니다. ZeroMQ는 범용성과 성능에 중점을 두며, LCM은 실시간 시스템과 로봇공학에서의 데이터 마샬링과 통신에 강점을 보입니다. 반면, ROS는 로봇공학 분야의 포괄적인 솔루션을 제공합니다.

프로젝트의 성공은 적절한 도구의 선택에서 시작됩니다. 프로젝트의 요구 사항, 성능 기준, 개발 환경 및 목표에 따라 가장 적합한 통신 솔루션을 선택해야 합니다. 소개드린 라이브러리 중 어느 것이든, 각각의 도구는 특정한 요구와 환경에 최적화된 솔루션을 제공할 수 있습니다. 주고받은 요건이 까다롭지 않다면 어느 툴을 이용해도 큰 문제는 없을 것 입니다.

답글 남기기

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