tags:

views:

1176

answers:

7

Hi again!

I have a client and server program where I want to send an entire struct from the client and then output the struct member "ID" on the server.

I have done all the connecting etc and already managed to send a string through:

send(socket, string, string_size, 0);

So, is it possible to send a struct instead of a string through send()? Can I just replace my buffer on the server to be an empty struct of the same type and go?

A: 

Yep, structs of the same type are all of the same size. If you get your pointer casting right you're good to go.

Carl Smotricz
Goodie! Could you please provide a small and simple example of how to approach this?
Orolin
Don't really think so... structs of the same type are not of the same size, because it depends on the padding applied to the struct members. Also, you have problems with architecture (32/64) and endianness.
Stefano Borini
Carl Smotricz
The result from read(socket, struct_buffer, sizeof(struct_buffer); returns -1 when I test this. Am I not receiving this correctly?
Orolin
A parenthesis is missing in the above comment, I know. Not in the source code tho..
Orolin
I'm sorry I don't have an environment handy to test, but some compilers will try to pass the struct by value if you don't put an ampersand on it! Still, this should not have caused a -1. Can you check `perror()` or something to get a better idea of what's wrong?
Carl Smotricz
+3  A: 

Are the client and server machines "the same"? What you are proposing will only work if the the C compilers at each end lay out the structure in memory exactly the same. There are lots of reasons why this may not be the case. For example the client and server macines might have different architectures, then the way they represent numbers in memory (big-endian, little-endian) might differ. Even if the clients machines and server machine have the same architecture two different C compilers may have different policies for how they lay out structs in memory (eg. padding between fields to align ints on word boundaries). Even the same conmpiler with different flags might give different results.

Pragmatically, I'm guessing that your client and server are the same kind of machine and so what you are proposing will work, however you need to be aware that as a general rule it won't and that's why standards such as CORBA were invented, or why folks use some general representation such as XML.

djna
Yes, the client and server machine are the same so this will not be a problem. Thanks for the tip tho:)
Orolin
+1  A: 

You can, if the client and the server have laid out the struct exactly the same way, meaning that the fields are all the same size, with the same padding. For instance if you have a long in your struct, that might be 32bits on one machine and 64bits on the other, in which case the struct will not be received correctly.

In your case, if the client and the server are always going to be on very similar C implementations (for instance if this is just code you're using to learn some basic concepts, or if for some other reason you know your code will only ever have to run on your current version of OSX), then you can probably get away with it. Just remember that your code will not necessarily work properly on other platforms, and that there's more work to do before it's suitable for use in most real-world situations.

For most client-server applications, this means that the answer is you can't do it in general. What you actually do is define the message in terms of the number of bytes sent, what order, what they mean, and so on. Then at each end, you do something platform-specific to ensure that the struct you're using has exactly the required layout. Even so, you might have to do some byte-swapping if you're sending integer members of the struct little-endian, and then you want your code to run on a big-endian machine. Data interchange formats like XML, json and Google's protocol buffers exist so that you don't have to do this fiddly stuff.

[Edit: also remember of course that some struct members can never be sent over the wire. For instance if your struct has a pointer in it, then the address refers to memory on the sending machine, and is useless on the receiving end. Apologies if this is already obvious to you, but it certainly isn't obvious to everyone when they're just beginning with C].

Steve Jessop
Technically, the struct will always be received correctly assuming his protocol over the connection works correctly. The problem is not the receiving, it is the interpreting or perhaps you might call it the accessing.
jmucchiello
True. I'd say if you don't actually read all the bytes into application space (because `sizeof(thestruct)` is smaller on the reader than the writer), then you haven't received the message. Also, I think technically it is sometimes undefined behavior to copy arbitrary bytes over a struct - you could trigger trap bits in the members. So in that case too the message is not received. But you're right, you'll receive (some of) the bytes of the message, just not understand the message itself.
Steve Jessop
+1  A: 

Welll... sending structs through the network is kinda hard if you are doing it properly.

Carl is right - you can send a struct through a network by saying:

send(socket, (char*)my_struct, sizeof(my_struct), 0);

But here's the thing:

  • sizeof(my_struct) might change between the client and server. Compilers often do some amount of padding and alignment, so unless you define alignment explicitly (maybe using a #pragma pack()), that size may be different.
  • The other problem tends to be byte-order. Some machines are big-endian and others are little-endian, so the arrangement of the bytes might be different. In reality, unless your server or client is running on non-Intel hardware (which is probably not the case) then this problem exists more in theory than in practice.

So the solution people often propose is to have a routine that serializes the struct. That is, it sends the struct one data member at a time, ensuring that the client and server only send() and recv() the exact specified number of bytes that you code into your program.

rascher
Not that it matters very much, but you should probably cast the structure to a void*, rather than a char *, since that's what send() is prototyped to take.
Mark Bessey
@rasher: While you mention both packing the struct and serializing, it's important to point out that serializing can murder performance if you're sending a high volume of data. Every call to send causes a fairly expensive context switch between user space and kernel space. Packing the data is really the preferred method.
Robert S. Barnes
+1  A: 

You can, but you need to be aware of 2 important things.

  1. The programs on both ends have to be ABI compatible. They usually are if both ends run on the same processor architecture, same OS, compiled with the same compiler and compiler flags.
  2. TCP is a stream. You have to make sure you are sending the whole struct. Look at the documentation for the send() call - it returns the nr of bytes sent - which might be less than you told it to. Same on the receiver side. Just because you sent the struct with 1 send call, does not mean you will receive it with 1 recv call. It make take several recv call to get all the pieces.
nos
A: 

In general, it's a bad idea to do this, even if your client and your server turn out to lay out the structure in memory the same way.

Even if you don't intend to pass more complex data structures (that would involve pointers) back and forth, I recommend that you serialize your data before you send it over the network.

hillu
Serializing the data is the worst possible solution as it kills system performance by causing expensive unnecessary context switches between user and kernel space to copy the data into the kernel buffers. Either arrange the members in the `struct` to guarantee there is no padding or use the pack pragma.
Robert S. Barnes
Robert S. Barnes: I don't even know how to begin an answer to such nonsensical overgeneralization. Let's just ignore the relevance for the original question for a moment and think about how large the amounts of data would have to be for copies between user space and the kernel to make a measurable difference. On the other hand, let's not think about it at all.
hillu
@hillu: Maybe I should rephrase: Serializing should be your last resort as each call to send causes an extremely expensive context switch. For example: http://www.linfo.org/context_switch.html "Context switching is generally computationally intensive. That is, it requires considerable processor time, which can be on the order of nanoseconds for each of the tens or hundreds of switches per second. Thus, context switching represents a substantial cost to the system in terms of CPU time and can, in fact, be the most costly operation on an operating system." Look up "cost of context switching"
Robert S. Barnes
Robert S. Barnes: That's a view I can live with. I still don't see what serialization would necessarily have to do with the overhead imposed by context switches. (The meaning of a context switch was quite clear to me.)The most important performance problem I see is that for (de)serialization, data has to be copied around in memory. And that is only relevant in high-throughput situations. I see optimizing for those without any actual need as premature optimization (and thus the root of all evil).
hillu
A: 

Send the data as text file and then decode it after receiving it. This is the best way if you want your data as its sent!!