1. Project Background
I need to receive the location information of the GPS device through UDP. The data package structure defined by the manufacturer is roughly as follows:
Packet header:
describe | Number of bytes |
---|---|
Command flag | 2 |
version number | 2 |
Data volume size | 4 |
Data volume:
describe | Field type | Data Length |
---|---|---|
Equipment Number | unsigned char | 10 |
Equipment type | unsigned char | 1 |
longitude | double | 8 |
latitude | doube | 8 |
Device Number: Less than 20 digits, zero before digits, one byte for each two digits
2. Initial design
Based on previous experience, I naturally first defined a structure:
typedef struct dataHeader { unsigned short Flag; unsigned short Ver; unsigned int Size; }Header; typedef struct dataLocation { unsigned char DeviceName[10]; unsigned char DeviceType; double Longitude; double Latitude; }Location; typedef struct Data { Header header; Location location; }GPSData;
Then there is a simple receiving program:
#include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sstream> #include "tUtil.h" int main(int argc, char *argv[]) { int ret; char* PORT="9302"; //Define udp sockets struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(atoi(PORT)); addr.sin_addr.s_addr = htonl(INADDR_ANY); int sock; if ( (sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("Socket init error;"); exit(1); } //Binding ports if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("Socket bind error;"); exit(1); } //Sender address struct sockaddr_in clientAddr; memset(&clientAddr,0,sizeof(clientAddr)); size_t n; socklen_t len = sizeof(clientAddr); //Declare Received Data Structures GPSData gpsLoc; char buff[sizeof(gpsLoc)]; memset(buff,0x00,sizeof(buff)); while (1) { n = recvfrom(sock, buff, sizeof(buff), 0, (struct sockaddr*)&clientAddr, &len); if (n > 0) { memcpy(&gpsLoc,buff,sizeof(gpsLoc)); //Print sender information printf("From address: %s port: %u \n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port)); //Processing received information //Print a number first. char id[20]=""; for(int i =0;i<10;i++) { char ts[2]=""; sprintf(ts,"%02x",gpsLoc.location.DeviceName[i]); sprintf(id,"%s%s",id,ts); } printf("GPS Info: DeviceID: %s\n",id);) } } return 0; }
Everything looks so good. Start testing!
3. Testing process
3.1 Screening data
When I received the first message: the programmer;
Grab a bag and look at it. Here comes a piece of data which is totally different from the structure above. Well, there are other formats of messages originally sent. At this point, I have to do a data filtering, so I changed to this:
if (n>0 && buff[1] == 0xcc) { ... }
Run again and try, wait for a long time, there is no one that meets the requirements, is there no news pushed over, and then grab a package for analysis, there are data, why not meet the judgment conditions?
Print buff[1] and find that it's not 0xcc, it's 0xffffffcc, which is different from what I imagined! Then I saw it. This blog In a nutshell, the printf() function's% x(X) output is in the hexadecimal format of the Int type, so the c variable of the char type is converted to the Int type, and the char type is signed. Look at the definition of other people's data interface above:
unsigned char DeviceName[10];
It suddenly dawned on me that my buff array was char type, and others sent unsigned char, so I changed the judgment condition to the following:
if (n>0 && (unsigned char)buff[1] == 0xcc) { ... }
Finally, I got the data. I printed out a series of device numbers. I'm very happy.
3.2 Structure Size
Next, the longitude and latitude are analyzed. It's easy to double the data.
//Change the code of the printed number above to this printf("GPS Info: DeviceID: %s, Longitude: %f Latitude: %f \n",id, gpsLoc.location.Longitude,gpsLoc.location.Latitude);
The result is that latitude and longitude are all 0. Just think about it at first. Normally, maybe no signal has been received. Wait a minute.
It's still zero, and occasionally there's a very long string of numbers...
My intuition tells me that the analysis is wrong. What is wrong? Or analyze the captured data packets.
The length of the data received is 35. I calculated that the size of the structure I defined should be 40. Obviously, the data sent by the other party is aligned according to 1 byte instead of the default byte alignment. It should be in order to save the amount of data sent.
Then you need to align by 1 byte and add pragma pack(1) at the top of the structure definition. Here's a reminder that after the structure definition is completed, you must cultivate pragma pack() to restore the default alignment mode, because it may affect the third-party library you use, for example, my project just used tinyxml to generate xml, just started. Without # pragma pack(), the resulting XML structure has been incorrectly generated.
Okay, so there should be no problem next.
3.3 Big and Small Ends, Network Byte Order
Facts have proved that there is no good, the phenomenon of output as above, there must be something wrong.
Here I want to say that the network byte order is used in other people's interfaces. In UDP/TCP/IP protocol, the network byte order is specified in the big-end mode. So I immediately checked whether the big-end mode or the small-end mode is used in my system. A piece of code verifies that the result is the small-end mode. Then what happens next is that the small-end mode is used in my system. It's clear. I have to convert the byte order.
So I changed the code for analyzing longitude and latitude to this:
typedef union cTod{ char a[8]; double f; }CTOD; //Processing received double data CTOD lonUnion,latUnion; memcpy(lonUnion.a,buff+19,8); memcpy(latUnion.a,buff+27,8); //Test System Size End and Size End union check { int i; char ch; } c; c.i = 1; if(c.ch == 1)//Small-end mode { for(int n =0;n<4;n++) { char tmp = lonUnion.a[n]; lonUnion.a[n] = lonUnion.a[7-n]; lonUnion.a[7-n] = tmp; tmp = latUnion.a[n]; latUnion.a[n] = latUnion.a[7-n]; latUnion.a[7-n] = tmp; } printf("*************Little endian Union result: Longitude: %f Latitude: %f **********\n", lonUnion.f,latUnion.f); } else if(c.ch == 0)//Large-end mode, consistent with network byte order { printf("*************Big endian Union result:, Longitude: %f Latitude: %f **********\n", lonUnion.f,latUnion.f); } printf("GPS Info: DeviceID: %s, Longitude: %f Latitude: %f \n",id, lonUnion.f,latUnion.f);
There's a consortium here, right. That's the thing I don't usually know what it's used for.
I use the nature of union to realize data memory sharing between the double type and char array. By the way, I use it to judge whether the byte order of this machine is large or small.
Save the received longitude and latitude data into the char array of union. For the small-end mode system, do reverse operation on the char array, so that the double data in union is the longitude and latitude I want.
4. Summary
After running for a while, I finally got to work properly and saw the familiar longitude and latitude data. I passed on the final code for interested students to see.
It's a long and tortuous way to learn C++.