Professional Documents
Culture Documents
The first problem is the endianness. The endianness is the order in which a
particular processor interprets the bytes of primitive types that occupy more
than a single byte (integers and floating point numbers). There are two main
families: "big endian" processors, which store the most significant byte first,
and "little endian" processors, which store the least significant byte first. There
are other, more exotic byte orders, but you'll probably never have to deal with
them.
The problem is obvious: If you send a variable between two computers whose
endianness doesn't match, they won't see the same value. For example, the
16-bit integer "42" in big endian notation is 00000000 00101010, but if you
send this to a little endian machine, it will be interpreted as "10752".
The second problem is the size of primitive types. The C++ standard doesn't
set the size of primitive types (char, short, int, long, float, double), so, again,
there can be differences between processors -- and there are. For example,
the long int type can be a 32-bit type on some platforms, and a 64-bit type on
others.
The third problem is specific to how the TCP protocol works. Because it
doesn't preserve message boundaries, and can split or combine chunks of
data, receivers must properly reconstruct incoming messages before
interpreting them. Otherwise bad things might happen, like reading incomplete
variables, or ignoring useful bytes.
You may of course face other problems with network programming, but these
are the lowest-level ones, that almost everybody will have to solve. This is the
reason why SFML provides some simple tools to avoid them.
1
Since primitive types cannot be exchanged reliably on a network, the solution
is simple: don't use them. SFML provides fixed-size types for data
exchange: sf::Int8, sf::Uint16, sf::Int32, etc. These types are just typedefs
to primitive types, but they are mapped to the type which has the expected
size according to the platform. So they can (and must!) be used safely when
you want to exchange data between two computers.
Packets
The two other problems (endianness and message boundaries) are solved by
using a specific class to pack your data: sf::Packet. As a bonus, it provides a
much nicer interface than plain old byte arrays.
// on sending side
sf::Uint16 x = 10;
double d = 0.6;
sf::Packet packet;
// on receiving side
sf::Uint16 x;
std ::string s;
double d;
2
Unlike writing, reading from a packet can fail if you try to extract more bytes
than the packet contains. If a reading operation fails, the packet error flag is
set. To check the error flag of a packet, you can test it like a boolean (the
same way you do with standard streams):
if (packet >> x)
// ok
else
tcpSocket.send(packet);
tcpSocket.receive(packet);
Packets solve the "message boundaries" problem, which means that when
you send a packet on a TCP socket, you receive the exact same packet on
the other end, it cannot contain less bytes, or bytes from the next packet that
you send. However, it has a slight drawback: To preserve message
boundaries, sf::Packet has to send some extra bytes along with your data,
which implies that you can only receive them with a sf::Packet if you want
them to be properly decoded. Simply put, you can't send an SFML packet to a
non-SFML packet recipient, it has to use an SFML packet for receiving too.
3
Note that this applies to TCP only, UDP is fine since the protocol itself
preserves message boundaries.
struct Character
sf::Uint8 age;
std::string name;
float weight;
};
Both operators return a reference to the packet: This allows chaining insertion
and extraction of data.
4
Character bob;
Custom packets
Packets provide nice features on top of your raw data, but what if you want to
add your own features such as automatically compressing or encrypting the
data? This can easily be done by deriving from sf::Packet and overriding the
following functions:
These functions provide direct access to the data, so that you can transform
them according to your needs.
function, of course :)
5
std::size_t dstSize;
append(dstData, dstSize);
};
Such a packet class can be used exactly like sf::Packet. All your operator
overloads will apply to them as well.
ZipPacket packet;
socket.send(packet);