2014-11-21 4 views
3

에의 char *에서 reinterpret_cast의 사용은 sendto(..)recvfrom()를 사용하여 UDP 소켓을 통해 같은 프로그램을 실행 (동일하거나 유사한 시스템의) 다른 서버와 struct update_packet을했다.C++ 직렬화 - 나는 구조체를 교환하고 구조체

update_packet은 일반 메시지 형식이어야합니다. 즉, 해당 필드는 미리 지정된 고정 크기이고 구조체의 크기는 필드의 합계입니다.

struct node { 
    uint32_t IP; 
    uint16_t port; 
    int16_t nil; 
    uint16_t server_id; 
    uint16_t cost; 
}; 

struct update_packet { 
    uint16_t num_update_fields; 
    uint16_t port; 
    uint32_t IP; 

    struct node * nodes; 

    update_packet() : 
     num_update_fields(num_nodes), IP(myIP), port(myport) 
     {//fill in nodes array}; 
}; 

I는 UDP를 통해 update packet의 인스턴스를 전송할 reinterpret_cast를 사용하고, 다음 컴파일 (update_packetstruct node의 포인터 배열을 포함) 및 적절한 목적지로 보낸다. 나는 그것을 받아

struct update_packet update_msg = 
    reinterpret_cast<struct update_packet>(recved_msg); 

하여 디코딩 할 때

int update_packet_size = sizeof(up); 
sendto(s, reinterpret_cast<const char*>(&up), update_packet_size, 0, 
     (struct sockaddr *)&dest_addr, sizeof(dest_addr)); 

는 그러나, 나는

In function ‘int main(int, char**)’: 
error: invalid cast from type ‘char*’ to type ‘update_packet’ 
      struct update_packet update_msg = 
      reinterpret_cast<struct update_packet>(recved_msg); 

왜이 오류가 발생 오류가 발생, 나는이 문제를 어떻게 해결할 수 있습니까?

또한 소켓을 통해 struct의 인스턴스에서 데이터를 교환하는 올바른 방법입니까? 그렇지 않다면 어떻게해야합니까? http://beej.us/guide/bgnet/examples/pack2.c과 같이 pack() ing 기능이 필요합니까?

+0

C++의'struct' 타입은 독립적으로 참조 할 수 있습니다.'struct' 한정자가 필요 없습니다. 즉,'struct update_packet' 대신'update_packet'을 쓰면됩니다. – legends2k

답변

5

일반적

캐스트 문제는 제대로 다른 질문에 답하고있다.

  • 포장 :

    그러나, 당신은 다음과 같은 여러 가지 이유로,/전송 네트워크를 통해 구조체를 수신 포인터 캐스트에 의존해서는 안 컴파일러는 구조체 변수를 정렬하고 패딩 바이트를 삽입 할 수 있습니다. 이것은 컴파일러에 의존하므로 코드를 이식 할 수 없습니다. 두 통신 기계가 서로 다른 컴파일러로 컴파일 된 프로그램을 실행하면 작동하지 않을 가능성이 높습니다.

  • endianness : 같은 이유로 여러 컴퓨터에서 멀티 바이트 숫자 (예 : int)를 보낼 때의 바이트 순서가 다를 수 있습니다.

누군가가 컴파일러, 플랫폼 등을 변경하면 많은 시간이 걸릴 수 있지만 몇 년 후에는 문제가 될 수있는 코드가 생성됩니다. 교육 프로젝트는 올바른 방법을 시도해야합니다 ...

이런 이유로 네트워크를 통해 전송하거나 파일에 쓰려면 구조체의 데이터를 char 배열로 변환해야합니다. 가능한 경우 엔디안을 고려해야합니다. 이 프로세스를 "직렬화"라고합니다. 세부

직렬화

직렬화는 네트워크를 통해 전송 될 수있는 바이트의 배열로 데이터 구조를 변환하는 수단.

직렬화 된 형식은 반드시 2 진수가 아닙니다. text 또는 xml은 가능한 옵션입니다. 데이터 양이 적다면 텍스트가 가장 좋은 해결책 일 수 있으며 문자열 스트림 (std :: istringstream 및 std :: ostringstream) 만 사용하여 STL에 의존 할 수 있습니다.

바이너리로 직렬화하기위한 몇 가지 좋은 라이브러리가 있습니다. Boost :: serialization 또는 Qt in Qt. 당신은 그것을 너 자신도 할 수

간단한 직렬화 귀하의 경우에는 STL

를 사용하여 텍스트 "직렬화 C++"에 대한 SO 보면, 당신은 같은 것을 사용하여 텍스트 문자열로 직렬화 수 있습니다

std::string str(data_received, num_bytes_received); 
std::istringstream(str); 


update_packet up; 
iss >> up.port; 
iss >> up.IP; 
iss >> up.num_update_fields; 
//maximum number of nodes should be checked here before doing memory allocation! 
up.nodes = (nodes*)malloc(sizeof(node)*up.num_update_fields); 
for(unsigned int i=0;i<up.num_update_fields;i++) 
{ 
    iss >> up.nodes[i].IP; 
    iss >> up.nodes[i].port; 
    iss >> up.nodes[i].nil; 
    iss >> up.nodes[i].server_id; 
    iss >> up.nodes[i].cost; 
} 

이 10이 될 것이다 :

std::ostringstream oss; 

oss << up.port; 
oss << up.IP; 
oss << up.num_update_fields; 
for(unsigned int i=0;i<up.num_update_fields;i++) 
{ 
    oss << up.nodes[i].IP; 
    oss << up.nodes[i].port; 
    oss << up.nodes[i].nil; 
    oss << up.nodes[i].server_id; 
    oss << up.nodes[i].cost; 
} 

std::string str = oss.str(); 

char * data_to_send = str.data(); 
unsigned int num_bytes_to_send = str.size(); 

및 역 직렬화에 대한 는 수신 된 데이터를 0 % 휴대용 및 안전. iss 오류 플래그를 검사하여 데이터 유효성을 검증 할 수 있습니다.

또한 당신은 안전을 위해 수 있습니다

  • 는 표준 : : 벡터 대신 노드 포인터를 사용합니다. 이것은 메모리 누수 및 기타 문제를 방지합니다
  • iss >> up.num_update_fields;의 노드 수를 확인하십시오. 너무 크면 프로그램을 크래시하고 시스템을 손상시키는 큰 버퍼를 할당하기 전에 디코딩을 중단하십시오. 네트워크 공격은 이와 같은 "구멍"을 기반으로합니다. 이러한 종류의 검사가 수행되지 않으면 서버가 RAM보다 100 배 큰 버퍼를 할당하도록하여 서버를 중단시킬 수 있습니다. 네트워킹 API는 표준 : iostream 인터페이스를 가지고 있다면 당신은 공백으로 구분 텍스트를 사용하여 생각
  • , 당신은 그것에서, 중간 문자열을 사용하지 않고 직접 < < 및 >> 연산자를 사용하고 stringstream을 할 수있다하는 것은 낭비 대역폭. 노드 수가 많고 대역폭 사용을 무시할 수 없게 만드는 경우에만 이것을 생각하십시오. 이 경우 바이너리로 직렬화해야합니다. 그러나 텍스트 솔루션이 완벽하게 작동하면 (조기 최적화를 조심하십시오!)

간단한 바이너리 직렬화 (하지 바이트 순서)/엔디 언 인식 :

교체 :

oss.write << up.port; 

작성자 :

oss.write((const char *)&up.port, sizeof(up.port)); 

엔디안

그러나 프로젝트에서 Big-Endian이 필요합니다. PC (x86)에서 실행중인 경우 모든 필드에서 바이트를 변환해야합니다.

1) 첫 번째 옵션 : 손

const char * ptr = &up.port; 
unsigned int s = sizeof(up.port); 
for(unsigned int i=0; i<s; i++) 
    oss.put(ptr[s-1-i]); 

궁극적 인 코드 :이 할 어려운 일이 아니다 (엔디 언을 감지 - SO 그것을 볼) 및 직렬화 코드를 적용.

2) 두 번째 옵션 : 부스트 또는 Qt는

같은 라이브러리를 사용이 라이브러리는 출력 데이터의 엔디안을 선택할 수 있습니다. 그런 다음 플랫폼 엔디안을 자동 감지하고 자동으로 작업을 수행합니다.

+0

이 프로젝트의 요구 사항은 업데이트 패킷이 고정 크기 (헤더의 경우 32 * 2 비트, 각 노드의 경우 32 * 3)입니다. XML은 분명히 더 많은 비트를 추가하므로 사용할 수 없습니다. 또한,이 경우에는 직렬화의 출력이 같은 크기의 이진이어야한다고 생각합니다. 이것이 어떻게 이루어 졌는지 궁금합니다. 예를 들어, 10 진수 값을 갖는 16 비트 정수, 예를 들어 500은'uint16_t'에서 00000001 00101100입니다. 이것은 1 문자가 8 비트를 취할 때 16 비트를 취하는 문자열로 어떻게 변환됩니까? – user2418202

+0

음. 신경 쓰지 마. 나는 아직도 프로젝트 명세에 대해 명확하지 않다고 생각한다. – user2418202

+0

그런데'struct node' 노드 배열에 대해서는 어떻게해야합니까? 배열은 주소에 대한 포인터이고 다른 컴퓨터에 대한 포인터를 전송하는 것은 의미가 없으므로 배열에'node's에 대한 데이터가 들어있는 char *을 만들어야하며 serialization이이를 수행하는 방법입니까? – user2418202

2

구조체에 포인터를 캐스트 할 수는 없지만 구조체에 대한 포인터에 포인터를 캐스트 할 수는 있습니다.

변경

struct update_packet update_msg = 
     reinterpret_cast<struct update_packet>(recved_msg); 

update_packet * update_msg = 
     reinterpret_cast<update_packet *>(recved_msg); 

에 그리고 그래, 당신은 필요 이상 pack() 때문에 송신 측에 컴파일러가 다른 패딩을 추가 할 수 있습니다. 그러나 그것은 100 % 안전하지 않습니다. 송수신 기계의 엔디안이 다르다는 점도 고려해야합니다. 적절한 직렬화 메커니즘을 살펴 보는 것이 좋습니다.

+0

패킹은 컴파일러에 종속적이며 이식 가능하지 않습니다. 직렬화에 의존하는 것은 더 많은 코드가 필요함에도 불구하고 char 배열로 적절하게 직렬화하는 게으른 것입니다. 더 나은 제어를 제공하고 엔디안 문제를 해결하며 훨씬 더 휴대 가능하고 안전합니다. – galinette

+0

휴대 성은 단지 학교 프로젝트 일 뿐이며 모든 필드는 빅 엔디안이어야하기 때문에 문제가되지 않습니다. 귀하의 솔루션은'update_packet'의 int 필드에서 작동하지만, 한가지 더 질문 할 수 있습니까? 나는'update_packet' 안에'struct node'의 포인터 배열을 가지고 있습니다. 나는이 배열의 요소를 update_msg-> nodes [0] -> cost,'update_msg-> nodes-> cost' 등으로 참조하려했으나 실패했다. 메시지에서 포인터 배열을 사용하는 것이 문제가 될 수 있다고 생각했지만 정확히 무엇이 문제인지 아십니까? – user2418202

+0

음, 나는 그 반대라고 말할 것입니다 ... 교육을위한 것이라면, 믿을만한 것들을하는 깨끗한 방법을 배우십시오. 우주 발사를위한 것이라면 깨끗하고 신뢰할 수있는 코드를 사용하십시오. 화장지 공장 엔지니어링 개발의 경우 OS가 변경된 후 10 년 내에 코드를 디버깅하는 것을 피하기 위해 깨끗하고 안정적인 코드를 사용하십시오. – galinette

2

당신은 또한 사용할 수 있습니다 :

struct update_packet update_msg; 

memcpy(&update_msg, recved_msg, size-of-message); 

당신은 그러나 size-of-message 당신이 찾고있는 정확히 무엇인지 확인해야합니다.

+0

이것은 자주 인용되고 끔찍하게 정의되지 않은 어떤'char *'를'reinterpret_cast '하려고 시도하는 것과는 달리 (필연적으로 복사 가능한) 목적지가 제대로 정렬되고 살아 있는지를 보장하기 때문에 ** 이런 식으로하는 ** 방법입니다 ** 객체 모델 측면에서 존재하지 않는 정렬되지 않은 가상 객체. 게다가 네이티브 플랫폼과 컴파일러가이를 지원한다면, _as-if_ 규칙은 완전히 합법적으로'struct' 인스턴스를 최적화 할 수 있고 동일한 해킹, 정렬되지 않은 캐스팅 _anyway_에 액세스 할 수 있음을 의미합니다. 그래서 –

관련 문제