본문 바로가기
-----ETC-----/C++ 네트워크 프로그래밍 시리즈

[C++ 네트워크 프로그래밍] Day 5: 네트워크 데이터 직렬화

by cogito21_cpp 2024. 8. 1.
반응형

네트워크 데이터 직렬화

네트워크 통신에서 데이터를 전송할 때, 데이터를 직렬화(Serialization)하여 전송하고, 수신 측에서는 이를 역직렬화(Deserialization)하여 원래의 데이터로 복원해야 합니다. 직렬화는 데이터를 일정한 형식으로 변환하여 네트워크를 통해 전송할 수 있게 합니다.

 

직렬화의 주요 개념

직렬화와 역직렬화

  • 직렬화(Serialization): 객체나 데이터를 바이트 스트림으로 변환하는 과정입니다. 이 과정을 통해 데이터를 네트워크로 전송하거나 파일에 저장할 수 있습니다.
  • 역직렬화(Deserialization): 바이트 스트림을 원래의 객체나 데이터로 복원하는 과정입니다.

직렬화 포맷

  • JSON: 가볍고 읽기 쉬운 텍스트 기반의 데이터 교환 형식입니다. 대부분의 언어에서 지원되며, 웹 애플리케이션에서 많이 사용됩니다.
  • XML: 마크업 언어로, 데이터의 계층적 구조를 표현하는 데 사용됩니다.
  • Protobuf: Google이 개발한 바이너리 직렬화 포맷으로, 효율적이고 빠릅니다.
  • Binary: 이진 데이터 형식으로, 빠르고 공간 효율적입니다. 다만, 사람에게는 읽기 어렵습니다.

Boost.Serialization을 이용한 직렬화

Boost.Serialization 라이브러리는 C++에서 객체를 직렬화하고 역직렬화하는 기능을 제공합니다. 이를 사용하여 데이터를 네트워크로 전송할 수 있습니다.

Boost.Serialization 설치

Boost 라이브러리를 설치하여 Boost.Serialization 모듈을 사용할 수 있도록 합니다. (이전 단계에서 설치된 Boost 라이브러리를 사용할 수 있습니다.)

예제 코드: 객체의 직렬화와 역직렬화

Person 클래스 정의 및 직렬화

#include <iostream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/string.hpp>

class Person {
public:
    Person() = default;
    Person(const std::string& name, int age) : name_(name), age_(age) {}

    friend std::ostream& operator<<(std::ostream& os, const Person& person) {
        os << "이름: " << person.name_ << ", 나이: " << person.age_;
        return os;
    }

private:
    std::string name_;
    int age_;

    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive& ar, const unsigned int version) {
        ar & name_;
        ar & age_;
    }
};

 

직렬화와 역직렬화 예제

main.cpp

#include <iostream>
#include <sstream>
#include "Person.h"

int main() {
    Person person1("홍길동", 30);

    // 객체를 문자열 스트림으로 직렬화
    std::ostringstream oss;
    boost::archive::text_oarchive oa(oss);
    oa << person1;

    std::string serialized_data = oss.str();
    std::cout << "직렬화된 데이터: " << serialized_data << std::endl;

    // 문자열 스트림에서 객체로 역직렬화
    std::istringstream iss(serialized_data);
    boost::archive::text_iarchive ia(iss);
    Person person2;
    ia >> person2;

    std::cout << "역직렬화된 객체: " << person2 << std::endl;

    return 0;
}

 

설명

위의 코드는 Person 클래스를 정의하고, 이를 Boost.Serialization 라이브러리를 사용하여 직렬화하고 역직렬화하는 예제입니다.

  • Person 클래스:
    • name_age_ 멤버 변수를 가지며, Boost.Serialization 라이브러리를 사용하여 직렬화할 수 있도록 serialize 함수를 정의합니다.
  • main.cpp:
    • Person 객체를 생성하고, 이를 문자열 스트림으로 직렬화합니다.
    • 직렬화된 데이터를 출력한 후, 이를 다시 Person 객체로 역직렬화합니다.

네트워크 데이터 전송

이제 직렬화된 데이터를 네트워크를 통해 전송하는 방법을 살펴보겠습니다. 이전 단계에서 작성한 비동기 TCP 서버와 클라이언트 코드를 사용하여 직렬화된 Person 객체를 전송하고 수신하는 예제를 작성하겠습니다.

 

비동기 TCP 서버: 직렬화된 데이터 전송

AsyncTcpServer.h

#ifndef ASYNCTCPSERVER_H
#define ASYNCTCPSERVER_H

#include <boost/asio.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/access.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>

using boost::asio::ip::tcp;

class Person {
public:
    Person() = default;
    Person(const std::string& name, int age) : name_(name), age_(age) {}

    friend std::ostream& operator<<(std::ostream& os, const Person& person) {
        os << "이름: " << person.name_ << ", 나이: " << person.age_;
        return os;
    }

private:
    std::string name_;
    int age_;

    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive& ar, const unsigned int version) {
        ar & name_;
        ar & age_;
    }
};

class AsyncTcpServer {
public:
    AsyncTcpServer(boost::asio::io_context& io_context, short port)
        : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
        start_accept();
    }

private:
    void start_accept() {
        auto new_session = std::make_shared<tcp::socket>(acceptor_.get_executor().context());
        acceptor_.async_accept(*new_session,
            [this, new_session](const boost::system::error_code& error) {
                if (!error) {
                    handle_accept(new_session);
                }
                start_accept();
            });
    }

    void handle_accept(std::shared_ptr<tcp::socket> socket) {
        auto person = std::make_shared<Person>("홍길동", 30);
        std::ostringstream oss;
        boost::archive::text_oarchive oa(oss);
        oa << *person;

        auto serialized_data = std::make_shared<std::string>(oss.str());
        boost::asio::async_write(*socket, boost::asio::buffer(*serialized_data),
            [this, socket, serialized_data](const boost::system::error_code& error, std::size_t) {
                if (!error) {
                    std::cout << "데이터 전송 완료" << std::endl;
                }
            });
    }

    tcp::acceptor acceptor_;
};

#endif // ASYNCTCPSERVER_H

 

main.cpp (서버)

#include <iostream>
#include <boost/asio.hpp>
#include "AsyncTcpServer.h"

int main() {
    try {
        boost::asio::io_context io_context;
        AsyncTcpServer server(io_context, 12345);
        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "예외 발생: " << e.what() << std::endl;
    }

    return 0;
}

 

비동기 TCP 클라이언트: 직렬화된 데이터 수신

AsyncTcpClient.h

#ifndef ASYNCTCPCLIENT_H
#define ASYNCTCPCLIENT_H

#include <boost/asio.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/access.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>

using boost::asio::ip::tcp;

class Person {
public:
    Person() = default;
    Person(const std::string& name, int age) : name_(name), age_(age) {}

    friend std::ostream& operator<<(std::ostream& os, const Person& person) {
        os << "이름: " << person.name_ << ", 나이: " << person.age_;
        return os;
    }

private:
    std::string name_;
    int age_;

    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive& ar, const unsigned int version) {
        ar & name_;
        ar & age_;
    }
};

class AsyncTcpClient {
public:
    AsyncTcpClient(boost::asio::io_context& io_context, const std::string& host, const std::string& service)
        : resolver_(io_context), socket_(io_context) {
        connect(host, service);
    }

private:
    void connect(const std::string& host, const std::string& service) {
        auto endpoints = resolver_.resolve(host, service);
        boost::asio::async_connect(socket_, endpoints,
            [this](const boost::system::error_code& error, const tcp::endpoint&) {
                if (!error) {
                    do_read();
                }
            });
    }

    void do_read() {
        boost::asio::async_read(socket_,

 boost::asio::buffer(data_),
            [this](const boost::system::error_code& error, std::size_t length) {
                if (!error) {
                    std::istringstream iss(std::string(data_, length));
                    boost::archive::text_iarchive ia(iss);
                    Person person;
                    ia >> person;

                    std::cout << "수신한 데이터: " << person << std::endl;
                }
            });
    }

    tcp::resolver resolver_;
    tcp::socket socket_;
    char data_[1024];
};

#endif // ASYNCTCPCLIENT_H

 

main.cpp (클라이언트)

#include <iostream>
#include <boost/asio.hpp>
#include "AsyncTcpClient.h"

int main() {
    try {
        boost::asio::io_context io_context;
        AsyncTcpClient client(io_context, "127.0.0.1", "12345");
        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "예외 발생: " << e.what() << std::endl;
    }

    return 0;
}

 

설명

위의 코드는 비동기 TCP 서버와 클라이언트를 구현하여 Person 객체를 직렬화하여 전송하고, 수신하는 예제입니다.

  • 비동기 TCP 서버:
    • 서버는 AsyncTcpServer 클래스를 통해 클라이언트의 연결을 비동기적으로 수락합니다.
    • Person 객체를 직렬화하여 클라이언트로 전송합니다.
  • 비동기 TCP 클라이언트:
    • 클라이언트는 AsyncTcpClient 클래스를 통해 서버에 비동기적으로 연결합니다.
    • 서버로부터 직렬화된 데이터를 수신하여 Person 객체로 역직렬화합니다.

이제 다섯 번째 날의 학습을 마쳤습니다. 네트워크 데이터 직렬화의 기본 개념과 Boost.Serialization 라이브러리를 사용하여 데이터를 직렬화하고, 이를 네트워크로 전송하는 방법을 학습했습니다.

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "네트워크 프로토콜 기초"에 대해 학습하겠습니다.

반응형