Reputation: 21
How to construct a request message with a given message specification, and then send to server thought c socket? Binary protocol is employed for Client and Server communication. Are the following approaches correct?
Given message specification:
Field Fomat Length values ------------ ------ ------ -------- requesID Uint16 2 20 requestNum Uint16 2 100 requestTitle String 10 data sring
/************** approach 1 ****************/ typedef unsigned short uint16; typedef struct { uint16 requesID [2]; uint16 requestNum [2]; unsigned char requestTitle [10]; }requesMsg; … requesMsg rqMsg; memcpy(rqMsg.requesID, "\x0\x14", 2); //20 memcpy(rqMsg.requesNum, "\x0\x64", 2); //100 memcpy(rqMsg.requesTitle, "title01 ", 10); … send(sockfd, &rqMsg, sizeof(rqMsg), 0); /************** approach 2 ****************/ unsigned char rqMsg[14]; memset(rqMsg, 0, 14); memcpy(rqMsg, "\x0\x14", 2); memcpy(rqMsg+2, "\x0\x64", 2); memcpy(rqMsg+4, "title01 ", 10); … send(sock, &rqMsg, sizeof(rqMsg), 0);
Upvotes: 2
Views: 676
Reputation: 80
Both of them are at least partially correct but I much prefer the first one because it allows for quick and natural data manipulations and access and leaves less space for errors compared to manual indexing. As a bonus you can even copy and assign structure values as a whole and in C it works as expected.
But for any outgoing data you should make sure to use a "packed" struct. Not only it will reduce the amount of data transmitted down to the array-based implementation figure but it will also make sure that the fields alignments are the same in all the programs involved. For most C compilers I tried (GCC included) it can be done with __attribute__((__packed__))
attribute, but there are different compilers that require different attributes or even a different keyword.
Also endianness control may be required if your application is going to run on different architectures (ARM clients vs x86_64 server is a major example). I just use some simple macros like these to preprocess each field individually before doing any calculations or data output:
#define BYTE_SWAP16(num) ( ((num & 0xFF) << 8) | ((num >> 8) & 0xFF) )
#define BYTE_SWAP32(num) ( ((num>>24)&0xff) | ((num<<8)&0xff0000) | ((num>>8)&0xff00) | ((num<<24)&0xff000000) )
But you can use different approaches like BCD encoding, separate decoding functions or something else.
Also notice that uint16_t is already a 2-byte value. You probably don't need two of them to store your single values.
Upvotes: 0
Reputation: 3211
I'm afraid you are misunderstanding something: The length column appears to tell you the length in bytes, so if you receive a uint16 you receive 2 bytes.
Your first approach could lead to serious problem through data structure alignment. If I were in your shoes I'd prefer the second approach and fill in the bytes on my own into a byte array.
A general note about filling fields here: I'ts useless to use memcpy for "native" fields like uint16, etc. It might work but is simply a waste of runtime. You can fill in fields of a struct simply assigning them a value like rqMsg.requesID = 20;
Another issue is the question of byte order or endianness of your binary protocol.
As a whole package, I'd implement a "serializeRequest" function taking fields of your struct and convert it into a byte array according to the protocol.
Upvotes: 3