IP and port are not reliable identifiers of client instance. Especially if you allow more than one client to run on a machine, or are working through NAT.
EDIT: I stand corrected, see below.
[Edited by - _winterdyne_ on November 16, 2005 1:01:51 PM]
Packet Protocol/layout
An alternate approach might be something like:
To send a packet, you'd keep concatenating Message chunks out of your queue until you reach the threshold for packet size (or the queue is empty), and then send the packet.
When you receive the packet, you would decode messages in it, switch on the code, and call the appropriate handler, casting the Message to the right struct kind (this is easily implementable using a macro, or hard-to-implement using template metaprogramming).
Btw: IP + port is guaranteed to be unique to a single session, even when you're behind NAT. The reason is that, when you return a packet to the NAT, the NAT needs to know what internal machine to forward the packet to, so it allocates one external port per internal connection. It's still a good idea to add an authentication token of some sort, though.
enum MessageCode { CodeNull = 0, CodeLogin = 1,};struct MessageHeader { unsigned short dataSize; // including header unsigned short code;};struct LoginMessage : MessageHeader { LoginMessage() { code = CodeLogin; dataSize = sizeof(*this); } char name[16]; char password[16];};class Message {public: void setData( void const * data, size_t size ) { data_.resize( size ); memcpy( &data_[0], data, size ); } void * data() { return &data_[0]; } size_t size() { return data_.size(); }protected: std::vector data_;};class MessageQueue {public: void addMessage( void const * src, size_t size ) { queue_.push_back( Message() ); queue_.back().setData( src, size ); } void addMessage( Message const & m ) { addMessage( &m, m.dataSize ); } bool firstMessage( void const * & oData, size_t & oSize ) { if( !queue_.size() ) return false; oData = queue_[0].data(); oSize = queue_[0].size(); return true; } void removeFirst() { if( queue_.size() ) queue_.pop_front(); }protected: std::deque queue_;};
To send a packet, you'd keep concatenating Message chunks out of your queue until you reach the threshold for packet size (or the queue is empty), and then send the packet.
When you receive the packet, you would decode messages in it, switch on the code, and call the appropriate handler, casting the Message to the right struct kind (this is easily implementable using a macro, or hard-to-implement using template metaprogramming).
#define DOMESSAGE( t ) \ case Code ## t : \ if( mh.dataSize != sizeof( Message ## t ) ) { \ return; // drop badly formatted message \ } \ handle ## t ( static_cast< Message ## t const & >( mh ) ); \ break;void DispatchMessage( Message & m ) { MessageHeader const & mh = *(MessageHeader *)m.data(); assert( mh.dataSize == m.size() ); switch( mh.code ) { DOMESSAGE( Null ) DOMESSAGE( Login ) ... default: // drop message on the floor; unknown kind }}
Btw: IP + port is guaranteed to be unique to a single session, even when you're behind NAT. The reason is that, when you return a packet to the NAT, the NAT needs to know what internal machine to forward the packet to, so it allocates one external port per internal connection. It's still a good idea to add an authentication token of some sort, though.
enum Bool { True, False, FileNotFound };
> Btw: IP + port is guaranteed to be unique to a single
> session, even when you're behind NAT.
The problem with some router NATs is that they tend to reset unused UDP port bindings if there is no traffic for a few minutes. It's not a problem for TCP but with UDP being connection-less a router can never tell if the communication is closed or not. It's a good idea to schedule "keep alive" packets every 30 seconds to keep the router's NAT port binding allocated.
-cb
> session, even when you're behind NAT.
The problem with some router NATs is that they tend to reset unused UDP port bindings if there is no traffic for a few minutes. It's not a problem for TCP but with UDP being connection-less a router can never tell if the communication is closed or not. It's a good idea to schedule "keep alive" packets every 30 seconds to keep the router's NAT port binding allocated.
-cb
When using the approaching of mapping structs directly on to the wire buffer, make damn sure you have a very good understanding of precisely how your compiler lays out structures in memory. Especially when you start throwing C++ and inheritence into the mix. If any of the following phrases is the least bit unclear in your mind you have more studying to do: "mapping structs directly on to the wire buffer", "structure packing", "member alignment", "external padding". For C++ add "POD" (aka "plain-old data") and "vptr" (aka, why you shouldn't use virtual functions in any classes/structs that are doing this sort of thing).
Having said all that, once you do have it understood it's a very elegant method. I'd sprinkle a few static asserts into hplus's code to validate my assumptions.
Having said all that, once you do have it understood it's a very elegant method. I'd sprinkle a few static asserts into hplus's code to validate my assumptions.
-Mike
Yeah, all my packet and message structures are byte-aligned structs, with variable length data as the last field.
Winterdyne Solutions Ltd is recruiting - this thread for details!
I'm a bit lost to whether this is TCP or UDP.. I'm easily confused :(. All of this is so much more complex than what I've done so far, which generally works other than the problems with the first character.
Also, is the WSNET the .NET version?
Also, is the WSNET the .NET version?
The code I put up is the basis for a reliable message system. The actual means of packet delivery doesn't matter. My socket class can operate in TCP or UDP modes, with pretty much the same interface.
I tend to use it in UDP mode though.
WSNet is the prefix for all classes in my WSNet DLL. WS = Winterdyne Solutions.
Gah! Except WSPacketEntry... bad Winter, no twinkie... :-p Better change that now other people are looking at my code...
WSNetBase is an adaptation from the NetBase class in planeshift. I liked their UDP system in general, so I took the good stuff, fixed a few bugs (memory logging although slow is a good idea providing you can turn it off) and adapted it to a common style with my other libraries.
I suppose really it should be a namespace, but I've run into fun with people forgetting a 'using' keyword.
[Edited by - _winterdyne_ on November 22, 2005 11:51:15 AM]
I tend to use it in UDP mode though.
WSNet is the prefix for all classes in my WSNet DLL. WS = Winterdyne Solutions.
Gah! Except WSPacketEntry... bad Winter, no twinkie... :-p Better change that now other people are looking at my code...
WSNetBase is an adaptation from the NetBase class in planeshift. I liked their UDP system in general, so I took the good stuff, fixed a few bugs (memory logging although slow is a good idea providing you can turn it off) and adapted it to a common style with my other libraries.
I suppose really it should be a namespace, but I've run into fun with people forgetting a 'using' keyword.
[Edited by - _winterdyne_ on November 22, 2005 11:51:15 AM]
Winterdyne Solutions Ltd is recruiting - this thread for details!
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement
Recommended Tutorials
Advertisement