src/Network/Buffer.hh
changeset 399 c7295b72731a
parent 380 d193dd1d8a7e
child 400 d64bf28c4340
equal deleted inserted replaced
398:306825786fba 399:c7295b72731a
     1 #ifndef NETWORK_BUFFER_HH
     1 #ifndef NETWORK_BUFFER_HH
     2 #define NETWORK_BUFFER_HH
     2 #define NETWORK_BUFFER_HH
     3 
     3 
       
     4 /**
       
     5  * @file
       
     6  *
       
     7  * Buffering of network streams
       
     8  */
       
     9 
     4 #include "Socket.hh"
    10 #include "Socket.hh"
     5 #include "../Error.hh"
    11 #include "../Error.hh"
     6 
    12 
     7 #include <cassert>
    13 #include <cassert>
     8 
    14 
    18     public:
    24     public:
    19         NetworkBufferError (const std::string &message) : Error(message) { }
    25         NetworkBufferError (const std::string &message) : Error(message) { }
    20 };
    26 };
    21 
    27 
    22 /**
    28 /**
    23  * Base buffer-manipulation operations for buffered socket send/recv
    29  * Base buffer-manipulation operations for buffered socket send/recv.
       
    30  *
       
    31  * This implements a simple linear in-memory buffer which can contain data that must be buffered for send/recv.
       
    32  * New data can be written to the end of the buffer, and data can be read/discarded from the beginning.
       
    33  *
       
    34  * A buffer is associated with a NetworkSocket, and takes care of all send/recv activity.
    24  */
    35  */
    25 class NetworkBufferBase {
    36 class NetworkBufferBase {
    26     protected:
    37     protected:
    27         /** The socket that we use */
    38         /** The socket that we use */
    28         NetworkSocket *socket;
    39         NetworkSocket *socket;
    29         
    40         
    30         /** The buffer itself */
    41         /** The buffer itself */
    31         char *buf;
    42         char *buf;
    32 
    43 
    33         /** Buffer size and current read/write offset */
    44         /** Buffer size and current write offset */
    34         size_t size, offset;
    45         size_t size, offset;
    35     
    46     
    36     public:
    47     public:
    37         /**
    48         /**
    38          * Allocate buf using the given initial size, and set offset to zero
    49          * Allocate buf using the given initial size, and set offset to zero
    52         NetworkBufferBase& operator= (const NetworkBufferBase &copy);
    63         NetworkBufferBase& operator= (const NetworkBufferBase &copy);
    53     
    64     
    54     protected:
    65     protected:
    55         /**
    66         /**
    56          * Resize the buffer, allocating enough new space to hold <item_size> bytes at the end, and leaving any
    67          * Resize the buffer, allocating enough new space to hold <item_size> bytes at the end, and leaving any
    57          * existing data at the beginning in-place
    68          * existing data at the beginning in-place.
       
    69          *
       
    70          * This is done by doubling the buffer size until item_size fits, and then reallocing (if needed).
       
    71          * Currently, the buffer is never shrunk, although it probably should be to adapt better to peak load (e.g.
       
    72          * when a player joins and the map data is transmitted...).
    58          *
    73          *
    59          * @param item_size the number of bytes that must fit at the end of the buffer
    74          * @param item_size the number of bytes that must fit at the end of the buffer
    60          */
    75          */
    61         void resize (size_t item_size);
    76         void resize (size_t item_size);
    62 
    77 
    63         /**
    78         /**
    64          * Trim the buffer, discarding <prefix_size> bytes at the beginning. Updates offset to match.
    79          * Trim the buffer, discarding <prefix_size> bytes at the beginning. Updates offset to match.
    65          *
    80          *
       
    81          * Currently, this is implemented as a simple memmove() of all the remaining data in the buffer, which may be
       
    82          * slow under some load patterns.
       
    83          *
    66          * @param prefix_size the number of bytes to discard
    84          * @param prefix_size the number of bytes to discard
    67          */
    85          */
    68         void trim (size_t prefix_size);
    86         void trim (size_t prefix_size);
    69 };
    87 };
    70 
    88 
    71 /**
    89 /**
    72  * Buffered prefix-len socket input
    90  * Buffered prefix-len socket input
       
    91  *
       
    92  * This handles calling recv() on the socket, and is specialized to handle a stream of messages prefixed with a length
       
    93  * header (uint16_t or uint32_t). Use the peek_data() method to receive these messages when NetworkSocket::sig_read() 
       
    94  * indicates that more data is available.
    73  */
    95  */
    74 class NetworkBufferInput : public NetworkBufferBase {
    96 class NetworkBufferInput : public NetworkBufferBase {
    75     public:
    97     public:
    76         /**
    98         /**
    77          * @see NetworkBufferBase
    99          * @see NetworkBufferBase
   108         bool peek_prefix (uint16_t &val_ref);
   130         bool peek_prefix (uint16_t &val_ref);
   109         bool peek_prefix (uint32_t &val_ref);
   131         bool peek_prefix (uint32_t &val_ref);
   110         // @} 
   132         // @} 
   111         
   133         
   112         /**
   134         /**
   113          * This attempts to read a length-prefix of the given type (using peek_prefix), and then the associated data.
   135          * This attempts to locate a full message in the buffer (calling recv as nessecary), prefixed with a length
   114          * If succesful, this sets prefix to the length of the data, and buf_ref to point at the data inside our buffer
   136          * prefix of the given type (using peek_prefix). If succesfull (we have a full message in the buffer), 
   115          * and returns true, else false.
   137          * \a prefix will be updated to the length of the message (not including the prefix), and \a buf_ref will be
   116          *
   138          * updated to point at the message of \a prefix bytes (not including the prefix) in our internal buffer memory.
   117          * This will try and consume data from the buffer, or recv if needed.
   139          * If a full message could not be received (recv would block), this will return false.
   118          *
   140          *
   119          * @param prefix stores the data length here
   141          * Once you have processed the message, call flush_data() to remove the unused message from the buffer.
   120          * @param buf_ref stores a pointer to the data here
   142          *
   121          * @return bool true if we have the full data, false if we need to wait for more data on the socket
   143          * @param prefix updated to the message length
       
   144          * @param buf_ref updated to point at the message data
       
   145          * @return bool true if we have a full message, false if we need to wait for more data on the socket
   122          *
   146          *
   123          * @see peek_prefix
   147          * @see peek_prefix
   124          * @see flush_data
   148          * @see flush_data
   125          */
   149          */
   126         template <typename PrefixType> bool peek_data (PrefixType &prefix, char *&buf_ref) {
   150         template <typename PrefixType> bool peek_data (PrefixType &prefix, char *&buf_ref);
   127             size_t missing = 0;
       
   128             
       
   129             do {    
       
   130                 // do we have the prefix?
       
   131                 if (peek_prefix(prefix)) {
       
   132                     // do we already have the payload?
       
   133                     if (offset >= sizeof(PrefixType) + prefix) {
       
   134                         break;
       
   135 
       
   136                     } else {
       
   137                         missing = (sizeof(PrefixType) + prefix) - offset;
       
   138                     }
       
   139 
       
   140                 } else {
       
   141                     missing = sizeof(PrefixType);
       
   142                 }
       
   143 
       
   144                 // sanity-check
       
   145                 // XXX: a zero-prefix will trigger this
       
   146                 assert(missing);
       
   147                 
       
   148                 // try and read the missing data
       
   149                 if (try_read(missing) == false) {
       
   150                     // if unable to read what we need, return zero.
       
   151                     return false;
       
   152                 }
       
   153                 
       
   154                 // assess the situation again
       
   155             } while (true);
       
   156             
       
   157             // update the buf_ref to point past the prefix-length
       
   158             buf_ref = buf + sizeof(PrefixType);
       
   159 
       
   160             // return
       
   161             return true;
       
   162         }
       
   163           
   151           
   164         /**
   152         /**
   165          * This flushes a prefix-length worth of data from the buffer, i.e. it first reads the prefix, and then trims
   153          * This flushes the current message from the buffer, as returned when peek_data returns true. It is a bug to
   166          * the prefix and the data away. Don't call this unless you *know* that the buffer contains enough data.
   154          * call flush_data when there is no full message present, the behaviour is unspecified (most likely an
       
   155          * assert()).
   167          *
   156          *
   168          * @see peek_data
   157          * @see peek_data
   169          */
   158          */
   170         template <typename PrefixType> void flush_data (void) {
   159         template <typename PrefixType> void flush_data (void);
   171             PrefixType prefix;
   160 };
   172             
   161 
   173             // we *must* have a valid prefix
   162 /**
   174             if (!peek_prefix(prefix))
   163  * Buffered prefix-len socket output.
   175                 assert(false);
   164  *
   176 
   165  * This handles calling send() on the socket, and is specialized to handle a stream of message prefixed with a length
   177             // trim the bytes out
   166  * header (uint16_t or uint32_t). You can write messages using write_prefix(), and they will be buffered if needed.
   178             trim(sizeof(PrefixType) + prefix);
   167  * If write_prefix() returns true (socket buffer full), then you must register the socket for
   179         }
   168  * NetworkSocket::set_poll_write(), and call flush_write() once NetworkSocket::sig_write() is triggered. The socket's
   180 };
   169  * poll_write should be unregistered once flush_write returns false (no more buffered data remaining to be sent), or
   181 
   170  * an many processor cycles will be wasted, for the socket will remain ready for write until its buffer fills up again.
   182 /**
   171  *
   183  * Buffered prefix-len socket output
   172  * Data is not buffered needlessly; if the socket's buffer has room, write_prefix will not have to touch our internal
       
   173  * buffer. In fact, write_prefix will rarely return false except under heavy network congestion with high levels of
       
   174  * traffic on the socket.
   184  */
   175  */
   185 class NetworkBufferOutput : public NetworkBufferBase {
   176 class NetworkBufferOutput : public NetworkBufferBase {
   186     public:
   177     public:
   187         /**
   178         /**
   188          * @see NetworkBufferBase
   179          * @see NetworkBufferBase
   189          */
   180          */
   190         NetworkBufferOutput (NetworkSocket *socket, size_t size_hint);
   181         NetworkBufferOutput (NetworkSocket *socket, size_t size_hint);
   191 
   182 
   192     private:
   183     private:
   193         /**
   184         /**
   194          * Write the given data to the socket, either now of later. 
   185          * Write the given data to the socket, either now or later. 
   195          *
   186          *
   196          * If our buffer is empty, fast-path the given buf_ptr directly to send(), then copy the remaining portion to
   187          * If our buffer is empty, fast-path the given buf_ptr directly to send(), then copy the remaining portion to
   197          * our buffer for later use with flush_write.
   188          * our buffer for later use with flush_write.
   198          *
   189          *
   199          * @param buf_ptr the data that we need to send
   190          * @param buf_ptr the data that we need to send
   201          */
   192          */
   202         void push_write (char *buf_ptr, size_t buf_size);
   193         void push_write (char *buf_ptr, size_t buf_size);
   203    
   194    
   204     public:    
   195     public:    
   205         /**
   196         /**
   206          * If we have data in our buffer, flush it out using send().
   197          * If we have data in our buffer, flush it out using send(). This should be called once the socket indicates it
   207          *
   198          * is ready for write again after a call to write_prefix that returned false. Once this returns false, the
   208          * @return true if there's still buffered data left to write, false otherwise
   199          * NetworkSocket::set_poll_write() flag should be unset again to avoid needless calls to this.
       
   200          *
       
   201          * @return true if there's still buffered data left to write, false otherwise (buffer is empty)
   209          */
   202          */
   210         bool flush_write (void);
   203         bool flush_write (void);
   211         
   204         
   212         // @{
   205         // @{
   213         /**
   206         /**
   214          * Write out the given data, writing first the prefix, and then the data itself, using push_write.
   207          * Write out the given message, writing first the prefix, and then the data itself, using push_write.
       
   208          *
       
   209          * Returns true if the data was passed on to the socket API directly, false if a portion of it had to be
       
   210          * buffered (if there is already data buffered, all subsequent write_prefix's will buffer the full message
       
   211          * until our buffer is empty again). If this method returns false, you must register the socket for
       
   212          * poll_write and call flush_write later.
   215          * 
   213          * 
   216          * @param buf the data to write
   214          * @param buf the data to write
   217          * @param prefix the amount of data
   215          * @param prefix the amount of data
   218          * @return true if we had to buffer data, false otherwise
   216          * @return true if we had to buffer data, false otherwise
   219          */
   217          */
   220         bool write_prefix (char *buf, uint16_t prefix);
   218         bool write_prefix (char *buf, uint16_t prefix);
   221         bool write_prefix (char *buf, uint32_t prefix);
   219         bool write_prefix (char *buf, uint32_t prefix);
   222         // @}
   220         // @}
   223 };
   221 };
   224 
   222 
       
   223 
       
   224 /*
       
   225  * NetworkBufferInput template method implementation
       
   226  */
       
   227 template <typename PrefixType> bool NetworkBufferOutput::peek_data (PrefixType &prefix, char *&buf_ref) {
       
   228     size_t missing = 0;
       
   229     
       
   230     do {    
       
   231         // do we have the prefix?
       
   232         if (peek_prefix(prefix)) {
       
   233             // do we already have the payload?
       
   234             if (offset >= sizeof(PrefixType) + prefix) {
       
   235                 break;
       
   236 
       
   237             } else {
       
   238                 missing = (sizeof(PrefixType) + prefix) - offset;
       
   239             }
       
   240 
       
   241         } else {
       
   242             missing = sizeof(PrefixType);
       
   243         }
       
   244 
       
   245         // sanity-check (above >= and - should never happen like this)
       
   246         assert(missing);
       
   247         
       
   248         // try and read the missing data
       
   249         if (try_read(missing) == false) {
       
   250             // if unable to read what we need, return zero.
       
   251             return false;
       
   252         }
       
   253         
       
   254         // assess the situation again
       
   255     } while (true);
       
   256     
       
   257     // update the buf_ref to point past the prefix-length
       
   258     buf_ref = buf + sizeof(PrefixType);
       
   259 
       
   260     // return message
       
   261     return true;
       
   262 }
       
   263 
       
   264 template <typename PrefixType> void NetworkBufferOutput::flush_data (void) {
       
   265     PrefixType prefix;
       
   266     
       
   267     // we *must* have a valid prefix
       
   268     if (!peek_prefix(prefix))
       
   269         assert(false);
       
   270 
       
   271     // ensure that we have the data to trim...
       
   272     assert(offset >= sizeof(PrefixType) + prefix);
       
   273 
       
   274     // trim the bytes out
       
   275     trim(sizeof(PrefixType) + prefix);
       
   276 }
       
   277 
   225 #endif
   278 #endif