소켓 연결을 통해 직렬화하고 전송해야하는 작은 개체 계층 구조가 있습니다. 객체를 직렬화 한 다음 유형에 따라 역 직렬화해야합니다. C ++에서이 작업을 수행하는 쉬운 방법이 있습니까 (Java에 있음)?
C ++ 직렬화 온라인 코드 샘플 또는 자습서가 있습니까?
편집 : 명확하게 말하면 객체를 바이트 배열로 변환 한 다음 다시 객체로 변환하는 방법을 찾고 있습니다. 소켓 전송을 처리 할 수 있습니다.
소켓 연결을 통해 직렬화하고 전송해야하는 작은 개체 계층 구조가 있습니다. 객체를 직렬화 한 다음 유형에 따라 역 직렬화해야합니다. C ++에서이 작업을 수행하는 쉬운 방법이 있습니까 (Java에 있음)?
C ++ 직렬화 온라인 코드 샘플 또는 자습서가 있습니까?
편집 : 명확하게 말하면 객체를 바이트 배열로 변환 한 다음 다시 객체로 변환하는 방법을 찾고 있습니다. 소켓 전송을 처리 할 수 있습니다.
답변:
직렬화에 대해 이야기하면 부스트 직렬화 API 가 떠 오릅니다. 직렬화 된 데이터를 네트워크를 통해 전송하려면 Berkeley 소켓이나 asio 라이브러리를 사용 합니다.
편집 :
객체를 바이트 배열로 직렬화하려는 경우 다음과 같은 방식으로 부스트 직렬 변환기를 사용할 수 있습니다 (자습서 사이트에서 가져옴).
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
class gps_position
{
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & degrees;
ar & minutes;
ar & seconds;
}
int degrees;
int minutes;
float seconds;
public:
gps_position(){};
gps_position(int d, int m, float s) :
degrees(d), minutes(m), seconds(s)
{}
};
실제 직렬화는 매우 쉽습니다.
#include <fstream>
std::ofstream ofs("filename.dat", std::ios::binary);
// create class instance
const gps_position g(35, 59, 24.567f);
// save data to archive
{
boost::archive::binary_oarchive oa(ofs);
// write class instance to archive
oa << g;
// archive and stream closed when destructors are called
}
역 직렬화는 유사한 방식으로 작동합니다.
포인터의 직렬화 (tress 등과 같은 복잡한 데이터 구조는 문제 없음), 파생 클래스를 처리 할 수있는 메커니즘도 있으며 이진 및 텍스트 직렬화 중에서 선택할 수 있습니다. 게다가 모든 STL 컨테이너는 즉시 지원됩니다.
경우에 따라 단순 유형을 처리 할 때 다음을 수행 할 수 있습니다.
object o;
socket.write(&o, sizeof(o));
개념 증명 또는 초안으로 괜찮으므로 팀의 다른 구성원이 다른 부분에서 계속 작업 할 수 있습니다.
그러나 조만간, 일반적으로 조만간 이것은 당신을 다치게 할 것입니다!
다음과 같은 문제가 발생합니다.
(또한 수신 측에서 무엇을 풀고 있는지 알아야합니다.)
모든 클래스에 대해 고유 한 마샬링 / 언 마샬링 메서드를 개발하여이를 개선 할 수 있습니다. (이상적으로는 가상이므로 하위 클래스에서 확장 할 수 있습니다.) 몇 가지 간단한 매크로를 사용하면 빅 / 리틀 엔디안 중립 순서로 다른 기본 유형을 매우 빠르게 작성할 수 있습니다.
그러나 그런 종류의 지저분한 작업은 boost의 직렬화 라이브러리 를 통해 훨씬 더 좋고 더 쉽게 처리됩니다 .
객체를 직렬화하는 데 사용할 수있는 일반적인 패턴이 있습니다. 기본 기본 요소는 반복기에서 읽고 쓸 수있는 다음 두 함수입니다.
template <class OutputCharIterator>
void putByte(char byte, OutputCharIterator &&it)
{
*it = byte;
++it;
}
template <class InputCharIterator>
char getByte(InputCharIterator &&it, InputCharIterator &&end)
{
if (it == end)
{
throw std::runtime_error{"Unexpected end of stream."};
}
char byte = *it;
++it;
return byte;
}
그런 다음 직렬화 및 역 직렬화 기능은 다음 패턴을 따릅니다.
template <class OutputCharIterator>
void serialize(const YourType &obj, OutputCharIterator &&it)
{
// Call putbyte or other serialize overloads.
}
template <class InputCharIterator>
void deserialize(YourType &obj, InputCharIterator &&it, InputCharIterator &&end)
{
// Call getByte or other deserialize overloads.
}
클래스의 경우 friend 함수 패턴을 사용하여 ADL을 사용하여 오버로드를 찾을 수 있습니다.
class Foo
{
int internal1, internal2;
// So it can be found using ADL and it accesses private parts.
template <class OutputCharIterator>
friend void serialize(const Foo &obj, OutputCharIterator &&it)
{
// Call putByte or other serialize overloads.
}
// Deserialize similar.
};
프로그램에서 직렬화하고 다음과 같은 파일로 개체를 지정할 수 있습니다.
std::ofstream file("savestate.bin");
serialize(yourObject, std::ostreambuf_iterator<char>(file));
그런 다음 읽으십시오.
std::ifstream file("savestate.bin");
deserialize(yourObject, std::istreamBuf_iterator<char>(file), std::istreamBuf_iterator<char>());
내 오래된 대답은 다음과 같습니다.
직렬화는 객체를 이진 데이터로 바꾸는 것을 의미합니다. 역 직렬화는 데이터에서 개체를 다시 만드는 것을 의미합니다.
직렬화 할 때 바이트를 uint8_t
벡터 로 푸시합니다 . 직렬화를 해제 할 때 uint8_t
벡터 에서 바이트를 읽는 것 입니다.
물건을 직렬화 할 때 사용할 수있는 패턴이 확실히 있습니다.
각 직렬화 가능 클래스 serialize(std::vector<uint8_t> &binaryData)
에는 제공된 벡터에 이진 표현을 기록 하는 또는 유사한 서명 함수 가 있어야합니다 . 그런 다음이 함수는이 벡터를 멤버의 직렬화 함수로 전달하여 자신의 내용도 쓸 수 있습니다.
데이터 표현은 아키텍처마다 다를 수 있기 때문입니다. 데이터를 표현하는 방법을 찾아야합니다.
기본부터 시작하겠습니다.
리틀 엔디안 순서로 바이트를 작성하십시오. 또는 크기가 중요한 경우 varint 표현을 사용하십시오.
리틀 엔디안 순서로 직렬화 :
data.push_back(integer32 & 0xFF);
data.push_back((integer32 >> 8) & 0xFF);
data.push_back((integer32 >> 16) & 0xFF);
data.push_back((integer32 >> 24) & 0xFF);
리틀 엔디안 순서에서 역 직렬화 :
integer32 = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
내가 아는 한 IEEE 754는 여기에서 독점권을 가지고 있습니다. 나는 수레에 대해 다른 것을 사용할 주류 아키텍처를 알지 못합니다. 다를 수있는 유일한 것은 바이트 순서입니다. 일부 아키텍처는 리틀 엔디안을 사용하고 다른 아키텍처는 빅 엔디안 바이트 순서를 사용합니다. 이것은 수신 측에서 바이트를 크게하는 순서에주의해야 함을 의미합니다. 또 다른 차이점은 비정규 및 무한대 및 NAN 값을 처리하는 것입니다. 그러나 이러한 값을 피하는 한 괜찮습니다.
직렬화 :
uint8_t mem[8];
memcpy(mem, doubleValue, 8);
data.push_back(mem[0]);
data.push_back(mem[1]);
...
역 직렬화는 역 직렬화입니다. 아키텍처의 바이트 순서에 유의하십시오!
먼저 인코딩에 동의해야합니다. UTF-8이 일반적입니다. 그런 다음 길이 접두사 방식으로 저장합니다. 먼저 위에서 언급 한 방법을 사용하여 문자열의 길이를 저장 한 다음 문자열을 바이트 단위로 작성합니다.
그들은 문자열과 동일합니다. 먼저 배열의 크기를 나타내는 정수를 직렬화 한 다음 배열의 각 개체를 직렬화합니다.
앞서 말했듯 serialize
이 벡터에 콘텐츠를 추가 하는 방법이 있어야합니다 . 객체를 직렬화 해제하려면 바이트 스트림을 사용하는 생성자가 있어야합니다. 일 수 istream
있지만 가장 간단한 경우에는 참조 uint8_t
포인터 일 수 있습니다 . 생성자는 스트림에서 원하는 바이트를 읽고 객체의 필드를 설정합니다. 시스템이 잘 설계되고 개체 필드 순서로 필드를 직렬화하는 경우 이니셜 라이저 목록의 필드 생성자에 스트림을 전달하고 올바른 순서로 역 직렬화 할 수 있습니다.
먼저 이러한 객체가 실제로 직렬화하려는 것인지 확인해야합니다. 이러한 개체의 인스턴스가 대상에있는 경우 직렬화 할 필요가 없습니다.
이제 포인터가 가리키는 객체를 직렬화해야한다는 것을 알았습니다. 포인터를 사용하는 프로그램에서만 유효하다는 문제. 포인터를 직렬화 할 수 없으므로 개체에서 포인터 사용을 중지해야합니다. 대신 개체 풀을 만듭니다. 이 개체 풀은 기본적으로 "상자"를 포함하는 동적 배열입니다. 이 상자에는 참조 횟수가 있습니다. 0이 아닌 참조 횟수는 라이브 객체를 나타내고 0은 빈 슬롯을 나타냅니다. 그런 다음 객체에 대한 포인터를 저장하지 않고 배열의 인덱스를 저장하는 shared_ptr과 유사한 스마트 포인터를 만듭니다. 또한 널 포인터를 나타내는 색인에 동의해야합니다. -1.
기본적으로 여기서 우리가 한 일은 포인터를 배열 인덱스로 대체 한 것입니다. 이제 직렬화 할 때 평소처럼이 배열 인덱스를 직렬화 할 수 있습니다. 대상 시스템의 메모리에서 개체가 어디에 있는지 걱정할 필요가 없습니다. 동일한 개체 풀이 있는지 확인하십시오.
따라서 개체 풀을 직렬화해야합니다. 그러나 어떤 것? 객체 그래프를 직렬화 할 때 객체를 직렬화하는 것이 아니라 전체 시스템을 직렬화하는 것입니다. 이것은 시스템의 직렬화가 시스템의 일부에서 시작되어서는 안된다는 것을 의미합니다. 이러한 개체는 시스템의 나머지 부분에 대해 걱정할 필요가 없으며 배열 인덱스를 직렬화하기 만하면됩니다. 시스템 직렬화를 오케스트레이션하고 관련 개체 풀을 살펴보고 모두 직렬화하는 시스템 직렬화 루틴이 있어야합니다.
수신 측에서 모든 배열과 개체가 역 직렬화되어 원하는 개체 그래프를 다시 만듭니다.
개체에 포인터를 저장하지 마십시오. 이러한 함수에 대한 포인터를 포함하는 정적 배열을 갖고 객체에 인덱스를 저장합니다.
두 프로그램 모두이 테이블이 쉘프에 컴파일되어 있으므로 인덱스 만 사용하면됩니다.
직렬화 가능한 유형의 포인터를 피해야하고 대신 배열 인덱스를 사용해야한다고 말 했으므로 다형성은 포인터가 필요하기 때문에 작동하지 않습니다.
유형 태그 및 공용체로이 문제를 해결해야합니다.
위의 모든 것 위에. 서로 다른 버전의 소프트웨어가 상호 운용되기를 원할 수 있습니다.
이 경우 각 개체는 직렬화 시작 부분에 버전 번호를 작성하여 버전을 표시해야합니다.
다른 쪽에서 객체를로드 할 때 최신 객체는 이전 표현을 처리 할 수 있지만 이전 표현은 최신 표현을 처리 할 수 없으므로 이에 대한 예외를 throw해야합니다.
무언가 변경 될 때마다 버전 번호를 올려야합니다.
그래서 이것을 마무리하기 위해 직렬화는 복잡 할 수 있습니다. 그러나 다행스럽게도 프로그램의 모든 것을 직렬화 할 필요는 없습니다. 대부분의 경우 프로토콜 메시지 만 직렬화되며, 이는 종종 평범한 오래된 구조체입니다. 따라서 위에서 언급 한 복잡한 트릭이 너무 자주 필요하지 않습니다.
학습을 통해 저는 간단한 C ++ 11 직렬 변환기를 작성했습니다. 나는 다른 다양한 무거운 제품을 시도해 보았지만 잘못되거나 최신 g ++로 컴파일하지 못했을 때 실제로 이해할 수있는 무언가를 원했습니다. (Cereal에서 저에게 일어났던 일입니다. 정말 멋진 라이브러리이지만 복잡하고 그럴 수 없었습니다. 컴파일러가 업그레이드 할 때 발생시킨 오류.) 어쨌든, 헤더 만 있고 POD 유형, 컨테이너, 맵 등을 처리합니다. 버전 관리가 없으며 저장된 동일한 아치에서 파일 만로드합니다.
https://github.com/goblinhack/simple-c-plus-plus-serializer
사용 예 :
#include "c_plus_plus_serializer.h"
static void serialize (std::ofstream out)
{
char a = 42;
unsigned short b = 65535;
int c = 123456;
float d = std::numeric_limits<float>::max();
double e = std::numeric_limits<double>::max();
std::string f("hello");
out << bits(a) << bits(b) << bits(c) << bits(d);
out << bits(e) << bits(f);
}
static void deserialize (std::ifstream in)
{
char a;
unsigned short b;
int c;
float d;
double e;
std::string f;
in >> bits(a) >> bits(b) >> bits(c) >> bits(d);
in >> bits(e) >> bits(f);
}