rework Network send() code to use NetworkTarget/Node/Group::send classes, add a NetworkMessage class for sending NetworkObject messages, and fix a bug whereby the server's client TCP sockets weren't nonblocking.... I wonder how this has worked before?\!
authorTero Marttila <terom@fixme.fi>
Mon, 26 Jan 2009 23:03:47 +0200
changeset 431 c6d7272a164b
parent 430 74e562e16399
child 432 82b0f4e55a13
rework Network send() code to use NetworkTarget/Node/Group::send classes, add a NetworkMessage class for sending NetworkObject messages, and fix a bug whereby the server's client TCP sockets weren't nonblocking.... I wonder how this has worked before?\!
src/Engine.cc
src/Network/Channel.hh
src/Network/Client.cc
src/Network/Client.hh
src/Network/Group.cc
src/Network/Group.hh
src/Network/Message.cc
src/Network/Message.hh
src/Network/Node.cc
src/Network/Node.hh
src/Network/Object.cc
src/Network/Object.hh
src/Network/Protocol.hh
src/Network/Server.cc
src/Network/Server.hh
src/Network/Session.cc
src/Network/Session.hh
src/Network/TCP.cc
src/Network/Target.cc
src/Network/Target.hh
--- a/src/Engine.cc	Mon Jan 26 19:52:52 2009 +0200
+++ b/src/Engine.cc	Mon Jan 26 23:03:47 2009 +0200
@@ -144,7 +144,7 @@
         
 GameState& Engine::onNetworkClientConnected (Terrain *terrain) {
     // sanity-check
-    assert(!terrain && !game_state && graphics);
+    assert(!this->terrain && !game_state && graphics);
 
     // setup the game
     setupGame(terrain);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Network/Channel.hh	Mon Jan 26 23:03:47 2009 +0200
@@ -0,0 +1,15 @@
+#ifndef NETWORK_CHANNEL_HH
+#define NETWORK_CHANNEL_HH
+
+#include "../Types.hh"
+
+/**
+ * A NetworkSession puts each packet onto a specific channel, which can the be used to run multiple different modules
+ * on top of a single session.
+ *
+ * NetworkChannelID zero is reserved for internal NetworkSession use
+ */
+typedef uint16_t NetworkChannelID;
+
+
+#endif /* NETWORK_CHANNEL_HH */
--- a/src/Network/Client.cc	Mon Jan 26 19:52:52 2009 +0200
+++ b/src/Network/Client.cc	Mon Jan 26 23:03:47 2009 +0200
@@ -5,6 +5,9 @@
 #include "../Engine.hh"
 #include "../Logger.hh"
 
+// XXX: move
+#include "Message.hh"
+
 #include <cassert>
 
 /*
@@ -66,13 +69,13 @@
     Engine::log(INFO, "net.client") << "Got game data, creating player";
 
     // create our new NetworkClient object
-    client = new NetworkClient(engine, gs, netsession, server);
+    client = new NetworkClient(engine, gs, netsession, *server);
 }
  
 /*
  * NetworkClient
  */
-NetworkClient::NetworkClient (Engine &engine, GameState &state, NetworkSession &netsession, NetworkNode *server) : 
+NetworkClient::NetworkClient (Engine &engine, GameState &state, NetworkSession &netsession, NetworkNode &server) : 
     engine(engine), state(state), netsession(netsession), server(server),
     controller(*this)
 {
@@ -108,9 +111,10 @@
         case NETMSG_PLAYER_JOIN:
             on_player_join(obj_id, pkt);
             break;
-
+        
+        case NETMSG_PROJECTILE_INFO:
         case NETMSG_PROJECTILE_PLAYER_FIRED:
-            on_projectile_player_fired(obj_id, pkt);
+            on_projectile_create(obj_id, pkt);
             break;
 
         default:
@@ -155,7 +159,7 @@
     Engine::log(INFO, "net.client") << "Player joined: " << player;
 }
         
-void NetworkClientController::on_projectile_player_fired (NetworkObjectID obj_id, NetworkPacketInput &pkt) {
+void NetworkClientController::on_projectile_create (NetworkObjectID obj_id, NetworkPacketInput &pkt) {
     // read the packet
     NetworkObject *player_obj = client.controller.read_object(pkt);
     Vector position = pkt.read_vector();
@@ -337,14 +341,14 @@
 }
         
 void NetworkClientLocalPlayer::handleInput (PlayerInput input, TimeMS dt) {
-    NetworkPacket pkt;
+    NetworkMessage msg (*this, NETMSG_CLIENT_INPUT);
 
     // write packet
-    pkt.write_uint16(input);
-    pkt.write_uint32(dt);
+    msg.write_uint16(input);
+    msg.write_uint32(dt);
     
-    // send
-    send(NETMSG_CLIENT_INPUT, pkt, false);
+    // send to server
+    client.server.send_raw(msg);
     
     // do not handle locally
 }
--- a/src/Network/Client.hh	Mon Jan 26 19:52:52 2009 +0200
+++ b/src/Network/Client.hh	Mon Jan 26 23:03:47 2009 +0200
@@ -60,9 +60,9 @@
         void on_player_join (NetworkObjectID obj_id, NetworkPacketInput &pkt);
 
         /**
-         * Handle NETMSG_PROJECTILE_PLAYER_FIRED -> NetworkClientProjectile
+         * Handle NETMSG_PROJECTILE_{INFO/PLAYER_FIRED} -> NetworkClientProjectile
          */
-        void on_projectile_player_fired (NetworkObjectID obj_id, NetworkPacketInput &pkt);
+        void on_projectile_create (NetworkObjectID obj_id, NetworkPacketInput &pkt);
 };
 
 /**
@@ -95,7 +95,7 @@
         /**
          * The server NetworkNode from Netsession::connect
          */
-        NetworkNode *server;
+        NetworkNode &server;
 
         /**
          * Our specialized NetworkObject_ClientController
@@ -112,7 +112,7 @@
          * @param state the GameState to use
          * @param connect_to the address to connect to
          */
-        NetworkClient (Engine &engine, GameState &state, NetworkSession &netsession, NetworkNode *server);
+        NetworkClient (Engine &engine, GameState &state, NetworkSession &netsession, NetworkNode &server);
     
     public:
         /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Network/Group.cc	Mon Jan 26 23:03:47 2009 +0200
@@ -0,0 +1,18 @@
+
+#include "Group.hh"
+#include "Node.hh"
+        
+void NetworkGroup::send_pkt (const NetworkPacketBuffer &pkt, bool reliable) {
+    // iterate + recurse send_pkt
+    for (iterator_type it = targets_begin; it != targets_end; it++) {
+        // handle as NetworkTarget
+        NetworkTarget *target = static_cast<NetworkTarget*>(*it);
+        
+        // exclude?
+        if (target == exclude)
+            continue;
+        
+        // recurse
+        target->send_pkt(pkt, reliable);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Network/Group.hh	Mon Jan 26 23:03:47 2009 +0200
@@ -0,0 +1,46 @@
+#ifndef NETWORK_GROUP_HH
+#define NETWORK_GROUP_HH
+
+#include "Target.hh"
+
+#include <list>
+
+// XXX: ugly forward-declare hack
+class NetworkNode;
+
+/**
+ * Implements group-multicast for groups of NetworkTargets, by having each target copy + send the packet
+ */
+class NetworkGroup : public NetworkTarget {
+    protected:
+        // XXX: only support NetworkNodes for now...
+        typedef std::list<NetworkNode*>::iterator iterator_type;
+           
+        /** List of targets to send to */
+        iterator_type targets_begin, targets_end;
+
+        /** Special-case for single target to exclude */
+        NetworkNode *exclude;
+    
+    public:
+        /**
+         * Construct a NetworkGroup with the given targets.
+         *
+         * This does not copy the iterated list contents - the list must remain valid for the lifetime of this object.
+         *
+         * @param targets the list of targets to send to
+         */
+        NetworkGroup (iterator_type targets_begin, iterator_type targets_end, NetworkNode *except = NULL) :
+            targets_begin(targets_begin), targets_end(targets_end), exclude(except)
+        {
+
+        }
+
+    protected:
+        /**
+         * Implement NetworkTarget
+         */
+        virtual void send_pkt (const NetworkPacketBuffer &pkt, bool reliable = true); 
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Network/Message.cc	Mon Jan 26 23:03:47 2009 +0200
@@ -0,0 +1,11 @@
+
+#include "Message.hh"
+
+NetworkMessage::NetworkMessage (NetworkObject &obj, NetworkMessageID msg_id) {
+    // write the netsession channel ID header
+    obj.controller.session.write_packet_header(*this, obj.controller.channel_id);
+
+    // write the NetworkObject header
+    write_uint32(obj.obj_id);
+    write_uint16(msg_id);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Network/Message.hh	Mon Jan 26 23:03:47 2009 +0200
@@ -0,0 +1,24 @@
+#ifndef NETWORK_MESSAGE_HH
+#define NETWORK_MESSAGE_HH
+
+/**
+ * @file
+ *
+ * NetworkPacket for use with NetworkObjects
+ */
+
+#include "Packet.hh"
+#include "Object.hh"
+
+/**
+ * NetworkMessages are NetworkPackets that are related to some specfic NetworkObject and its NetworkObjectController.
+ */
+class NetworkMessage : public NetworkPacket {
+    public:
+        /**
+         * Create a message of the given type to be sent on the given object
+         */
+        NetworkMessage (NetworkObject &obj, NetworkMessageID msg_id);
+};
+
+#endif /* NETWORK_MESSAGE_HH */
--- a/src/Network/Node.cc	Mon Jan 26 19:52:52 2009 +0200
+++ b/src/Network/Node.cc	Mon Jan 26 23:03:47 2009 +0200
@@ -27,11 +27,7 @@
 //    delete this;
 }
 
-void NetworkNode::write_packet_header (NetworkPacketOutput &pkt, NetworkChannelID channel_id) {
-    pkt.write_uint16(channel_id);
-}
-
-void NetworkNode::send_raw (const NetworkPacketBuffer &pkt, bool reliable) {
+void NetworkNode::send_pkt (const NetworkPacketBuffer &pkt, bool reliable) {
     // either tcp or udp
     if (reliable) {
         assert(tcp);
@@ -43,19 +39,6 @@
     }
 
 }
-
-void NetworkNode::send (NetworkChannelID channel_id, const NetworkPacketBuffer &pkt, bool reliable) {
-    assert(channel_id > 0);
-    
-    // add our header
-    NetworkPacket pkt2;
-    
-    write_packet_header(pkt2, channel_id);
-    pkt2.write_packet(pkt);
-    
-    // send
-    send_raw(pkt2, reliable);
-}
         
 const NetworkAddress& NetworkNode::getRemoteAddress (void) {
     return address;
--- a/src/Network/Node.hh	Mon Jan 26 19:52:52 2009 +0200
+++ b/src/Network/Node.hh	Mon Jan 26 23:03:47 2009 +0200
@@ -23,13 +23,16 @@
 #include "TCP.hh"
 #include "UDP.hh"
 #include "Session.hh"
+#include "Target.hh"
 
 /**
  * A NetworkNode represents a remote NetworkSession connected to our NetworkSession.
  *
  * A NetworkNode has a tcp connection, plus an udp socket to us (either NetworkSession's udp_srv or udp_client)
+ *
+ * NetworkNode implements the NetworkTarget interface, so you can use send()
  */
-class NetworkNode {
+class NetworkNode : public NetworkTarget {
     private:
         /**
          * Our local NetworkSession
@@ -82,41 +85,13 @@
          */
         CL_Signal_v0 _sig_disconnected;
 
-    public:
-        /**
-         * Write to appropriate NetworkSession header to the given packet. Can be used to prepare packets for use with
-         * send_raw. The size of the header is equal to NETWORK_SESSION_HEADER_SIZE
-         *
-         * @param pkt the packet to write the header to
-         * @param channel_id the NetworkChannelID to use
-         *
-         * @see send_raw
-         * @see NETWORK_SESSION_HEADER_SIZE
-         */
-        void write_packet_header (NetworkPacketOutput &pkt, NetworkChannelID channel_id);
-        
+    protected:
         /**
-         * Send a raw packet prepared using write_packet_header. This does not need to copy the packet data around.
-         *
-         * @param pkt the NetworkPacket prepared using write_packet_header
-         * @param reliable Whether to use TCP or UDP
-         *
-         * @see write_packet_header
+         * Send the given packet to this node, used by NetworkTarget
          */
-        void send_raw (const NetworkPacketBuffer &pkt, bool reliable = true);
-
-        /**
-         * Send the given packet to this node on the given channel.
-         *
-         * Note that this constructs a new *NetworkPacket* containing our header and the given packet, so there
-         * given packet must be small enough to fit.
-         *
-         * @param channel_id the NetworkChannelID to use
-         * @param pkt the NetworkPacket to send on the given channel
-         * @param reliable Whether to use TCP or UDP
-         */
-        void send (NetworkChannelID channel_id, const NetworkPacketBuffer &pkt, bool reliable = true);
+        virtual void send_pkt (const NetworkPacketBuffer &pkt, bool reliable = true);
         
+    public:
         /**
          * Get this node's remote address (both TCP and UDP).
          *
--- a/src/Network/Object.cc	Mon Jan 26 19:52:52 2009 +0200
+++ b/src/Network/Object.cc	Mon Jan 26 23:03:47 2009 +0200
@@ -67,15 +67,16 @@
 /* 
  * NetworkObject_ClientController *
  */
-NetworkObject_ClientController::NetworkObject_ClientController (NetworkSession &session, NetworkChannelID channel_id, NetworkNode *server) :
-    NetworkObjectController(session, channel_id), server(server) {
+NetworkObject_ClientController::NetworkObject_ClientController (NetworkSession &session, NetworkChannelID channel_id, NetworkNode &server) :
+    NetworkObjectController(session, channel_id), server(server) 
+{
 
 
 }
         
 void NetworkObject_ClientController::handle_create (NetworkObjectID obj_id, NetworkMessageID msg_id, NetworkPacketInput &pkt, NetworkNode *node) {
     // we only communicate with the server
-    if (node != server)
+    if (node != &server)
         assert(false);
     
     // create new object
@@ -101,12 +102,6 @@
     controller.objects.erase(obj_id);
 }
         
-void NetworkObject::buildPacket (NetworkPacketOutput &pkt, NetworkMessageID msg_id, const NetworkPacketBuffer &payload) {
-    pkt.write_uint32(obj_id);
-    pkt.write_uint16(msg_id);
-    pkt.write_packet(payload);
-}
-
 std::ostream& operator<< (std::ostream &s, const NetworkObject &obj) {
     return s << "<NetworkObject #" << obj.obj_id << ">";
 }
@@ -123,26 +118,6 @@
     _map_sig_message[msg_id](node, pkt);
 }
 
-void NetworkObject_Server::send_to (NetworkNode *dst, NetworkMessageID msg_id, const NetworkPacketBuffer &pkt, bool reliable) {
-    NetworkPacket pkt_out;
-
-    buildPacket(pkt_out, msg_id, pkt);
-
-    dst->send(controller.channel_id, pkt_out, reliable);
-}
-
-void NetworkObject_Server::send_all (NetworkMessageID msg_id, const NetworkPacketBuffer &pkt, bool reliable) {
-    send_all_except(msg_id, pkt, NULL, reliable);
-}
-
-void NetworkObject_Server::send_all_except (NetworkMessageID msg_id, const NetworkPacketBuffer &pkt, NetworkNode *black_sheep, bool reliable) {
-    NetworkPacket pkt_out;
-
-    buildPacket(pkt_out, msg_id, pkt);
-    
-    controller.session.send_all_except(controller.channel_id, pkt_out, black_sheep, reliable);
-}
- 
 /* 
  * NetworkObject_Client 
  */
@@ -153,17 +128,9 @@
 }
 
 void NetworkObject_Client::handle_packet (NetworkNode *node, NetworkMessageID msg_id, NetworkPacketInput &pkt) {
-    if (node != controller.server)
-        assert(false);
+    if (node != &controller.server)
+        throw Error("Reject NetworkObject packet from non-server");
 
     _map_sig_message[msg_id](pkt);
 }
-       
-void NetworkObject_Client::send (NetworkMessageID msg_id, const NetworkPacketBuffer &pkt, bool reliable) {
-    NetworkPacket pkt_out;
 
-    buildPacket(pkt_out, msg_id, pkt);
-
-    controller.server->send(controller.channel_id, pkt_out, reliable);
-}
-
--- a/src/Network/Object.hh	Mon Jan 26 19:52:52 2009 +0200
+++ b/src/Network/Object.hh	Mon Jan 26 23:03:47 2009 +0200
@@ -38,6 +38,9 @@
     friend class NetworkObject;
     friend class NetworkObject_Server;
     friend class NetworkObject_Client;
+    
+    // XXX: needs to access session and channel_id
+    friend class NetworkMessage;
 
     private:
         /**
@@ -146,7 +149,7 @@
         /**
          * The server node, as returned by NetworkSession::connect
          */
-        NetworkNode *server;
+        NetworkNode &server;
         
         /**
          * A mapping of NetworkMessageID -> sig_create, used by the default handle_create
@@ -160,7 +163,7 @@
          *
          * @see NetworkObjectController
          */
-        NetworkObject_ClientController (NetworkSession &session, NetworkChannelID channel_id, NetworkNode *server);
+        NetworkObject_ClientController (NetworkSession &session, NetworkChannelID channel_id, NetworkNode &server);
 
     protected:
         /**
@@ -190,6 +193,9 @@
     friend class NetworkObjectController;
     friend std::ostream& operator<< (std::ostream &s, const NetworkObject &obj);
 
+    // XXX: needs to access controller and obj_id
+    friend class NetworkMessage;
+
     protected:
         /**
          * Generic controller
@@ -223,15 +229,6 @@
          * @param pkt the packet itself, with the headers read
          */
         virtual void handle_packet (NetworkNode *node, NetworkMessageID msg_id, NetworkPacketInput &pkt) = 0;
-        
-        /**
-         * Prepare an outgoing packet, writing the header and the payload
-         *
-         * @param pkt the packet to prepare
-         * @param msg_id the packet's message id
-         * @param payload the packet's payload
-         */
-        void buildPacket (NetworkPacketOutput &pkt, NetworkMessageID msg_id, const NetworkPacketBuffer &payload);
 };
 
 /**
@@ -272,35 +269,6 @@
 
     public:
         /**
-         * Send a message on this object to the given node
-         *
-         * @param dst the NetworkNode to send the message to
-         * @param msg_id the type of message to send
-         * @param pkt the packet payload
-         * @param reliable Whether to use TCP or UDP
-         */
-        void send_to (NetworkNode *dst, NetworkMessageID msg_id, const NetworkPacketBuffer &pkt, bool reliable = true);
-
-        /**
-         * Send a message on this object to all nodes on our controller's NetworkSession
-         *
-         * @param msg_id the type of message to send
-         * @param pkt the packet payload
-         * @param reliable Whether to use TCP or UDP
-         */
-        void send_all (NetworkMessageID msg_id, const NetworkPacketBuffer &pkt, bool reliable = true);
-
-        /**
-         * Send a message on this object to all nodes on our controller's NetworkSession, except the given node
-         *
-         * @param msg_id the type of message to send
-         * @param pkt the packet payload
-         * @param black_sheep the node to *NOT* send the message to
-         * @param reliable Whether to use TCP or UDP
-         */
-        void send_all_except (NetworkMessageID msg_id, const NetworkPacketBuffer &pkt, NetworkNode *black_sheep, bool reliable = true);
-        
-        /**
          * Triggered whenever we receive a message of the given type on this object, giving the node that sent it and the
          * packet itself
          *
@@ -343,15 +311,6 @@
 
     public:
         /**
-         * Send a message on this object to the server
-         *
-         * @param msg_id the type of message to send
-         * @param pkt the packet payload
-         * @param reliable Whether to use TCP or UDP
-         */
-        void send (NetworkMessageID msg_id, const NetworkPacketBuffer &pkt, bool reliable = true);
-        
-        /**
          * Triggered whenever we receive a message of the given type on this object from the server.
          *
          * @param msg_id the NetworkMessageID to handle
--- a/src/Network/Protocol.hh	Mon Jan 26 19:52:52 2009 +0200
+++ b/src/Network/Protocol.hh	Mon Jan 26 23:03:47 2009 +0200
@@ -47,9 +47,10 @@
  *
  * Message names are of the form 'NETMSG_<object type>_<action>'
  */
-enum NetworkMessage {
+enum NetworkMessageID_ {
     NETMSG_PACKET_INVALID       = 0x0000,
 
+// Server -> Client: Handshake
     /*
      * You have joined the game:
      *
@@ -57,6 +58,7 @@
      */
     NETMSG_SERVER_HELLO         = 0x0100,
 
+// Server -> Client: Player state    
     /*
      * New client has connected to server:
      *  
@@ -70,6 +72,7 @@
      */
     NETMSG_PLAYER_QUIT          = 0x0102,
 
+// Client -> Server: Player
     /*
      * Client has input to process
      *
@@ -78,6 +81,7 @@
      */
     NETMSG_CLIENT_INPUT         = 0x0201,
     
+// Server -> Client: Player    
     /*
      * Initial player info
      *
@@ -153,7 +157,15 @@
      * float length
      */
     NETMSG_PLAYER_ROPE_LENGTH   = 0x0334,
-    
+
+// Server -> Client: Projectiles    
+    /**
+     * Projectile object sync
+     *
+     * @see NETMSG_PROJECTILE_PLAYER_FIRED
+     */
+    NETMSG_PROJECTILE_INFO          = 0x0410,
+   
     /*
      * Player has fired a weapon, creating this projectile
      *
--- a/src/Network/Server.cc	Mon Jan 26 19:52:52 2009 +0200
+++ b/src/Network/Server.cc	Mon Jan 26 23:03:47 2009 +0200
@@ -5,6 +5,9 @@
 #include "../Engine.hh"
 #include "../Logger.hh"
 
+// XXX: move
+#include "Message.hh"
+
 #include <cassert>
 
 NetworkServer::NetworkServer (GameState &state, const NetworkEndpoint &listen_addr) : 
@@ -23,16 +26,16 @@
     Engine::log(INFO, "net.server") << "Client connected, sending terrain data: " << node->getRemoteAddress();
 
     // send the terrain data
-    send_terrain_data(node);
+    send_terrain_data(*node);
 
     // create the player object (it logs it)
-    NetworkServerPlayer *player = new NetworkServerPlayer(*this, node);
+    NetworkServerPlayer *player = new NetworkServerPlayer(*this, *node);
 
     // add to players
     players.push_back(player);
 }
  
-void NetworkServer::send_terrain_data (NetworkNode *node) {
+void NetworkServer::send_terrain_data (NetworkNode &node) {
     Terrain &terrain = state.world.terrain;
     
     // dimensions?
@@ -57,7 +60,7 @@
     );
     
     // write netsession header
-    node->write_packet_header(pkt, NETCHAN_TERRAIN_ARRAY);
+    netsession.write_packet_header(pkt, NETCHAN_TERRAIN_ARRAY);
 
     // write terrain dimensions
     pkt.write_uint32(map.x);
@@ -67,7 +70,7 @@
     pkt.write_compressed(terrain_buf, terrain_size);
 
     // send
-    node->send_raw(pkt, true);
+    node.send_raw(pkt, true);
 }
 
        
@@ -88,53 +91,64 @@
 /*
  * NetworkServerPlayer
  */
-NetworkServerPlayer::NetworkServerPlayer (NetworkServer &server, NetworkNode *node) : 
+NetworkServerPlayer::NetworkServerPlayer (NetworkServer &server, NetworkNode &node) : 
     Player(server.state, Vector(PLAYER_INITIAL_X, PLAYER_INITIAL_Y), true), NetworkServerObject(server), node(node) 
 {
     // messages
-    slots.connect(node->sig_disconnected(), this, &NetworkServerPlayer::on_disconnected);
+    slots.connect(node.sig_disconnected(), this, &NetworkServerPlayer::on_disconnected);
     slots.connect(this->sig_message(NETMSG_CLIENT_INPUT), this, &NetworkServerPlayer::on_input);
 
     // the initial NETMSG_PLAYER_HELLO
-    NetworkPacket hello_pkt;
-    hello_pkt.write_vector(getPosition());
+    NetworkMessage hello_msg(*this, NETMSG_SERVER_HELLO);
+    hello_msg.write_vector(getPosition());
+    node.send_raw(hello_msg);
 
-    this->send_to(node, NETMSG_SERVER_HELLO, hello_pkt, true);
-
-    // send other player objects
+    // sync other players
     for (std::list<NetworkServerPlayer*>::iterator it = server.players.begin(); it != server.players.end(); it++) {
-        NetworkPacket player_pkt;
+        NetworkMessage player_msg(*this, NETMSG_PLAYER_INFO);
         NetworkServerPlayer *player = *it;
         
         // player is not in players list yet
         assert(player != this);
         
         // write packet
-        player_pkt.write_vector(player->getPosition());
-
-        player->send_to(node, NETMSG_PLAYER_INFO, player_pkt, true);
+        player_msg.write_vector(player->getPosition());
+        
+        // send message to client
+        node.send_raw(player_msg);
 
         // XXX: send rope info...
     }
 
-    // XXX: send projectiles? Or let the client handle the events that the unknown projectiles generate?
+    // sync projectiles
+    for (std::list<Projectile*>::iterator it = server.state.projectiles.begin(); it != server.state.projectiles.end(); it++) {
+        NetworkServerProjectile *proj = dynamic_cast<NetworkServerProjectile*>(*it);
+        
+        // all projectiles should be under the control of the server
+        assert(proj);
+
+        // XXX: send info
+    }
 
     // broadcast NETMSG_PLAYER_JOIN to all clients except current
-    this->send_all_except(NETMSG_PLAYER_JOIN, hello_pkt, node, true);
-
-    Engine::log(INFO, "net.server") << "Player joined: " << this << " from " << node->getRemoteAddress();
-}
+    NetworkMessage join_msg(*this, NETMSG_PLAYER_JOIN);
+    join_msg.write_vector(getPosition());
+    server.netsession.all_nodes_except(node).send_raw(join_msg);
 
+    Engine::log(INFO, "net.server") << "Player joined: " << this << " from " << node.getRemoteAddress();
+}
+        
 void NetworkServerPlayer::handleDig (Vector position, float radius) {
-    NetworkPacket pkt;
-
-    pkt.write_vector(position);
-    pkt.write_float32(radius);
+    NetworkMessage msg(*this, NETMSG_PLAYER_DIG);
 
     Engine::log(DEBUG, "server_player.handle_dig") << "position=" << position << ", radius=" << radius;
+
+    // write packet
+    msg.write_vector(position);
+    msg.write_float32(radius);
     
-    // tell everyone... make this reliable... 
-    this->send_all(NETMSG_PLAYER_DIG, pkt, true);
+    // broadcast packet
+    server.netsession.all_nodes().send_raw(msg);
 
     // and carry out the actual dig on the server as well
     Player::handleDig(position, radius);
@@ -151,94 +165,100 @@
 }
 
 void NetworkServerPlayer::handleChangeWeapon (unsigned int weaponIndex) {
-    NetworkPacket pkt;
+    NetworkMessage msg(*this, NETMSG_PLAYER_WEAPON_CHANGE);
 
     Engine::log(DEBUG, "server_player.change_weapon") << "weaponIndex=" << weaponIndex;
     
     // write packet
-    pkt.write_uint8(weaponIndex);
+    msg.write_uint8(weaponIndex);
     
     // XXX: only tell the client itself?
-    send_all(NETMSG_PLAYER_WEAPON_CHANGE, pkt, true);
+    server.netsession.all_nodes().send_raw(msg);
     
     // pass through
     Player::handleChangeWeapon(weaponIndex);
 }
         
 void NetworkServerPlayer::handleRopeState (RopeState state) {
-    NetworkPacket pkt; 
-
     Engine::log(DEBUG, "server_player.rope_state") << "state=" << rope.getState() << ", position=" << rope.getPosition() << ", velocity=" << rope.getVelocity() << ", length=" << rope.getLength() << ", pivotPlayer=" << rope.getPivotPlayer();
 
     switch (state) {
-    case ROPE_FLYING:
-        pkt.write_vector(rope.getPosition());
-        pkt.write_vector(rope.getVelocity());
-        pkt.write_float32(rope.getLength());
+    case ROPE_FLYING: {
+        NetworkMessage msg(*this, NETMSG_PLAYER_ROPE_THROW);
 
-        send_all(NETMSG_PLAYER_ROPE_THROW, pkt, true);
+        msg.write_vector(rope.getPosition());
+        msg.write_vector(rope.getVelocity());
+        msg.write_float32(rope.getLength());
         
-        break;
+        server.netsession.all_nodes().send_raw(msg);
+       
+        } break;
 
     case ROPE_FIXED: {
+        NetworkMessage msg(*this, NETMSG_PLAYER_ROPE_FIXED);
+
         Player *player_base = rope.getPivotPlayer();
         NetworkServerPlayer *player = NULL;
 
         if (player_base != NULL && (player = dynamic_cast<NetworkServerPlayer*>(player_base)) == NULL)
             throw Error("NetworkServerPlayer::handleRopeState: rope's pivotPlayer is not a NetworkServerPlayer");
         
-        pkt.write_vector(rope.getPosition());
-        pkt.write_float32(rope.getLength());
-        controller.write_object(pkt, player);    // may be NULL
+        msg.write_vector(rope.getPosition());
+        msg.write_float32(rope.getLength());
+        controller.write_object(msg, player);    // may be NULL
         
-        send_all(NETMSG_PLAYER_ROPE_FIXED, pkt, true);
+        server.netsession.all_nodes().send_raw(msg);
 
-    } break;
+        } break;
     
-    case ROPE_FOLDED:
-        send_all(NETMSG_PLAYER_ROPE_RELEASED, pkt, true);
+    case ROPE_FOLDED: {
+        NetworkMessage msg(*this, NETMSG_PLAYER_ROPE_RELEASED);
 
-        break;
+        server.netsession.all_nodes().send_raw(msg);
+
+        } break;
     }
 }
         
 void NetworkServerPlayer::handleRopeLength (float length) {
-    NetworkPacket pkt;
-
-    pkt.write_float32(length);
-
-    send_all(NETMSG_PLAYER_ROPE_LENGTH, pkt, true);
+    NetworkMessage msg(*this, NETMSG_PLAYER_ROPE_LENGTH);
+    
+    // write packet
+    msg.write_float32(length);
+        
+    // send packet
+    server.netsession.all_nodes().send_raw(msg);
 }
 
 void NetworkServerPlayer::spawn (Vector position) {
-    NetworkPacket pkt;
+    NetworkMessage msg(*this, NETMSG_PLAYER_SPAWN);
     
     // write packet
-    pkt.write_vector(position);
+    msg.write_vector(position);
 
     Engine::log(DEBUG, "server_player.spawn") << this << ": position=" << position;
     
-    // send
-    send_all(NETMSG_PLAYER_SPAWN, pkt, true);
+    // send packet
+    server.netsession.all_nodes().send_raw(msg);
     
     // super
     Player::spawn(position);
 }
         
 void NetworkServerPlayer::die (bool start_timer) {
-    NetworkPacket pkt;
+    NetworkMessage msg(*this, NETMSG_PLAYER_DIE);
 
     Engine::log(DEBUG, "server_player.die") << this;
     
-    // send
-    send_all(NETMSG_PLAYER_DIE, pkt, true);
+    // send packet
+    server.netsession.all_nodes().send_raw(msg);
 
     // super
     Player::die(start_timer);
 }
 
 void NetworkServerPlayer::on_disconnected (void) {
-    NetworkPacket pkt;
+    NetworkMessage msg(*this, NETMSG_PLAYER_QUIT);
     
     Engine::log(INFO, "net.server") << "Player disconnected: " << this;
     
@@ -246,16 +266,16 @@
     server.handle_disconnect(this);
     
     // tell other clients
-    send_all(NETMSG_PLAYER_QUIT, pkt, true);
+    server.netsession.all_nodes().send_raw(msg);
 
-    // free
+    // XXX: free
 //    delete this;
 }
 
 void NetworkServerPlayer::on_input (NetworkNode *src, NetworkPacketInput &pkt) {
     // sanity-check, other players shouldn't move
-    if (src != node) {
-        Engine::log(WARN, "server_player.on_input") << "packet from wrong src=" << src << ", node=" << node;
+    if (src != &node) {
+        Engine::log(WARN, "server_player.on_input") << "packet from wrong src=" << src << ", node=" << &node;
         return;
     }
     
@@ -273,20 +293,20 @@
 }
         
 void NetworkServerPlayer::send_position_update (void) {
-    NetworkPacket pkt;
+    NetworkMessage msg(*this, NETMSG_PLAYER_POSITION);
     
     int flags = 
         (inAir ? NETWORK_PHYSICS_INAIR : 0) | 
         (facing == FACING_RIGHT ? NETWORK_PHYSICS_FACE_RIGHT : 0);
 
-    pkt.write_vector(getPosition());
-    pkt.write_vector(getVelocity());
-    pkt.write_uint8(flags);
-    pkt.write_float32(aim);
+    msg.write_vector(getPosition());
+    msg.write_vector(getVelocity());
+    msg.write_uint8(flags);
+    msg.write_float32(aim);
 
 //    Engine::log(INFO, "server_player.send_position_update") << "obj=" << obj << " -> " << position << "+" << velocity << " [" << flags << "]";
 
-    send_all(NETMSG_PLAYER_POSITION, pkt, false);
+    server.netsession.all_nodes().send_raw(msg);
 }
 
 /* 
@@ -296,25 +316,29 @@
         Vector velocity, Weapon *weapon) :
     Projectile(player, position, velocity, weapon, true), NetworkServerObject(server)
 {
-    NetworkPacket pkt;
+    NetworkMessage msg(*this, NETMSG_PROJECTILE_PLAYER_FIRED);
+
+    // write out packet
+    server.controller.write_object(msg, player);
+    msg.write_vector(getPosition());
+    msg.write_vector(getVelocity());
+    msg.write_uint8(weapon->getID());
     
-    server.controller.write_object(pkt, player);
-    pkt.write_vector(position);
-    pkt.write_vector(velocity);
-    pkt.write_uint8(weapon->getID());
-
-    send_all(NETMSG_PROJECTILE_PLAYER_FIRED, pkt, true);
+    // send to given target
+    server.netsession.all_nodes().send_raw(msg);
 }
 
 void NetworkServerProjectile::onDestroy (Vector position, bool removeGround) {
-    NetworkPacket pkt;
+    NetworkMessage msg(*this, NETMSG_PROJECTILE_DESTROY);
 
     Engine::log(DEBUG, "server_projectile.destroy") << this << "position=" << position << ", removeGround=" << removeGround;
     
-    pkt.write_vector(position);
-    pkt.write_uint8(removeGround ? NETWORK_PROJECTILE_REMOVE_GROUND : 0);
-
-    send_all(NETMSG_PROJECTILE_DESTROY, pkt, true);
+    // write packet
+    msg.write_vector(position);
+    msg.write_uint8(removeGround ? NETWORK_PROJECTILE_REMOVE_GROUND : 0);
+    
+    // broadcast packet
+    server.netsession.all_nodes().send_raw(msg);
 
     // XXX: leak obj, not yet implemented:  obj.destory();
     
@@ -323,7 +347,7 @@
 }
 
 void NetworkServerProjectile::onHitPlayer (Player *player_ptr) {
-    NetworkPacket pkt;
+    NetworkMessage msg(*this, NETMSG_PROJECTILE_HIT_PLAYER);
     NetworkServerPlayer *player = dynamic_cast<NetworkServerPlayer*>(player_ptr);
 
     if (player == NULL) 
@@ -332,10 +356,10 @@
     Engine::log(DEBUG, "server_projectile.hit_player") << this << ": player=" << player;
     
     // write packet
-    controller.write_object(pkt, player);
+    controller.write_object(msg, player);
 
-    // send
-    send_all(NETMSG_PROJECTILE_HIT_PLAYER, pkt, true);
+    // send packet
+    server.netsession.all_nodes().send_raw(msg);
 
     // super
     Projectile::onHitPlayer(player_ptr);
--- a/src/Network/Server.hh	Mon Jan 26 19:52:52 2009 +0200
+++ b/src/Network/Server.hh	Mon Jan 26 23:03:47 2009 +0200
@@ -72,7 +72,7 @@
          * Called from on_node_connected to send the initial Terrain data using the NETCHAN_TERRAIN_ARRAY channel to
          * the given node.
          */
-        void send_terrain_data (NetworkNode *node);
+        void send_terrain_data (NetworkNode &node);
 };
 
 /**
@@ -101,7 +101,7 @@
         /**
          * The remote node that represents this actual player
          */
-        NetworkNode *node;
+        NetworkNode &node;
 
     public:
         /**
@@ -113,7 +113,7 @@
          *
          * @see NetworkServerObject
          */
-        NetworkServerPlayer (NetworkServer &server, NetworkNode *node);
+        NetworkServerPlayer (NetworkServer &server, NetworkNode &node);
     
     protected:    
         // @{
@@ -167,7 +167,7 @@
          * @param weapon the Player's Weapon that was fired
          */
         NetworkServerProjectile (NetworkServer &server, NetworkServerPlayer *player, Vector position, Vector velocity, Weapon *weapon);
-    
+   
     protected:
         /**
          * Overriden from Projectile to send a NETMSG_PROJECTILE_DESTROY with the given position and flags
--- a/src/Network/Session.cc	Mon Jan 26 19:52:52 2009 +0200
+++ b/src/Network/Session.cc	Mon Jan 26 23:03:47 2009 +0200
@@ -47,7 +47,8 @@
     NetworkNode *client_node = build_node(tcp_client, udp_client, remote_address, NETWORK_NODE_CLIENT_SERVER);
 
     // add to nodes
-    nodes[remote_address] = client_node;
+    node_map[remote_address] = client_node;
+    node_list.push_back(client_node);
 
     // connect signals
     slots.connect(udp_client->sig_packet(), this, &NetworkSession::on_udp_packet);
@@ -58,7 +59,8 @@
                 
 void NetworkSession::handle_disconnect (NetworkNode *node) {
     // remove from nodes
-    nodes.erase(node->getRemoteAddress());
+    node_map.erase(node->getRemoteAddress());
+    node_list.remove(node);
 }
         
 void NetworkSession::handle_message (NetworkPacketInput &pkt, NetworkNode *node) {
@@ -77,14 +79,15 @@
     NetworkNode *client_node = build_node(tcp_client, udp_srv, addr, NETWORK_NODE_SERVER_CLIENT);
 
     // add to nodes
-    nodes[addr] = client_node;
+    node_map[addr] = client_node;
+    node_list.push_back(client_node);
     
     // fire signals
     _sig_node_connected(client_node);
 }
         
 void NetworkSession::on_udp_packet (NetworkPacketInput &pkt, const NetworkAddress &addr) {
-    NetworkNode *node = nodes[addr];
+    NetworkNode *node = node_map[addr];
     
     // drop from unknown sources
     if (!node) {
@@ -95,16 +98,8 @@
     // handle
     handle_message(pkt, node);
 }
-       
-void NetworkSession::send_all (NetworkChannelID channel_id, const NetworkPacketBuffer &pkt, bool reliable) {
-    send_all_except(channel_id, pkt, NULL, reliable);
+
+void NetworkSession::write_packet_header (NetworkPacketOutput &pkt, NetworkChannelID channel_id) {
+    pkt.write_uint16(channel_id);
 }
-        
-void NetworkSession::send_all_except (NetworkChannelID channel_id, const NetworkPacketBuffer &pkt, const NetworkNode *node, bool reliable) {
-    for (std::map<NetworkAddress, NetworkNode*>::iterator it = nodes.begin(); it != nodes.end(); it++) {
-        if (it->second == node)
-            continue;
 
-        it->second->send(channel_id, pkt, reliable);
-    }
-}
--- a/src/Network/Session.hh	Mon Jan 26 19:52:52 2009 +0200
+++ b/src/Network/Session.hh	Mon Jan 26 23:03:47 2009 +0200
@@ -14,14 +14,6 @@
 class NetworkSession;
 
 /**
- * A NetworkSession puts each packet onto a specific channel, which can the be used to run multiple different modules
- * on top of a single session.
- *
- * NetworkChannelID zero is reserved for internal NetworkSession use
- */
-typedef uint16_t NetworkChannelID;
-
-/**
  * Size of a NetworkSession's packet header:
  *  uint16      channel_id
  */
@@ -30,6 +22,9 @@
 #include "TCP.hh"
 #include "UDP.hh"
 #include "Node.hh"
+#include "Group.hh"
+#include "Packet.hh"
+#include "Channel.hh"
 
 /**
  * A NetworkSession provides TCP/UDP Server and Client functionality, representing remote NetworkSessions with
@@ -65,7 +60,12 @@
          * A map of NetworkAddress -> NetworkNode, manipulated when TCP connections are established/broken down,
          * and used to map UDP packets to their NetworkNode
          */
-        std::map<NetworkAddress, NetworkNode*> nodes;
+        std::map<NetworkAddress, NetworkNode*> node_map;
+
+        /**
+         * A plain list of connected NetworkNode's
+         */
+        std::list<NetworkNode*> node_list;
 
         /**
          * A map of NetworkChannelID -> signal, used to signal our users when we receieve packets
@@ -146,21 +146,32 @@
 
     public:
         /**
-         * Send the given NetworkPacket to all our nodes using the given NetworkChannelID, using TCP if reliable, UDP otherwise.
-         *
-         * @param channel_id the NetworkChannelID to use
-         * @param pkt the NetworkPacket to send
-         * @param reliable Whether to use TCP or UDP
+         * Get a NetworkGroup containing all connected nodes
          */
-        void send_all (NetworkChannelID channel_id, const NetworkPacketBuffer &pkt, bool reliable = true);
+        NetworkGroup all_nodes (void) {
+            return NetworkGroup(node_list.begin(), node_list.end(), NULL);
+        }
 
         /**
-         * Like send_all, but do not send the packet to the specified node. If node is NULL, this behaves like
-         * send_all.
+         * Get a NetworkGroup containing all connected nodes, but excluding the given one
+         */
+        NetworkGroup all_nodes_except (NetworkNode &node) {
+            return NetworkGroup(node_list.begin(), node_list.end(), &node);
+        }
+
+        /**
+         * Write the appropriate NetworkSession header to the given packet. Can be used to prepare packets for use with
+         * NetworkTarget::send_raw(). The size of the header is equal to NETWORK_SESSION_HEADER_SIZE
          *
-         * @see send_all
+         * XXX: this interface needs fixing to be less procedural
+         *
+         * @param pkt the packet to write the header to
+         * @param channel_id the NetworkChannelID to use
+         *
+         * @see NetworkTarget::send_raw()
+         * @see NETWORK_SESSION_HEADER_SIZE
          */
-        void send_all_except (NetworkChannelID channel_id, const NetworkPacketBuffer &pkt, const NetworkNode *node, bool reliable = true);
+        void write_packet_header (NetworkPacketOutput &pkt, NetworkChannelID channel_id);
 
         /**
          * A new node has connected to us
--- a/src/Network/TCP.cc	Mon Jan 26 19:52:52 2009 +0200
+++ b/src/Network/TCP.cc	Mon Jan 26 23:03:47 2009 +0200
@@ -120,6 +120,9 @@
     // accept a new socket
     NetworkSocket *client_sock = socket.accept(NULL);
 
+    // make it nonblocking
+    client_sock->set_nonblocking(true);
+
     // create a new NetworkTCPTransport
     NetworkTCPTransport *client = buildTransport(client_sock);
         
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Network/Target.cc	Mon Jan 26 23:03:47 2009 +0200
@@ -0,0 +1,24 @@
+
+#include "Target.hh"
+
+#include <cassert>
+
+void NetworkTarget::send_raw (const NetworkPacketBuffer &pkt, bool reliable) {
+    // currently this is the same as send_pkt
+    send_pkt(pkt, reliable);
+}
+
+void NetworkTarget::send (NetworkChannelID channel_id, const NetworkPacketBuffer &pkt, bool reliable) {
+    assert(channel_id > 0);
+    
+    // add our header
+    NetworkPacket pkt2;
+    
+    // XXX: not using NetworkSession::write_packet_header
+    pkt2.write_uint16(channel_id);
+    pkt2.write_packet(pkt);
+    
+    // send
+    send_pkt(pkt2, reliable);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Network/Target.hh	Mon Jan 26 23:03:47 2009 +0200
@@ -0,0 +1,52 @@
+#ifndef NETWORK_TARGET_HH
+#define NETWORK_TARGET_HH
+
+/**
+ * @file
+ *
+ * Abstract NetworkTarget definition
+ */
+
+#include "Channel.hh"
+#include "Packet.hh"
+
+/**
+ * Abstract interface for sending NetworkPackets to some NetworkSession-related destination, either a single
+ * NetworkNode or a NetworkGroup of nodes.
+ */
+class NetworkTarget {
+    friend class NetworkGroup;
+
+    protected:
+        /**
+         * Send the given packet directly
+         */
+        virtual void send_pkt (const NetworkPacketBuffer &pkt, bool reliable = true) = 0;
+
+    public:        
+        /**
+         * Send a raw packet prepared using write_packet_header. This does not need to copy the packet data around.
+         *
+         * XXX: this interface needs fixing to be less procedural
+         *
+         * @param pkt the NetworkPacket prepared using write_packet_header
+         * @param reliable whether to use TCP or UDP
+         *
+         * @see write_packet_header
+         */
+        void send_raw (const NetworkPacketBuffer &pkt, bool reliable = true);
+
+        /**
+         * Send the given packet on the given channel to this destination.
+         *
+         * Note that this constructs a new *NetworkPacket* containing our header and the given packet, so there
+         * given packet must be small enough to fit.
+         *
+         * @param channel_id the NetworkChannelID to use
+         * @param pkt the NetworkPacket to send on the given channel
+         * @param reliable Whether to use TCP or UDP
+         */
+        void send (NetworkChannelID channel_id, const NetworkPacketBuffer &pkt, bool reliable = true);
+};
+
+#endif /* NETWORK_TARGET_HH */