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 ©); |
63 NetworkBufferBase& operator= (const NetworkBufferBase ©); |
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 |