简体   繁体   中英

Can we typecast buffer into C++ structure on client when server is sending data as c structure?

I have server, client processes written in C named as NetworkServer.c and NetworkClient.c and these 2 are communicating using linux sockets. When client sends a request as below to get ethernet statistics,

// rxbuf - character array of 128K
// ETHERNET_DIAGNOSTIC_INFO - structure typedefed
recv(sockfd, rxbuf, sizeof(ETHERNET_DIAGNOSTIC_INFO), 0)

server fills the data in to rxbuf (as ETHERNET_DIAGNOSTIC_INFO because server also uses the same copy of header file where this structure is defined) and sends the data. Once client receives, it will typecast as below to get the data.

ETHERNET_DIAGNOSTIC_INFO *info = (ETHERNET_DIAGNOSTIC_INFO *) rxbuf;

the structure is defined in NetworkDiag.h as below.

#ifdef __cplusplus
extern "C" {
#endif

typedef struct ETHERNET_DIAGNOSTIC_INFO
{
    uint32_t             cmdId; 
    unsigned long        RxCount[MAX_SAMPLES];
    unsigned long        TxCount[MAX_SAMPLES];
    time_t               TimeStamp[MAX_SAMPLES] ;
    char                 LanIpAddress[20];
    char                 LanMacAddress[20];
    char                 WanIpAddress[20];
    char                 LanDefaultGateway[20];
    char                 LanSubnetMask[20];
    char                 LanLease[5000];
}ETHERNET_DIAGNOSTIC_INFO;

This is working fine.

Now there is a requirement that I need to create a c++ file which should work as client (I removed client C file and server should remain as c file). I defined header file for the structure definition as below.

struct ETHERNET_DIAGNOSTIC_INFO
{
    uint32_t             cmdId; 
    unsigned long        RxCount[MAX_SAMPLES];
    unsigned long        TxCount[MAX_SAMPLES];
    time_t               TimeStamp[MAX_SAMPLES] ;
    char                 LanIpAddress[20];
    char                 LanMacAddress[20];
    char                 WanIpAddress[20];
    char                 LanDefaultGateway[20];
    char                 LanSubnetMask[20];
    char                 LanLease[5000];
};

basically I removed the C++ guard and typedef and using the below code in client.cpp file to get the result from server.

if(recv(sockfd, rxbuf, sizeof(ETHERNET_DIAGNOSTIC_INFO), 0) > 0)
{
    ETHERNET_DIAGNOSTIC_INFO *info = reinterpret_cast<ETHERNET_DIAGNOSTIC_INFO *> (rxbuf);
}

I am not getting the correct results. The values in the structure are misplaced (some values are correct but lot of values are misplaced). I tried 'C' type casting also but no use.

I doubt that we can not typecast buffer into C++ structure on client when server is sending data as c structure. Is it correct? Can any one please let me know how to solve this issue?

There are multiple problems with this approach:

  1. Endianness might be different between server and client machine

    You then need to deserealize numbers and time_t 's.

  2. Structure packing might be different between code compiled on server (c++) and on client (C)

    You then need to use a protocol to send data, like binary ASN, protobuf or many others.

  3. if(recv(sockfd, rxbuf, sizeof(ETHERNET_DIAGNOSTIC_INFO), 0) > 0) there is no guarantee recv will read exactly sizeof(ETHERNET_DIAGNOSTIC_INFO) bytes.

    You need to wrap this into while loop ( code is sample and might be non-compilable ):

.

int left = sizeof(ETHERNET_DIAGNOSTIC_INFO);
char *ptr = rxbuf;
int rd;

while(left>0)
{
    rd=recv(sockfd, ptr, left, 0);
    if(rd==0)
    {
        if(left>0) return SOCKET_CLOSED_PREMATURELY;
        else return SOCKET_DONE;
    } else if(rd==-1 && errno==EAGAIN) {
        //do again
        continue; 
    } else if(rd==-1 && errno!=EAGAIN) {
       return SOCKET_ERROR;
    }
    left = left - rd;
    ptr=ptr+rd;
}

The proper way to send binary data is to use protobuf or apache thrift, or ASN or invent something yourself.

You can probably do it but are likely to run into serious significant issues in trying:

  • Different compilers and compiler settings will pack and align structures differently in order to optimise for the particular processor architecture. There is absolutely no guarantee that the members of a structure will lay out exactly next to each other unless you play with pragmas.
  • Different processors will use different byte orders for things like integers and floating point values. If you are going to exchange data between a client and server (or vice versa) it behooves you to explicitly define the byte order and then make both sides conform to that definition regardless of the native order.
  • Values like unsigned long will have different sizes based upon the processor architecture targeted by the compiler. In order to reliably exchange data, you will need to explicitly define the size of the values that will be transferred.

For these reasons, I prefer to write functions (or methods) that will explicitly pack and unpack messages as they are exchanged. By doing so, you will be subjected to much fewer seemingly mysterious errors.

A number of possible explanations spring to mind:

  • Different packing of ETHERNET_DIAGNOSTIC_INFO between a C struct and a C++ struct .
  • (less likely) Different alignments of rxbuf (you don't show where this pointer comes from). There are no guarantees in C or C++ that reading a int or long that does not lie on natural boundary (eg 4-byte aligned) yields correct results.
  • That your C and C++ compilers are compiling against different ABIs (eg 32-bit and 64-bit respectively). Note that sizeof(time_t) == 4 on a 32-bit platform and 8 on many 64-bit platforms.

All of these issues point in the same direction: Mapping a struct onto a wire data layout like this is really non-portable and problematic.

If you really insist on doing it you'll need to do the following:

  • Use #pragma pack directives (or better: if using a C++11 compiler __attribute__ ((__packed__))) . Even then, you can get surprises.
  • Decide which byte-ordering you intend using and byte-swap all multi-byte values with htons() and friends. The convention is for multi-byte quantities to be big-endian over TCP/IP.
  • Ensure the buffer you call recv() with is aligned - probably to a 4-byte boundary.

A more robust approach is to read the input buffer as a stream of bytes, reconstructing any multi-byte fields as required.

Yes, you can as the buffer is just the byte representation of the struct sent by the other side. After you have handled the byte order, you can just cast the buffer pointer to a pointer of the type of your struct.

In C++ you can write for example ETHERNET_DIAGNOSTIC_INFO* NewPtr = reinterpret_cast<ETHERNET_DIAGNOSTIC_INFO*>(buffer); This will do what you want unless you run an older C++ compiler not capable of understanding C++11 syntax. However, depending on your compiler the error might arrive from padding the data.

If you define bit fields and pack the struct on both sides you will be fine though. Ask if you need help, but google is your friend.

I doubt that we can not typecast buffer into C++ structure on client when server is sending data as c structure. Is it correct?

EDIT: You can cast any binary data generated by any programming language into a readable piece of code in your program. After all, it is all about bits and bytes. So you can cast any data from any program to any data in any other program. Could you quickly print the sizeof(ETHERNET_DIAGNOSTIC_INFO) on both sides and see if they match?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM