mxwcore-legion/dep/g3dlite/include/G3D/NetworkDevice.h

744 lines
23 KiB
C++

/**
@file NetworkDevice.h
These classes abstract networking from the socket level to a
serialized messaging style that is more appropriate for games. The
performance has been tuned for sending many small messages. The
message protocol contains a header that prevents them from being used
with raw UDP/TCP (e.g. connecting to an HTTP server).
LightweightConduit and ReliableConduits have different interfaces
because they have different semantics. You would never want to
interchange them without rewriting the surrounding code.
NetworkDevice creates conduits because they need access to a global
log pointer and because I don't want non-reference counted conduits
being created.
Be careful with threads and reference counting. The reference
counters are not threadsafe, and are also not updated correctly if a
thread is explicitly killed. Since the conduits will be passed by
const XConduitRef& most of the time this doesn't appear as a major
problem. With non-blocking conduits, you should need few threads
anyway.
LightweightConduits preceed each message with a 4-byte host order
unsigned integer that is the message type. This does not appear in
the message serialization/deserialization.
ReliableConduits preceed each message with two 4-byte host order
unsigned integers. The first is the message type and the second
indicates the length of the rest of the data. The size does not
include the size of the header itself. The minimum message is 9
bytes:a 4-byte type, a 4-byte header equal to "1", and one byte of data.
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-11-22
@edited 2006-11-25
*/
#ifndef G3D_NETWORKDEVICE_H
#define G3D_NETWORKDEVICE_H
#include "G3D/platform.h"
#include "G3D/NetAddress.h"
#include <string>
#include <iostream>
#include "G3D/g3dmath.h"
#include "G3D/ReferenceCount.h"
#include "G3D/Array.h"
#include "G3D/BinaryOutput.h"
namespace G3D {
class TextOutput;
/** \deprecated */
class Conduit : public ReferenceCountedObject {
protected:
friend class NetworkDevice;
friend class NetListener;
uint64 mSent;
uint64 mReceived;
uint64 bSent;
uint64 bReceived;
SOCKET sock;
/**
Used for serialization. One per socket
to make this threadsafe.
*/
BinaryOutput binaryOutput;
Conduit();
public:
virtual ~Conduit();
uint64 bytesSent() const;
uint64 messagesSent() const;
uint64 bytesReceived() const;
uint64 messagesReceived() const;
/**
If true, receive will return true.
*/
virtual bool messageWaiting();
/**
Returns the type of the waiting message (i.e. the type supplied
with send). The return value is zero when there is no message
waiting.
One way to use this is to have a Table mapping message types to
pre-allocated subclasses so receiving looks like:
<PRE>
// My base class for messages.
class Message {
virtual void serialize(BinaryOutput&) const;
virtual void deserialize(BinaryInput&);
virtual void process() = 0;
};
Message* m = table[conduit->waitingMessageType()];
conduit->receive(m);
m->process();
</PRE>
Another is to simply switch on the message type:
<pre>
switch (conduit->waitingMessageType()) {
case 0:
// No message
break;
case ENTITY_SPAWN_MSG:
{
EntitySpawnMsg m;
condiut->receive(m);
spawnEntity(m.id, m.position, m.modelID);
}
break;
...
}
</pre>
*/
virtual uint32 waitingMessageType() = 0;
/** Returns true if the connection is ok. */
bool ok() const;
};
typedef shared_ptr<class ReliableConduit> ReliableConduitRef;
#ifdef __GNUC__
// Workaround for a known bug in gcc 4.x where htonl produces
// a spurrious warning.
// http://gcc.gnu.org/ml/gcc-bugs/2005-10/msg03270.html
uint32 gcchtonl(uint32);
#endif
// Messaging and stream APIs must be supported on a single class because
// sometimes an application will switch modes on a single socket. For
// example, when transferring 3D level geometry during handshaking with
// a game server.
/**
A conduit that guarantees messages will arrive, intact and in order.
Create on the client using NetworkDevice::createReliableConduit and
on the server using NetListener::waitForConnection. Set the reference
counted pointer to NULL to disconnect.
To construct a ReliableConduit:
<OL>
<LI> Create a G3D::NetworkDevice (if you are using G3D::GApp, it creates
one for you) on the client and on the server.
<LI> On the server, create a G3D::NetListener using
G3D::NetworkDevice::createListener
<LI> On the server, invoke G3D::NetListener::waitForConnection.
<LI> On the client, call G3D::NetworkDevice::createReliableConduit.
You will need the server's G3D::NetAddress. Consider using
G3D::Discovery::Client to find it via broadcasting.
</OL>
\deprecated
*/
class ReliableConduit : public Conduit {
private:
friend class NetworkDevice;
friend class NetListener;
enum State {RECEIVING, HOLDING, NO_MESSAGE} state;
NetAddress addr;
/**
Type of the incoming message.
*/
uint32 messageType;
/**
Total size of the incoming message (read from the header).
*/
uint32 messageSize;
/** Shared buffer for receiving messages. */
void* receiveBuffer;
/** Total size of the receiveBuffer. */
size_t receiveBufferTotalSize;
/** Size occupied by the current message... so far. This will be
equal to messageSize when the whole message has arrived.
*/
size_t receiveBufferUsedSize;
ReliableConduit(const NetAddress& addr);
ReliableConduit(const SOCKET& sock,
const NetAddress& addr);
template<typename T> static void serializeMessage
(uint32 t, const T& m, BinaryOutput& b) {
b.writeUInt32(t);
// Reserve space for the 4 byte size header
b.writeUInt32(0);
size_t L = (size_t)b.length();
m.serialize(b);
if ((size_t)b.length() == L) {
// No data was created by serialization.
// We need to send at least one byte because receive assumes that
// a zero length message is an error.
b.writeUInt8(0xFF);
}
uint32 len = (uint32)b.size() - 8;
// We send the length first to tell recv how much data to read.
// Here we abuse BinaryOutput a bit and write directly into
// its buffer, violating the abstraction.
// Note that we write to the second set of 4 bytes, which is
// the size field.
uint32* lenPtr = ((uint32*)b.getCArray()) + 1;
#if defined(__GNUC__)
*lenPtr = gcchtonl(len);
#else
*lenPtr = htonl(len);
#endif
}
void sendBuffer(const BinaryOutput& b);
/** Accumulates whatever part of the message (not the header) is
still waiting on the socket into the receiveBuffer during
state = RECEIVING mode. Closes the socket if anything goes
wrong. When receiveBufferUsedSize == messageSize, the entire
message has arrived. */
void receiveIntoBuffer();
/** Receives the messageType and messageSize from the socket. */
void receiveHeader();
public:
/**
Client invokes this to connect to a server. The call blocks until the
conduit is opened. The conduit will not be ok() if it fails.
*/
static ReliableConduitRef create(const NetAddress& address);
/** Closes the socket. */
~ReliableConduit();
/** The message is actually copied from the socket to an internal buffer during
this call. Receive only deserializes.*/
virtual bool messageWaiting();
/**
Serializes the message and schedules it to be sent as soon as possible,
and then returns immediately. The message can be any <B>class</B> with
a serialize and deserialize method. On the receiving side,
use G3D::ReliableConduit::waitingMessageType() to detect the incoming
message and then invoke G3D::ReliableConduit::receive(msg) where msg
is of the same class as the message that was sent.
The actual data sent across the network is preceeded by the
message type and the size of the serialized message as a 32-bit
integer. The size is sent because TCP is a stream protocol and
doesn't have a concept of discrete messages.
*/
template<typename T> inline void send(uint32 type, const T& message) {
binaryOutput.reset();
serializeMessage(type, message, binaryOutput);
sendBuffer(binaryOutput);
}
/** Sends an empty message with the given type. Useful for sending
commands that have no parameters. */
void send(uint32 type);
/** Send the same message to a number of conduits. Useful for sending
data from a server to many clients (only serializes once). */
template<typename T>
inline static void multisend(
const Array<ReliableConduitRef>& array,
uint32 type,
const T& m) {
if (array.size() > 0) {
array[0]->binaryOutput.reset();
serializeMessage(type, m, array[0]->binaryOutput);
for (int i = 0; i < array.size(); ++i) {
array[i]->sendBuffer(array[0]->binaryOutput);
}
}
}
virtual uint32 waitingMessageType();
/**
If a message is waiting, deserializes the waiting message into
message and returns true, otherwise returns false. You can
determine the type of the message (and therefore, the class
of message) using G3D::ReliableConduit::waitingMessageType().
*/
template<typename T> inline bool receive(T& message) {
if (! messageWaiting()) {
return false;
}
debugAssert(state == HOLDING);
// Deserialize
BinaryInput b((uint8*)receiveBuffer, receiveBufferUsedSize, G3D_LITTLE_ENDIAN, BinaryInput::NO_COPY);
message.deserialize(b);
// Don't let anyone read this message again. We leave the buffer
// allocated for the next caller, however.
receiveBufferUsedSize = 0;
state = NO_MESSAGE;
messageType = 0;
messageSize = 0;
// Potentially read the next message.
messageWaiting();
return true;
}
/** Removes the current message from the queue. */
inline void receive() {
if (! messageWaiting()) {
return;
}
receiveBufferUsedSize = 0;
state = NO_MESSAGE;
messageType = 0;
messageSize = 0;
// Potentially read the next message.
messageWaiting();
}
/** The address of the other end of the conduit */
NetAddress address() const;
};
typedef shared_ptr<class LightweightConduit> LightweightConduitRef;
/**
Provides fast but unreliable transfer of messages. On a LAN,
LightweightConduit will probably never drop messages but you
<I>might</I> get your messages out of order. On an internet
connection it might drop messages altogether. Messages are never
corrupted, however. LightweightConduit requires a little less setup
and overhead than ReliableConduit. ReliableConduit guarantees
message delivery and order but requires a persistent connection.
To set up a LightweightConduit (assuming you have already made
subclasses of G3D::NetMessage based on your application's
pcommunication protocol):
[Server Side]
<OL>
<LI> Call LightweightConduit::create(port, true, false),
where port is the port on which you will receive messages.
<LI> Poll LightweightConduit::messageWaiting from your main loop. When
it is true (or, equivalently, when LightweightConduit::waitingMessageType
is non-zero) there is an incoming message.
<LI> To read the incoming message, call LightweightConduit::receive with
the appropriate class type, which mist have a deserialize method.
LightweightConduit::waitingMessageType tells you what class is
needed (you make up your own message constants for your program; numbers
under 1000 are reserved for G3D's internal use).
<LI> When done, simply set the G3D::LightweightConduitRef to NULL or let
it go out of scope and the conduit cleans itself up automatically.
</OL>
[Client Side]
<OL>
<LI> Call G3D::LightweightConduit::create(). If you will
broadcast to all servers on a LAN, set the third optional argument to
true (the default is false for no broadcast). You can also set up the
receive port as if it was a server to send and receive from a single
LightweightConduit.
<LI> To send, call G3D::LightweightConduit::send with the target address
and a pointer to an instance of the message you want to send.
<LI> When done, simply set the G3D::LightweightConduitRef to NULL or let
it go out of scope and the conduit cleans itself up automatically.
</OL>
\deprecated
*/
class LightweightConduit : public Conduit {
private:
friend class NetworkDevice;
/**
True when waitingForMessageType has read the message
from the network into messageType/messageStream.
*/
bool alreadyReadMessage;
/**
Origin of the received message.
*/
NetAddress messageSender;
/**
The type of the last message received.
*/
uint32 messageType;
/**
The message received (the type has already been read off).
*/
Array<uint8> messageBuffer;
LightweightConduit(uint16 receivePort, bool enableReceive, bool enableBroadcast);
void sendBuffer(const NetAddress& a, BinaryOutput& b);
/** Maximum transmission unit (packet size in bytes) for this socket.
May vary between sockets. */
int MTU;
template<typename T>
void serializeMessage(
uint32 type,
const T& m,
BinaryOutput& b) const {
debugAssert(type != 0);
b.writeUInt32(type);
m.serialize(b);
b.writeUInt32(1);
debugAssertM(b.size() < MTU,
format("This LightweightConduit is limited to messages of "
"%d bytes (Ethernet hardware limit; this is the "
"'UDP MTU')", maxMessageSize()));
if (b.size() >= MTU) {
throw LightweightConduit::PacketSizeException(
format("This LightweightConduit is limited to messages of "
"%d bytes (Ethernet hardware limit; this is the "
"'UDP MTU')", maxMessageSize()),
(int)b.size() - 4, // Don't count the type header
maxMessageSize());
}
}
public:
static LightweightConduitRef create(uint16 receivePort, bool enableReceive, bool enableBroadcast);
class PacketSizeException {
public:
std::string message;
int serializedPacketSize;
int maxMessageSize;
inline PacketSizeException(const std::string& m, int s, int b) :
message(m),
serializedPacketSize(s),
maxMessageSize(b) {}
};
/** Closes the socket. */
~LightweightConduit();
/** The maximum length of a message that can be sent
(G3D places a small header at the front of each UDP packet;
this is already taken into account by the value returned).
*/
inline int maxMessageSize() const {
return MTU - 4;
}
template<typename T> inline void send(const NetAddress& a, uint32 type, const T& msg) {
binaryOutput.reset();
serializeMessage(type, msg, binaryOutput);
sendBuffer(a, binaryOutput);
}
/** Send the same message to multiple addresses (only serializes once).
Useful when server needs to send to a known list of addresses
(unlike direct UDP broadcast to all addresses on the subnet) */
template<typename T> inline void send(const Array<NetAddress>& a, uint32 type, const T& m) {
binaryOutput.reset();
serializeMessage(type, m, binaryOutput);
for (int i = 0; i < a.size(); ++i) {
sendBuffer(a[i], binaryOutput);
}
}
bool receive(NetAddress& sender);
template<typename T> inline bool receive(NetAddress& sender, T& message) {
bool r = receive(sender);
if (r) {
BinaryInput b((messageBuffer.getCArray() + 4),
messageBuffer.size() - 4,
G3D_LITTLE_ENDIAN, BinaryInput::NO_COPY);
message.deserialize(b);
}
return r;
}
inline bool receive() {
static NetAddress ignore;
return receive(ignore);
}
virtual uint32 waitingMessageType();
virtual bool messageWaiting();
};
///////////////////////////////////////////////////////////////////////////////
typedef shared_ptr<class NetListener> NetListenerRef;
/**
Runs on the server listening for clients trying to make reliable connections.
\deprecated
*/
class NetListener : public ReferenceCountedObject {
private:
friend class NetworkDevice;
SOCKET sock;
/** Port is in host byte order. */
NetListener(uint16 port);
public:
static NetListenerRef create(const uint16 port);
~NetListener();
/** Block until a connection is received. Returns NULL if
something went wrong. */
ReliableConduitRef waitForConnection();
/** True if a client is waiting (i.e. waitForConnection will
return immediately). */
bool clientWaiting() const;
bool ok() const;
};
///////////////////////////////////////////////////////////////////////////////
/**
@brief Abstraction of network (socket) functionality.
An abstraction over sockets that provides a message-based network
infrastructure optimized for sending many small (~500 bytes) messages.
All functions always return immediately.
Create only one NetworkDevice per process (a WinSock restriction).
NetworkDevice is technically not thread safe. However, as long as
you use different conduits on different threads (or lock conduits
before sending), you will encounter no problems sharing the single
NetworkDevice across multiple threads. That is, do not invoke the same
Conduit's send or receive method on two threads at once.
This assumes that the underlying WinSock/BSD sockets implementation
is thread safe. That is not guaranteed, but in practice seems
to always be true (see
http://tangentsoft.net/wskfaq/intermediate.html#threadsafety)
<hr>
IP networks use "network byte order" (big-endian) for
communicating integers. "Host byte order" is the endian-ness of
the local machine (typically little-endian; see
System::endian). The C functions htonl() and ntohl() convert 32-bit
values between these formats. G3D only ever exposes host byte order,
so programmers rarely need to be aware of the distinction.
\deprecated
*/
class NetworkDevice {
public:
/** @brief Description of an ethernet or wireless ethernet adapter.*/
class EthernetAdapter {
public:
/** Reverse-DNS of the ip address.*/
std::string hostname;
/** Name of the adapter */
std::string name;
/** IP address in host byte order.*/
uint32 ip;
/** Subnet mask in host byte order.*/
uint32 subnet;
/** UDP broadcast address in host byte order.*/
uint32 broadcast;
/** MAC (hardware) address, if known */
uint8 mac[6];
EthernetAdapter();
/** Produces a text description of this adapter */
void describe(TextOutput& t) const;
};
private:
friend class Conduit;
friend class LightweightConduit;
friend class ReliableConduit;
friend class NetListener;
bool initialized;
Array<EthernetAdapter> m_adapterArray;
/** Broadcast addresses available on this machine,
extracted from m_adapterArray.*/
Array<uint32> m_broadcastAddresses;
/** Utility method. */
void closesocket(SOCKET& sock) const;
/** Utility method. Returns true on success.*/
bool bind(SOCKET sock, const NetAddress& addr) const;
/** The global instance */
static NetworkDevice* s_instance;
NetworkDevice();
bool init();
void _cleanup();
/** Called from init to update m_adapterArray and
m_broadcastAddresses. */
void addAdapter(const EthernetAdapter& a);
public:
/** Prints an IP address to a string.
@param ip In host byte order.*/
static std::string formatIP(uint32 ip);
/** Prints a MAC address to a string. */
static std::string formatMAC(const uint8 mac[6]);
~NetworkDevice();
/** Returns the available ethernet adapters for the current
machine that are online. Does not include the loopback adapter
for localhost.*/
inline const Array<EthernetAdapter>& adapterArray() const {
return m_adapterArray;
}
/** Returns the (unique) IP addresses for UDP broadcasting
extracted from adapterArray(). All are in host byte order. */
inline const Array<uint32>& broadcastAddressArray() const {
return m_broadcastAddresses;
}
/**
Returns NULL if there was a problem initializing the network.
*/
static NetworkDevice* instance();
/**
Shuts down the network device (destroying the global instance).
*/
static void cleanup();
/**
Prints a human-readable description of this machine
to the text output stream.
*/
void describeSystem(
TextOutput& t);
void describeSystem(
std::string& s);
/** Returns the name (or one of the names) of this computer */
std::string localHostName() const;
/** There is often more than one address for the local host. This
returns all of them.
@deprecated Use adapterArray()
*/
void localHostAddresses(Array<NetAddress>& array) const;
};
#ifdef __GNUC__
inline uint32 gcchtonl(uint32 x) {
// This pragma fools gcc into surpressing all error messages,
// including the bogus one that it creates for htonl
# pragma GCC system_header
return htonl(x);
}
#endif
} // G3D namespace
#ifndef _WIN32
#undef SOCKADDR_IN
#undef SOCKET
#endif
#endif