khanat-opennel-code/code/nel/src/net/buf_sock.cpp

486 lines
13 KiB
C++

// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "stdnet.h"
#include "nel/misc/hierarchical_timer.h"
#include "nel/misc/variable.h"
#include "nel/net/buf_sock.h"
#include "nel/net/buf_server.h"
#include "nel/net/net_log.h"
#ifdef NL_OS_WINDOWS
# define NOMINMAX
# include <windows.h>
#elif defined NL_OS_UNIX
# include <netinet/in.h>
#endif
using namespace NLMISC;
using namespace std;
NLMISC::CVariable<uint32> MaxTCPPacketSize("nel", "MaxTCPPacketSize", "Maximum size of TCP packets created by cumulating small packets", 10240, 0, true);
namespace NLNET {
NLMISC::CMutex nettrace_mutex("nettrace_mutex");
/*
* Constructor
*/
CBufSock::CBufSock( CTcpSock *sock ) :
Sock( sock ),
_KnowConnected( false ),
_LastFlushTime( 0 ),
_TriggerTime( 20 ),
_TriggerSize( -1 ),
_RTSBIndex( 0 ),
_AppId( 0 ),
_ConnectedState( false )
{
nlnettrace( "CBufSock::CBufSock" ); // don't define a global object
if ( Sock == NULL )
{
Sock = new CTcpSock();
}
#ifdef NL_DEBUG
_FlushTrigger = FTManual;
#endif
_LastFlushTime = CTime::getLocalTime();
}
/*
* Destructor
*/
CBufSock::~CBufSock()
{
nlassert (this != InvalidSockId); // invalid bufsock
nlnettrace( "CBufSock::~CBufSock" );
delete Sock; // the socket disconnects automatically if needed
// destroy the structur to be sure that other people will not access to this anymore
AuthorizedCallback = "";
Sock = NULL;
_KnowConnected = false;
_LastFlushTime = 0;
_TriggerTime = 0;
_TriggerSize = 0;
_ReadyToSendBuffer.clear ();
_RTSBIndex = 0;
_AppId = 0;
_ConnectedState = false;
}
/*
* Returns a readable string from a vector of bytes, beginning from pos, limited to 'len' characters. '\0' are replaced by ' '
*/
string stringFromVectorPart( const vector<uint8>& v, uint32 pos, uint32 len )
{
nlassertex( pos+len <= v.size(), ("pos=%u len=%u size=%u", pos, len, v.size()) );
string s;
if ( (! v.empty()) && (len!=0) )
{
// Copy contents
s.resize( len );
memcpy( &*s.begin(), &*v.begin()+pos, len );
// Replace '\0' characters
string::iterator is;
for ( is=s.begin(); is!=s.end(); ++is )
{
if ( ! isprint((uint8)(*is)) || (*is) == '%' )
{
(*is) = '?';
}
}
}
return s;
}
/*
* Force to send data pending in the send queue now. In the case of a non-blocking socket
* (see CNonBlockingBufSock), if all the data could not be sent immediately,
* the returned nbBytesRemaining value is non-zero.
* \param nbBytesRemaining If the pointer is not NULL, the method sets the number of bytes still pending after the flush attempt.
* \returns False if an error has occured (e.g. the remote host is disconnected).
* To retrieve the reason of the error, call CSock::getLastError() and/or CSock::errorString()
*
* Note: this method works with both blocking and non-blocking sockets
* Precond: the send queue should not contain an empty block
*/
bool CBufSock::flush( uint *nbBytesRemaining )
{
nlassert (this != InvalidSockId); // invalid bufsock
//nlnettrace( "CBufSock::flush" );
// Copy data from the send queue to _ReadyToSendBuffer
TBlockSize netlen;
// vector<uint8> tmpbuffer;
do
{
// Process each element in the send queue
uint8 *tmpbuffer = NULL;
uint32 size = 0;
if (! SendFifo.empty())
{
SendFifo.front( tmpbuffer, size );
}
while ( ! SendFifo.empty() && ( (_ReadyToSendBuffer.size()==0) || (_ReadyToSendBuffer.size() +size < MaxTCPPacketSize) ) )
{
// Compute the size and add it into the beginning of the buffer
netlen = htonl( (TBlockSize)size );
uint32 oldBufferSize = _ReadyToSendBuffer.size();
_ReadyToSendBuffer.resize (oldBufferSize+sizeof(TBlockSize)+size);
*(TBlockSize*)&(_ReadyToSendBuffer[oldBufferSize])=netlen;
//nldebug( "O-%u %u+L%u (0x%x)", Sock->descriptor(), oldBufferSize, size, size );
// Append the temporary buffer to the global buffer
CFastMem::memcpy (&_ReadyToSendBuffer[oldBufferSize+sizeof(TBlockSize)], tmpbuffer, size);
SendFifo.pop();
if (! SendFifo.empty())
{
SendFifo.front( tmpbuffer, size );
}
}
// Actual sending of _ReadyToSendBuffer
//if ( ! _ReadyToSendBuffer.empty() )
if ( _ReadyToSendBuffer.size() != 0 )
{
// Send
CSock::TSockResult res;
TBlockSize len = _ReadyToSendBuffer.size() - _RTSBIndex;
res = Sock->send( _ReadyToSendBuffer.getPtr()+_RTSBIndex, len, false );
if ( res == CSock::Ok )
{
/* // Debug display
switch ( _FlushTrigger )
{
case FTTime : LNETL1_DEBUG( "LNETL1: Time triggered flush for %s:", asString().c_str() ); break;
case FTSize : LNETL1_DEBUG( "LNETL1: Size triggered flush for %s:", asString().c_str() ); break;
default: LNETL1_DEBUG( "LNETL1: Manual flush for %s:", asString().c_str() );
}
_FlushTrigger = FTManual;
LNETL1_DEBUG( "LNETL1: %s sent effectively a buffer (%d on %d B)", asString().c_str(), len, _ReadyToSendBuffer.size() );
*/
// TODO OPTIM remove the nldebug for speed
//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()*/ );
if ( _RTSBIndex+len == _ReadyToSendBuffer.size() ) // for non-blocking mode
{
// If sending is ok, clear the global buffer
//nldebug( "O-%u all %u bytes (%u to %u) sent", Sock->descriptor(), len, _RTSBIndex, _ReadyToSendBuffer.size() );
_ReadyToSendBuffer.clear();
_RTSBIndex = 0;
if ( nbBytesRemaining )
*nbBytesRemaining = 0;
}
else
{
// Or clear only the data that was actually sent
nlassertex( _RTSBIndex+len < _ReadyToSendBuffer.size(), ("index=%u len=%u size=%u", _RTSBIndex, len, _ReadyToSendBuffer.size()) );
//nldebug( "O-%u only %u B on %u (%u to %u) sent", Sock->descriptor(), len, _ReadyToSendBuffer.size()-_RTSBIndex, _RTSBIndex, _ReadyToSendBuffer.size() );
_RTSBIndex += len;
if ( nbBytesRemaining )
*nbBytesRemaining = _ReadyToSendBuffer.size() - _RTSBIndex;
if ( _ReadyToSendBuffer.size() > 20480 ) // if big, clear data already sent
{
uint nbcpy = _ReadyToSendBuffer.size() - _RTSBIndex;
for (uint i = 0; i < nbcpy; i++)
{
_ReadyToSendBuffer[i] = _ReadyToSendBuffer[i+_RTSBIndex];
}
_ReadyToSendBuffer.resize(nbcpy);
//_ReadyToSendBuffer.erase( _ReadyToSendBuffer.begin(), _ReadyToSendBuffer.begin()+_RTSBIndex );
_RTSBIndex = 0;
//nldebug( "O-%u Cleared data already sent, %u B remain", Sock->descriptor(), nbcpy );
}
}
}
else
{
#ifdef NL_DEBUG
// Can happen in a normal behavior if, for example, the other side is not connected anymore
LNETL1_DEBUG( "LNETL1: %s failed to send effectively a buffer of %d bytes", asString().c_str(), _ReadyToSendBuffer.size() );
#endif
// NEW: Clearing (loosing) the buffer if the sending can't be performed at all
_ReadyToSendBuffer.clear();
_RTSBIndex = 0;
if ( nbBytesRemaining )
*nbBytesRemaining = 0;
return false;
}
}
else
{
if ( nbBytesRemaining )
*nbBytesRemaining = 0;
}
}
while ( !SendFifo.empty() && _ReadyToSendBuffer.size()==0 );
return true;
}
/* Sets the time flush trigger (in millisecond). When this time is elapsed,
* all data in the send queue is automatically sent (-1 to disable this trigger)
*/
void CBufSock::setTimeFlushTrigger( sint32 ms )
{
nlassert (this != InvalidSockId); // invalid bufsock
_TriggerTime = ms;
_LastFlushTime = CTime::getLocalTime();
}
/*
* Update the network sending (call this method evenly). Returns false if an error occured.
*/
bool CBufSock::update()
{
nlassert (this != InvalidSockId); // invalid bufsock
// nlnettrace( "CBufSock::update-BEGIN" );
// Time trigger
if ( _TriggerTime != -1 )
{
TTime now = CTime::getLocalTime();
if ( (sint32)(now-_LastFlushTime) >= _TriggerTime )
{
#ifdef NL_DEBUG
_FlushTrigger = FTTime;
#endif
if ( flush() )
{
_LastFlushTime = now;
// nlnettrace ( "CBufSock::update-END time 1" );
return true;
}
else
{
// nlnettrace ( "CBufSock::update-END time 0" );
return false;
}
}
}
// Size trigger
if ( _TriggerSize != -1 )
{
if ( (sint32)SendFifo.size() > _TriggerSize )
{
#ifdef NL_DEBUG
_FlushTrigger = FTSize;
#endif
// nlnettrace( "CBufSock::update-END size" );
return flush();
}
}
// nlnettrace( "CBufSock::update-END nosend" );
return true;
}
/*
* Connects to the specified addr; set connectedstate to true if no connection advertising is needed
* Precond: not connected
*/
void CBufSock::connect( const CInetAddress& addr, bool nodelay, bool connectedstate )
{
nlassert (this != InvalidSockId); // invalid bufsock
nlassert( ! Sock->connected() );
Sock->connect( addr );
_ConnectedState = connectedstate;
_KnowConnected = connectedstate;
if ( nodelay )
{
Sock->setNoDelay( true );
}
_ReadyToSendBuffer.clear();
_RTSBIndex = 0;
}
/*
* Disconnects; set connectedstate to false if no disconnection advertising is needed
*/
void CBufSock::disconnect( bool connectedstate )
{
nlassert (this != InvalidSockId); // invalid bufsock
Sock->disconnect();
_ConnectedState = connectedstate;
_KnowConnected = connectedstate;
}
/*
* Returns a string with the characteristics of the object
*/
string CBufSock::asString() const
{
// stringstream ss;
string str;
if (this == InvalidSockId) // tricky
str = "<null>";
else
{
// if it crashs here, it means that the CBufSock was deleted and you try to access to the virtual table that is empty
// because the object is destroyed.
str += typeStr();
str += NLMISC::toStringPtr(this) + " (socket ";
if (Sock == NULL)
str += "<null>";
else
str += NLMISC::toString(Sock->descriptor());
str += ")";
}
return str;
}
/*
* Constructor
*/
CNonBlockingBufSock::CNonBlockingBufSock( CTcpSock *sock, uint32 maxExpectedBlockSize ) :
CBufSock( sock ),
_MaxExpectedBlockSize( maxExpectedBlockSize ),
_NowReadingBuffer( false ),
_BytesRead( 0 ),
_Length( 0 )
{
nlnettrace( "CNonBlockingBufSock::CNonBlockingBufSock" );
}
/*
* Constructor with an existing socket (created by an accept())
*/
CServerBufSock::CServerBufSock( CTcpSock *sock ) :
CNonBlockingBufSock( sock ),
_Advertised( false ),
_OwnerTask( NULL )
{
nlassert (this != InvalidSockId); // invalid bufsock
nlnettrace( "CServerBufSock::CServerBufSock" );
}
// In Receive Threads:
/*
* Receives a part of a message (nonblocking socket only)
*/
bool CNonBlockingBufSock::receivePart( uint32 nbExtraBytes )
{
nlassert (this != InvalidSockId); // invalid bufsock
nlnettrace( "CNonBlockingBufSock::receivePart" );
TBlockSize actuallen;
if ( ! _NowReadingBuffer )
{
// Receiving length prefix
actuallen = sizeof(_Length)-_BytesRead;
CSock :: TSockResult ret = Sock->receive( (uint8*)(&_Length)+_BytesRead, actuallen, false );
if (ret == CSock::ConnectionClosed)
{
LNETL1_DEBUG( "LNETL1: Connection %s closed", asString().c_str() );
return false;
}
else if (ret == CSock::Error)
{
LNETL1_DEBUG( "LNETL1: Socket error for %s", asString().c_str() );
Sock->disconnect();
return false;
}
_BytesRead += actuallen;
if ( _BytesRead == sizeof(_Length ) )
{
if ( _Length != 0 )
{
_Length = ntohl( _Length );
//nldebug( "I-%u L%u (0x%x) a%u", Sock->descriptor(), _Length, _Length, actuallen );
// Test size limit
if ( _Length > _MaxExpectedBlockSize )
{
nlwarning( "LNETL1: Socket %s received header length %u exceeding max expected %u... Disconnecting", asString().c_str(), _Length, _MaxExpectedBlockSize );
throw ESocket( toString( "Received length %u exceeding max expected %u from %s", _Length, _MaxExpectedBlockSize, Sock->remoteAddr().asString().c_str() ).c_str(), false );
}
_NowReadingBuffer = true;
_ReceiveBuffer.resize( _Length + nbExtraBytes );
}
else
{
nlwarning( "LNETL1: Socket %s received null length in block header", asString().c_str() );
}
_BytesRead = 0;
}
}
if ( _NowReadingBuffer )
{
// Receiving payload buffer
actuallen = _Length-_BytesRead;
Sock->receive( &*_ReceiveBuffer.begin()+_BytesRead, actuallen );
_BytesRead += actuallen;
if ( _BytesRead == _Length )
{
#ifdef NL_DEBUG
LNETL1_DEBUG( "LNETL1: %s received buffer (%u bytes): [%s]", asString().c_str(), _ReceiveBuffer.size(), stringFromVector(_ReceiveBuffer).c_str() );
#endif
_NowReadingBuffer = false;
//nldebug( "I-%u all %u B on %u", Sock->descriptor(), actuallen );
_BytesRead = 0;
return true;
}
//else
//{
// nldebug( "I-%u only %u B on %u", actuallen, Sock->descriptor(), _Length-(_BytesRead-actuallen) );
//}
}
return false;
}
} // NLNET