0

Hi i have a client program and a server program. I created a struct like this

struct player
{
    Int x,y;
    Int score;
    Std::string name;
}

I created the object with:

player p;

And i initialized it:

p.x = 100; p.y = 200; p.score = 281;
p.name = "DiCri";

I sent the object to the server.

SDLNet_TCP_Send(client, (char*)&p, sizeof(p));

All works fine. The server gets the object with data. The only problem is the name. Server says that x is 100, y is 200, score is 281, and name is a sort of strange random symbols. I don't know why. Help. How to fix that? This happens also if that was a char* and not a string.

Thanks

EDIT1: I found a question similar to mine: Serialization of an object. And the user who asked this question wants to send this object over the network too. I'll try to follow the answers

EDIT2: using char name[100] it works.

EDIT3: Thanks now all works fine! Sorry for bad english because i'm italian!

manudicri
  • 176
  • 14
  • You have UB because player is not a POD type. You need can't serialize your structure this way. Think of this sizeof(p) is known at compile time. Regardless of how many characters are in your string the size does not change. – drescherjm Jun 30 '17 at 20:36
  • What's the meaning of UB and POD? So how i can serialize? – manudicri Jun 30 '17 at 20:39
  • Undefined Behavior. Pain Old Data. You will find thousands of hits in google for each. – drescherjm Jun 30 '17 at 20:39
  • So what should i do? – manudicri Jun 30 '17 at 20:39
  • https://stackoverflow.com/questions/146452/what-are-pod-types-in-c – drescherjm Jun 30 '17 at 20:40
  • So i should use constructors and destructors? – manudicri Jun 30 '17 at 20:44
  • That would make it not a POD also. – drescherjm Jun 30 '17 at 20:45
  • Manu, think of it like this. std::string is a smart wrapper around a pointer to your data. If you `write` the `string`, what goes into the IO stream is the pointer, not the data at the pointer. The pointer is meaningless at the other side because if there is anything at the address, you can bet it's not the data you want. Worse, the `string` doesn't own it, so when the `string` destructor tries to free the pointer badness will occur. – user4581301 Jun 30 '17 at 21:13

2 Answers2

2

Player contains 3 ints and a std::string This ints can easily be written to a stream with write (watch out for the differing byte orders, endian, used by processors) , but the string is too complex an object. Option 1 is to replace the string with a fixed size char array to simplify the structure, but this adds more pain than it's worth. The char array can easily be overflowed, and always writes the full size of the array whether you used it or not.

The better approach is to establish a communication protocol and serialize the data.

First establish the endian to be used by the protocol so that both sides know exactly which byte order is used. Traditionally Big Endian is used for network communications, but there are fewer and fewer big endian devices, so this is increasingly becoming a case of, "Your call."

Let's stick with big endian because the tools for this are ancient, well known, and well established. If you are using a socket library odds are very good you have access to tools to perform the operations required.

Next, explicitly size the integer data types. Different implementations of C++ may have different sizes for fundamental types. Solution for this one is simple: Don't use the fundamental types. Instead, use the fixed width integers defined in in cstdint

Right now our structure looks something like

struct player
{
    int x,y;
    int score;
    std::string name;
}

It needs to look more like

struct player
{
    int32_t x,y;
    int32_t score;
    std::string name;
}

Now there are no surprises about the size of an integer. The code either compiles or does not support int32_t. Stop laughing. There are still 8 bit micro controllers out there.

Now that we know what the integers look like, we can make a pair of functions to handle reading and writing:

bool sendInt32(int32_t val)
{
    int32_t temp = htonl(val);
    int result = SDLNet_TCP_Send(client, (char*)&temp, sizeof(temp));
    return result == sizeof(temp); // this is simplified. Could do a lot more here
}

bool readInt32(int32_t & val)
{
    int32_t temp;

    if (readuntil(client, (char*)&temp, sizeof(temp)))
    {
        val = ntohl(temp);
        return true;
    }
    return false;
}

Where readuntil is a function that keeps reading data from the socket until the socket either fails or all of the requested data has been read. Do NOT assume that all of the data you wanted will arrive all to the same time. Sometimes you have to wait. Even if you wrote it all in one chunk it won't necessarily arrive all in one chunk. Dealing with this is another question, but it gets asked just about weekly so you should be able to find a good answer with a bit of searching.

Now for the string. You go the C route and send a null terminated string, but I find this makes the reader much more complicated than it needs to be. Instead I prefix the string data with the length so all the reader has to do is read the length and then read length bytes.

Basically this:

bool sendstring(const std::string & str)
{
    if (sendInt32(str.size()))
    {
        int result = SDLNet_TCP_Send(client, str.c_str(), str.size());
        return result == str.size(); 
    }
    return false;
}

bool readstring(std::string & str)
{
    int32 len;
    if (readInt32(len))
    {
        str.resize(len);
        return readuntil(client, str.data(), len) == len;
    }
    return false;
}

All together, a writer looks something like

bool writePlayer(const Player & p)
{
    return sendInt32(p.x) && 
           sendInt32(p.y) && 
           sendInt32(p.score) &&
           sendString(p.name);
}

and a reader

bool readPlayer(Player & p)
{
    return readInt32(p.x) && 
           readInt32(p.y) && 
           readInt32(p.score) &&
           readString(p.name);
}
user4581301
  • 33,082
  • 7
  • 33
  • 54
-1

First of all, Std::string should be replaced by array of char. You also aware about network repsentation of Int on different OS. htons() and ntohs must be used for sending a 16bits integer over Networking.

Dang Huynh
  • 102
  • 3
  • I always use Int.. i don't know what's the difference between other type of ints.. and yes. I tried replacing std::string name; with char name[100] and the error is the same – manudicri Jun 30 '17 at 20:42
  • Yes i look noob but i'm not.. i am only 15 and started learning programming language (pascal) january 2016 – manudicri Jun 30 '17 at 20:48
  • If you use char name[100], you must use strcpy(p.name, "DiCri"); – Dang Huynh Jun 30 '17 at 20:58
  • "First of all, Std::string should be replaced by array of char." I recommend against that and offer instead defining a communication protocol that knows how to deal with a std::string. – user4581301 Jun 30 '17 at 21:08
  • @user3545894 name is written by the user that uses the client program. But strcpy wants a const char* and i can't use cin >> for constant varianle – manudicri Jun 30 '17 at 21:17
  • @Manu12x In this case all the `const` means is `strcpy` will treat the given pointer as `const char *` and not alter whatever it points to, so `strcpy` will happily accept a pointer to `const` or non`const` . – user4581301 Jun 30 '17 at 21:19
  • So i use cin with a string containing username and in the strcpy i'll use "name.c_str ()".. yes? – manudicri Jun 30 '17 at 21:20
  • You can `string temp; cin>>temp; strcpy(p.name, temp.c_str());`, but this is still a bad solution. – user4581301 Jun 30 '17 at 21:23
  • I know you're new guy for C/C++ programing. So, I didn't explain so much about the different between C and C++ but if you have time, you should send a little time to learn C with data structures fisrt. – Dang Huynh Jun 30 '17 at 21:30
  • Also disagree with that. Learn C++ as C++. – user4581301 Jun 30 '17 at 21:32