Merge branch 'fixes' into main/rendor-staging
[ryzomcore.git] / nel / src / net / buf_sock.cpp
blob1ce08f3b4f50a83684caca86d20bedf1749f9868
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2014 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "stdnet.h"
22 #include "nel/misc/hierarchical_timer.h"
23 #include "nel/misc/variable.h"
24 #include "nel/net/buf_sock.h"
25 #include "nel/net/buf_server.h"
26 #include "nel/net/net_log.h"
30 #ifdef NL_OS_WINDOWS
31 # ifndef NL_COMP_MINGW
32 # define NOMINMAX
33 # endif
34 # include <windows.h>
35 #elif defined NL_OS_UNIX
36 # include <netinet/in.h>
37 #endif
39 using namespace NLMISC;
40 using namespace std;
42 NLMISC::CVariable<uint32> MaxTCPPacketSize("nel", "MaxTCPPacketSize", "Maximum size of TCP packets created by cumulating small packets", 10240, 0, true);
45 namespace NLNET {
48 NLMISC::CMutex nettrace_mutex("nettrace_mutex");
52 * Constructor
54 CBufSock::CBufSock( CTcpSock *sock ) :
55 Sock( sock ),
56 _KnowConnected( false ),
57 _LastFlushTime( 0 ),
58 _TriggerTime( 20 ),
59 _TriggerSize( -1 ),
60 _RTSBIndex( 0 ),
61 _AppId( 0 ),
62 _ConnectedState( false )
64 nlnettrace( "CBufSock::CBufSock" ); // don't define a global object
66 if ( Sock == NULL )
68 Sock = new CTcpSock();
71 #ifdef NL_DEBUG
72 _FlushTrigger = FTManual;
73 #endif
74 _LastFlushTime = CTime::getLocalTime();
79 * Destructor
81 CBufSock::~CBufSock()
83 nlassert (this != InvalidSockId); // invalid bufsock
85 nlnettrace( "CBufSock::~CBufSock" );
87 delete Sock; // the socket disconnects automatically if needed
89 // destroy the structur to be sure that other people will not access to this anymore
90 AuthorizedCallback.clear();
91 Sock = NULL;
92 _KnowConnected = false;
93 _LastFlushTime = 0;
94 _TriggerTime = 0;
95 _TriggerSize = 0;
96 _ReadyToSendBuffer.clear ();
97 _RTSBIndex = 0;
98 _AppId = 0;
99 _ConnectedState = false;
104 * Returns a readable string from a vector of bytes, beginning from pos, limited to 'len' characters. '\0' are replaced by ' '
106 string stringFromVectorPart( const vector<uint8>& v, uint32 pos, uint32 len )
108 nlassertex( pos+len <= v.size(), ("pos=%u len=%u size=%u", pos, len, v.size()) );
110 string s;
111 if ( (! v.empty()) && (len!=0) )
113 // Copy contents
114 s.resize( len );
115 memcpy( &*s.begin(), &*v.begin()+pos, len );
117 // Replace '\0' characters
118 string::iterator is;
119 for ( is=s.begin(); is!=s.end(); ++is )
121 if ( ! isprint((uint8)(*is)) || (*is) == '%' )
123 (*is) = '?';
128 return s;
133 * Force to send data pending in the send queue now. In the case of a non-blocking socket
134 * (see CNonBlockingBufSock), if all the data could not be sent immediately,
135 * the returned nbBytesRemaining value is non-zero.
136 * \param nbBytesRemaining If the pointer is not NULL, the method sets the number of bytes still pending after the flush attempt.
137 * \returns False if an error has occurred (e.g. the remote host is disconnected).
138 * To retrieve the reason of the error, call CSock::getLastError() and/or CSock::errorString()
140 * Note: this method works with both blocking and non-blocking sockets
141 * Precond: the send queue should not contain an empty block
143 bool CBufSock::flush( uint *nbBytesRemaining )
145 nlassert (this != InvalidSockId); // invalid bufsock
146 //nlnettrace( "CBufSock::flush" );
148 // Copy data from the send queue to _ReadyToSendBuffer
149 TBlockSize netlen;
150 // vector<uint8> tmpbuffer;
154 // Process each element in the send queue
155 uint8 *tmpbuffer = NULL;
156 uint32 size = 0;
157 if (! SendFifo.empty())
159 SendFifo.front( tmpbuffer, size );
161 while ( ! SendFifo.empty() && ( _ReadyToSendBuffer.empty() || (_ReadyToSendBuffer.size() +size < MaxTCPPacketSize) ) )
163 // Compute the size and add it into the beginning of the buffer
164 netlen = htonl( (TBlockSize)size );
165 uint32 oldBufferSize = _ReadyToSendBuffer.size();
166 _ReadyToSendBuffer.resize (oldBufferSize+sizeof(TBlockSize)+size);
167 *(TBlockSize*)&(_ReadyToSendBuffer[oldBufferSize])=netlen;
168 //nldebug( "O-%u %u+L%u (0x%x)", Sock->descriptor(), oldBufferSize, size, size );
171 // Append the temporary buffer to the global buffer
172 CFastMem::memcpy (&_ReadyToSendBuffer[oldBufferSize+sizeof(TBlockSize)], tmpbuffer, size);
173 SendFifo.pop();
174 if (! SendFifo.empty())
176 SendFifo.front( tmpbuffer, size );
180 // Actual sending of _ReadyToSendBuffer
181 //if ( ! _ReadyToSendBuffer.empty() )
182 if ( _ReadyToSendBuffer.size() != 0 )
184 // Send
185 CSock::TSockResult res;
186 TBlockSize len = _ReadyToSendBuffer.size() - _RTSBIndex;
188 res = Sock->send( _ReadyToSendBuffer.getPtr()+_RTSBIndex, len, false );
190 if ( res == CSock::Ok )
192 /* // Debug display
193 switch ( _FlushTrigger )
195 case FTTime : LNETL1_DEBUG( "LNETL1: Time triggered flush for %s:", asString().c_str() ); break;
196 case FTSize : LNETL1_DEBUG( "LNETL1: Size triggered flush for %s:", asString().c_str() ); break;
197 default: LNETL1_DEBUG( "LNETL1: Manual flush for %s:", asString().c_str() );
199 _FlushTrigger = FTManual;
200 LNETL1_DEBUG( "LNETL1: %s sent effectively a buffer (%d on %d B)", asString().c_str(), len, _ReadyToSendBuffer.size() );
203 // TODO OPTIM remove the nldebug for speed
204 //commented for optimisation LNETL1_DEBUG( "LNETL1: %s sent effectively %u/%u bytes (pos %u wantedsend %u)", asString().c_str(), len, _ReadyToSendBuffer.size(), _RTSBIndex, realLen/*, stringFromVectorPart(_ReadyToSendBuffer,_RTSBIndex,len).c_str()*/ );
206 if ( _RTSBIndex+len == _ReadyToSendBuffer.size() ) // for non-blocking mode
208 // If sending is ok, clear the global buffer
209 //nldebug( "O-%u all %u bytes (%u to %u) sent", Sock->descriptor(), len, _RTSBIndex, _ReadyToSendBuffer.size() );
210 _ReadyToSendBuffer.clear();
211 _RTSBIndex = 0;
212 if ( nbBytesRemaining )
213 *nbBytesRemaining = 0;
215 else
217 // Or clear only the data that was actually sent
218 nlassertex( _RTSBIndex+len < _ReadyToSendBuffer.size(), ("index=%u len=%u size=%u", _RTSBIndex, len, _ReadyToSendBuffer.size()) );
219 //nldebug( "O-%u only %u B on %u (%u to %u) sent", Sock->descriptor(), len, _ReadyToSendBuffer.size()-_RTSBIndex, _RTSBIndex, _ReadyToSendBuffer.size() );
220 _RTSBIndex += len;
221 if ( nbBytesRemaining )
222 *nbBytesRemaining = _ReadyToSendBuffer.size() - _RTSBIndex;
223 if ( _ReadyToSendBuffer.size() > 20480 ) // if big, clear data already sent
225 uint nbcpy = _ReadyToSendBuffer.size() - _RTSBIndex;
226 for (uint i = 0; i < nbcpy; i++)
228 _ReadyToSendBuffer[i] = _ReadyToSendBuffer[i+_RTSBIndex];
230 _ReadyToSendBuffer.resize(nbcpy);
231 //_ReadyToSendBuffer.erase( _ReadyToSendBuffer.begin(), _ReadyToSendBuffer.begin()+_RTSBIndex );
232 _RTSBIndex = 0;
233 //nldebug( "O-%u Cleared data already sent, %u B remain", Sock->descriptor(), nbcpy );
237 else
239 #ifdef NL_DEBUG
240 // Can happen in a normal behavior if, for example, the other side is not connected anymore
241 LNETL1_DEBUG( "LNETL1: %s failed to send effectively a buffer of %d bytes", asString().c_str(), _ReadyToSendBuffer.size() );
242 #endif
243 // NEW: Clearing (loosing) the buffer if the sending can't be performed at all
244 _ReadyToSendBuffer.clear();
245 _RTSBIndex = 0;
246 if ( nbBytesRemaining )
247 *nbBytesRemaining = 0;
248 return false;
251 else
253 if ( nbBytesRemaining )
254 *nbBytesRemaining = 0;
258 while ( !SendFifo.empty() && _ReadyToSendBuffer.empty() );
260 return true;
264 /* Sets the time flush trigger (in millisecond). When this time is elapsed,
265 * all data in the send queue is automatically sent (-1 to disable this trigger)
267 void CBufSock::setTimeFlushTrigger( sint32 ms )
269 nlassert (this != InvalidSockId); // invalid bufsock
270 _TriggerTime = ms;
271 _LastFlushTime = CTime::getLocalTime();
276 * Update the network sending (call this method evenly). Returns false if an error occurred.
278 bool CBufSock::update()
280 nlassert (this != InvalidSockId); // invalid bufsock
281 // nlnettrace( "CBufSock::update-BEGIN" );
282 // Time trigger
284 if ( _TriggerTime != -1 )
286 TTime now = CTime::getLocalTime();
287 if ( (sint32)(now-_LastFlushTime) >= _TriggerTime )
289 #ifdef NL_DEBUG
290 _FlushTrigger = FTTime;
291 #endif
292 if ( flush() )
294 _LastFlushTime = now;
295 // nlnettrace ( "CBufSock::update-END time 1" );
296 return true;
298 else
300 // nlnettrace ( "CBufSock::update-END time 0" );
301 return false;
305 // Size trigger
306 if ( _TriggerSize != -1 )
308 if ( (sint32)SendFifo.size() > _TriggerSize )
310 #ifdef NL_DEBUG
311 _FlushTrigger = FTSize;
312 #endif
313 // nlnettrace( "CBufSock::update-END size" );
314 return flush();
317 // nlnettrace( "CBufSock::update-END nosend" );
318 return true;
323 * Connects to the specified addr; set connectedstate to true if no connection advertising is needed
324 * Precond: not connected
326 void CBufSock::connect( const CInetAddress& addr, bool nodelay, bool connectedstate )
328 nlassert (this != InvalidSockId); // invalid bufsock
329 nlassert( ! Sock->connected() );
331 Sock->connect( addr );
332 _ConnectedState = connectedstate;
333 _KnowConnected = connectedstate;
334 if ( nodelay )
336 Sock->setNoDelay( true );
338 _ReadyToSendBuffer.clear();
339 _RTSBIndex = 0;
344 * Disconnects; set connectedstate to false if no disconnection advertising is needed
346 void CBufSock::disconnect( bool connectedstate )
348 nlassert (this != InvalidSockId); // invalid bufsock
349 Sock->disconnect();
350 _ConnectedState = connectedstate;
351 _KnowConnected = connectedstate;
356 * Returns a string with the characteristics of the object
358 string CBufSock::asString() const
360 // stringstream ss;
361 string str;
362 if (this == InvalidSockId) // tricky
363 str = "<null>";
364 else
366 // if it crashs here, it means that the CBufSock was deleted and you try to access to the virtual table that is empty
367 // because the object is destroyed.
368 str += typeStr();
369 str += NLMISC::toStringPtr(this) + " (socket ";
371 if (Sock == NULL)
372 str += "<null>";
373 else
374 str += NLMISC::toString(Sock->descriptor());
376 str += ")";
378 return str;
383 * Constructor
385 CNonBlockingBufSock::CNonBlockingBufSock( CTcpSock *sock, uint32 maxExpectedBlockSize ) :
386 CBufSock( sock ),
387 _MaxExpectedBlockSize( maxExpectedBlockSize ),
388 _NowReadingBuffer( false ),
389 _BytesRead( 0 ),
390 _Length( 0 )
392 nlnettrace( "CNonBlockingBufSock::CNonBlockingBufSock" );
397 * Constructor with an existing socket (created by an accept())
399 CServerBufSock::CServerBufSock( CTcpSock *sock ) :
400 CNonBlockingBufSock( sock ),
401 _Advertised( false ),
402 _OwnerTask( NULL )
404 nlassert (this != InvalidSockId); // invalid bufsock
405 nlnettrace( "CServerBufSock::CServerBufSock" );
409 // In Receive Threads:
413 * Receives a part of a message (nonblocking socket only)
415 bool CNonBlockingBufSock::receivePart( uint32 nbExtraBytes )
417 nlassert (this != InvalidSockId); // invalid bufsock
418 nlnettrace( "CNonBlockingBufSock::receivePart" );
420 TBlockSize actuallen;
421 if ( ! _NowReadingBuffer )
423 // Receiving length prefix
424 actuallen = sizeof(_Length)-_BytesRead;
425 CSock :: TSockResult ret = Sock->receive( (uint8*)(&_Length)+_BytesRead, actuallen, false );
426 if (ret == CSock::ConnectionClosed)
428 LNETL1_DEBUG( "LNETL1: Connection %s closed", asString().c_str() );
429 return false;
431 else if (ret == CSock::Error)
433 LNETL1_DEBUG( "LNETL1: Socket error for %s", asString().c_str() );
434 Sock->disconnect();
435 return false;
438 _BytesRead += actuallen;
439 if ( _BytesRead == sizeof(_Length ) )
441 if ( _Length != 0 )
443 _Length = ntohl( _Length );
444 //nldebug( "I-%u L%u (0x%x) a%u", Sock->descriptor(), _Length, _Length, actuallen );
446 // Test size limit
447 if ( _Length > _MaxExpectedBlockSize )
449 nlwarning( "LNETL1: Socket %s received header length %u exceeding max expected %u... Disconnecting", asString().c_str(), _Length, _MaxExpectedBlockSize );
450 throw ESocket( toString( "Received length %u exceeding max expected %u from %s", _Length, _MaxExpectedBlockSize, Sock->remoteAddr().asString().c_str() ).c_str(), false );
453 _NowReadingBuffer = true;
454 _ReceiveBuffer.resize( _Length + nbExtraBytes );
456 else
458 nlwarning( "LNETL1: Socket %s received null length in block header", asString().c_str() );
460 _BytesRead = 0;
464 if ( _NowReadingBuffer )
466 // Receiving payload buffer
467 actuallen = _Length-_BytesRead;
468 Sock->receive( &*_ReceiveBuffer.begin()+_BytesRead, actuallen );
469 _BytesRead += actuallen;
471 if ( _BytesRead == _Length )
473 #ifdef NL_DEBUG
474 LNETL1_DEBUG( "LNETL1: %s received buffer (%u bytes): [%s]", asString().c_str(), _ReceiveBuffer.size(), stringFromVector(_ReceiveBuffer).c_str() );
475 #endif
476 _NowReadingBuffer = false;
477 //nldebug( "I-%u all %u B on %u", Sock->descriptor(), actuallen );
478 _BytesRead = 0;
479 return true;
481 //else
483 // nldebug( "I-%u only %u B on %u", actuallen, Sock->descriptor(), _Length-(_BytesRead-actuallen) );
487 return false;
491 } // NLNET