// NeL - MMORPG Framework // 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 . #include "stdnet.h" #include "nel/net/sock.h" #include "nel/net/net_log.h" #include "nel/misc/time_nl.h" #include "nel/misc/hierarchical_timer.h" #ifdef NL_OS_WINDOWS # if defined(NL_COMP_VC7) || defined(NL_COMP_VC71) || defined(NL_COMP_VC8) || defined(NL_COMP_VC9) # include # endif # define NOMINMAX # include # define socklen_t int # define ERROR_NUM WSAGetLastError() # define ERROR_WOULDBLOCK WSAEWOULDBLOCK #elif defined NL_OS_UNIX # include # include # include # include # include # include # include # include # include # include # include # define SOCKET_ERROR -1 # define INVALID_SOCKET -1 # define ERROR_NUM errno # define ERROR_WOULDBLOCK EWOULDBLOCK # define ERROR_MSG strerror(errno) typedef int SOCKET; #endif using namespace std; using namespace NLMISC; namespace NLNET { bool CSock::_Initialized = false; /* * ESocket constructor */ ESocket::ESocket( const char *reason, bool systemerror, CInetAddress *addr ) { /*it doesnt work on linux, should do something more cool std::stringstream ss; ss << "Socket error: " << reason; if ( systemerror ) { ss << " (" << ERROR_NUM; #ifdef NL_OS_UNIX ss << ": " << ERROR_MSG; #endif ss << ") " << std::endl; } _Reason = ss.str(); */ _Reason = "Socket error: "; uint errornum = CSock::getLastError(); char str[256]; if ( addr != NULL ) { // Version with address smprintf( str, 256, reason, addr->asString().c_str() ); // reason *must* contain "%s" _Reason += str; } else { // Version without address _Reason += reason; } if ( systemerror ) { _Reason += " ("; smprintf( str, 256, "%d", errornum ); _Reason += str; if ( errornum != 0 ) { _Reason += ": "; _Reason += CSock::errorString( errornum ); } _Reason += ")"; } LNETL0_INFO( "LNETL0: Exception will be launched: %s", _Reason.c_str() ); } /* * Initializes the network engine if it is not already done (under Windows, calls WSAStartup()). */ void CSock::initNetwork() { if ( ! CSock::_Initialized ) { #ifdef NL_OS_WINDOWS WORD winsock_version = MAKEWORD( 2, 0 ); WSADATA wsaData; if ( WSAStartup( winsock_version, &wsaData ) != 0 ) { throw ESocket( "Winsock initialization failed" ); } #endif CSock::_Initialized = true; } } /* * Releases the network engine */ void CSock::releaseNetwork() { #ifdef NL_OS_WINDOWS WSACleanup(); #endif CSock::_Initialized = false; } /* Returns the code of the last error that has occured. * Note: This code is platform-dependant. On Unix, it is errno; on Windows it is the Winsock error code. * See also errorString() */ uint CSock::getLastError() { return (uint)ERROR_NUM; } /* * Returns a string explaining the network error (see getLastError()) */ std::string CSock::errorString( uint errorcode ) { #ifdef NL_OS_WINDOWS switch( errorcode ) { case WSAEINTR /*10004*/: return "Blocking operation interrupted"; case WSAEINVAL /*10022*/: return "Invalid socket (maybe not bound) or argument"; case WSAEMFILE /*10024*/: return "Too many open sockets"; case WSAENOTSOCK /*10038*/: return "Socket operation on nonsocket (maybe invalid select descriptor)"; case WSAEMSGSIZE /*10040*/: return "Message too long"; case WSAEADDRINUSE /*10048*/: return "Address already in use (is this service already running in this computer?)"; case WSAEADDRNOTAVAIL/*10049*/: return "Address not available"; case WSAENETDOWN /*10050*/: return "Network is down"; case WSAENETUNREACH /*10051*/: return "Network is unreachable"; case WSAECONNRESET /*10054*/: return "Connection reset by peer"; case WSAENOBUFS /*10055*/: return "No buffer space available; please close applications or reboot"; case WSAESHUTDOWN /*10058*/: return "Cannot send/receive after socket shutdown"; case WSAETIMEDOUT /*10060*/: return "Connection timed-out"; case WSAECONNREFUSED /*10061*/: return "Connection refused, the server may be offline"; case WSAEHOSTUNREACH /*10065*/: return "Remote host is unreachable"; case WSANOTINITIALISED /*093*/: return "'Windows Sockets' not initialized"; default: return ""; } #elif defined NL_OS_UNIX return std::string( strerror( errorcode ) ); #endif } /* * Constructor */ CSock::CSock( bool logging ) : _Sock( INVALID_SOCKET ), _Logging( logging ), _NonBlocking( false ), _BytesReceived( 0 ), _BytesSent( 0 ), _TimeoutS( 0 ), _TimeoutUs( 0 ), _MaxReceiveTime( 0 ), _MaxSendTime( 0 ), _Blocking( false ) { nlassert( CSock::_Initialized ); /*{ CSynchronized::CAccessor sync( &_SyncConnected ); sync.value() = false; }*/ _Connected = false; } /* * Construct a CSock object using an existing connected socket descriptor and its associated remote address */ CSock::CSock( SOCKET sock, const CInetAddress& remoteaddr ) : _Sock( sock ), _RemoteAddr( remoteaddr ), _Logging( true ), _NonBlocking( false ), _BytesReceived( 0 ), _BytesSent( 0 ), _MaxReceiveTime( 0 ), _MaxSendTime( 0 ) { nlassert( CSock::_Initialized ); /*{ CSynchronized::CAccessor sync( &_SyncConnected ); sync.value() = true; }*/ _Connected = true; // Check remote address if ( ! _RemoteAddr.isValid() ) { throw ESocket( "Could not init a socket object with an invalid address", false ); } // Get local socket name setLocalAddress(); #ifdef NL_OS_UNIX // We set the close-on-exec flag on the socket to be sure that when // we call the exec() to spawn an application in the AES for example, // that the AES listen socket will be close and not given to the child. // From google: // Manipulate the close-on-exec flag to determine if a file descriptor // should be closed as part of the normal processing of the exec subroutine. // If the flag is set, the file descriptor is closed. // If the flag is clear, the file descriptor is left open ioctl(_Sock, FIOCLEX, NULL); // fcntl should be more portable but not tested fcntl(_Sock, F_SETFD, FD_CLOEXEC); #endif } /* * Creates the socket and get a valid descriptor */ void CSock::createSocket( int type, int protocol ) { nlassert( _Sock == INVALID_SOCKET ); _Sock = socket( AF_INET, type, protocol ); // or IPPROTO_IP (=0) ? if ( _Sock == INVALID_SOCKET ) { throw ESocket( "Socket creation failed" ); } if ( _Logging ) { // LNETL0_DEBUG( "LNETL0: Socket %d open (TCP)", _Sock ); } #ifdef NL_OS_UNIX // We set the close-on-exec flag on the socket to be sure that when // we call the exec() to spawn an application in the AES for example, // that the AES listen socket will be close and not given the to child. // From google: // Manipulate the close-on-exec flag to determine if a file descriptor // should be closed as part of the normal processing of the exec subroutine. // If the flag is set, the file descriptor is closed. // If the flag is clear, the file descriptor is left open ioctl(_Sock, FIOCLEX, NULL); // fcntl should be more portable but not tested fcntl(_Sock, F_SETFD, FD_CLOEXEC); #endif } /* * Closes the listening socket */ void CSock::close() { if ( _Logging ) { LNETL0_DEBUG( "LNETL0: Socket %d closing for %s at %s", _Sock, _RemoteAddr.asString().c_str(), _LocalAddr.asString().c_str() ); } SOCKET sockToClose = _Sock; // preset to invalid to bypass exception in listen thread _Sock = INVALID_SOCKET; #ifdef NL_OS_WINDOWS closesocket( sockToClose ); #elif defined NL_OS_UNIX ::close( sockToClose ); #endif _Connected = false; } /* * Destructor */ CSock::~CSock() { //nlinfo( "Report for %s socket %s: Max send time: %u Max recv time: %u", _NonBlocking?"non-blocking":"blocking", remoteAddr().asString().c_str(), _MaxSendTime, _MaxReceiveTime ); //nlinfo( "Max send time: %u", _MaxSendTime); if ( _Sock != INVALID_SOCKET ) { if ( _Logging ) { LNETL0_DEBUG( "LNETL0: Socket %d closing for %s at %s", _Sock, _RemoteAddr.asString().c_str(), _LocalAddr.asString().c_str() ); } if ( connected() ) { #ifdef NL_OS_WINDOWS shutdown( _Sock, SD_BOTH ); } closesocket( _Sock ); #elif defined NL_OS_UNIX shutdown( _Sock, SHUT_RDWR ); } ::close( _Sock ); #endif _Sock = INVALID_SOCKET; } } /* * Connection */ void CSock::connect( const CInetAddress& addr ) { LNETL0_DEBUG( "LNETL0: Socket %d connecting to %s...", _Sock, addr.asString().c_str() ); // Check address if ( ! addr.isValid() ) { throw ESocket( "Unable to connect to invalid address", false ); } #ifndef NL_OS_WINDOWS // Set Reuse Address On (does not work on Win98 and is useless on Win2000) int value = true; if ( setsockopt( _Sock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value) ) == SOCKET_ERROR ) { throw ESocket( "ReuseAddr failed" ); } #endif // Connection (when _Sock is a datagram socket, connect establishes a default destination address) if ( ::connect( _Sock, (const sockaddr *)(addr.sockAddr()), sizeof(sockaddr_in) ) != 0 ) { /* if ( _Logging ) { #ifdef NL_OS_WINDOWS nldebug( "Impossible to connect socket %d to %s %s (%d)", _Sock, addr.hostName().c_str(), addr.asIPString().c_str(), ERROR_NUM ); #elif defined NL_OS_UNIX nldebug( "Impossible to connect socket %d to %s %s (%d:%s)", _Sock, addr.hostName().c_str(), addr.asIPString().c_str(), ERROR_NUM, strerror(ERROR_NUM) ); #endif } */ throw ESocketConnectionFailed( addr ); } setLocalAddress(); if ( _Logging ) { LNETL0_DEBUG( "LNETL0: Socket %d connected to %s (local %s)", _Sock, addr.asString().c_str(), _LocalAddr.asString().c_str() ); } _RemoteAddr = addr; _BytesReceived = 0; _BytesSent = 0; /*CSynchronized::CAccessor sync( &_SyncConnected ); sync.value() = true;*/ _Connected = true; } /* * Checks if there is some data to receive */ bool CSock::dataAvailable() { fd_set fdset; FD_ZERO( &fdset ); FD_SET( _Sock, &fdset ); timeval tv; tv.tv_sec = _TimeoutS; tv.tv_usec = _TimeoutUs; // Test for message received. int res = select( _Sock+1, &fdset, NULL, NULL, &tv ); switch ( res ) { case 0 : return false; case -1 : throw ESocket( "CSock::dataAvailable(): select failed" ); default : return true; } } /* * Sets the local address */ void CSock::setLocalAddress() { sockaddr saddr; socklen_t saddrlen = sizeof(saddr); if ( getsockname( _Sock, &saddr, &saddrlen ) != 0 ) { throw ESocket( "Unable to find local address" ); } _LocalAddr.setSockAddr( (const sockaddr_in *)&saddr ); } /* * Sends data, or returns false if it would block */ CSock::TSockResult CSock::send( const uint8 *buffer, uint32& len, bool throw_exception ) { TTicks before = CTime::getPerformanceTime(); len = ::send( _Sock, (const char*)buffer, len, 0 ); _MaxSendTime = max( (uint32)(CTime::ticksToSecond(CTime::getPerformanceTime()-before)*1000.0f), _MaxSendTime ); // nldebug ("CSock::send(): Sent %d bytes to %d (%d)", len, _Sock, ERROR_NUM); if ( _Logging ) { // LNETL0_DEBUG ("LNETL0: CSock::send(): Sent %d bytes to %d res: %d (%d)", realLen, _Sock, len, ERROR_NUM); } if ( ((int)len) == SOCKET_ERROR ) { if ( ERROR_NUM == ERROR_WOULDBLOCK ) { H_AUTO(L0SendWouldBlock); len = 0; //nlSleep(10); if (!_Blocking) { //nldebug("SendWouldBlock - %s / %s Entering snooze mode",_LocalAddr.asString().c_str(),_RemoteAddr.asString().c_str()); _Blocking= true; } return Ok; } if ( throw_exception ) { #ifdef NL_OS_WINDOWS throw ESocket( NLMISC::toString( "Unable to send data: error %u", GetLastError() ).c_str() ); #else throw ESocket( "Unable to send data" ); #endif } return Error; } _BytesSent += len; if (_Blocking) { //nldebug("SendWouldBlock - %s / %s Leaving snooze mode",_LocalAddr.asString().c_str(),_RemoteAddr.asString().c_str()); _Blocking= false; } return Ok; } /* * Receives data */ CSock::TSockResult CSock::receive( uint8 *buffer, uint32& len, bool throw_exception ) { if ( _NonBlocking ) { // Receive incoming message (only the received part) TTicks before = CTime::getPerformanceTime(); len = ::recv( _Sock, (char*)buffer, len, 0 ); //nlinfo ("CSock::receive(): NBM Received %d bytes to %d res: %d (%d)", realLen, _Sock, len, ERROR_NUM); if ( _Logging ) { // LNETL0_DEBUG ("LNETL0: CSock::receive(): NBM Received %d bytes to %d res: %d (%d)", realLen, _Sock, len, ERROR_NUM); } _MaxReceiveTime = max( (uint32)(CTime::ticksToSecond(CTime::getPerformanceTime()-before)*1000.0f), _MaxReceiveTime ); switch ( len ) { // Graceful disconnection case 0 : { /*{ CSynchronized::CAccessor sync( &_SyncConnected ); sync.value() = false; }*/ _Connected = false; if ( throw_exception ) { throw ESocketConnectionClosed(); } return CSock::ConnectionClosed; } // Socket error or call would block case SOCKET_ERROR : { len = 0; if ( ERROR_NUM == ERROR_WOULDBLOCK ) { // Call would block return CSock::WouldBlock; } else { // Socket error if ( throw_exception ) { throw ESocket( "Unable to receive data" ); } return CSock::Error; } } } } else // Blocking Mode { // Receive incoming message, waiting until a complete message has arrived uint total = 0; uint brecvd; while ( total < len ) { TTicks before = CTime::getPerformanceTime(); brecvd = ::recv( _Sock, (char*)(buffer+total), len-total, 0 ); // nlinfo ("CSock::receive(): BM Received %d bytes to %d res: %d (%d) total %d", len, _Sock, brecvd, ERROR_NUM, total); _MaxReceiveTime = max( (uint32)(CTime::ticksToSecond(CTime::getPerformanceTime()-before)*1000.0f), _MaxReceiveTime ); switch ( brecvd ) { // Graceful disconnection case 0 : { /*{ CSynchronized::CAccessor sync( &_SyncConnected ); sync.value() = false; }*/ _Connected = false; len = total; _BytesReceived += len; if ( throw_exception ) { throw ESocketConnectionClosed(); } return CSock::ConnectionClosed; } // Socket error case SOCKET_ERROR : { len = total; _BytesReceived += len; if ( throw_exception ) { throw ESocket( "Unable to receive data" ); } return CSock::Error; } } total += brecvd; } } /*if ( _Logging ) { LNETL0_DEBUG( "LNETL0: Socket %d received %d bytes", _Sock, len ); }*/ _BytesReceived += len; return CSock::Ok; } /* * Sets the socket in nonblocking mode */ void CSock::setNonBlockingMode ( bool bm ) { if ( _NonBlocking != bm ) { #ifdef NL_OS_WINDOWS u_long b = bm; if ( ioctlsocket( _Sock, FIONBIO, &b ) != 0 ) #else if ( fcntl( _Sock, F_SETFL, FNDELAY | fcntl( _Sock, F_GETFL, 0 ) ) == -1 ) #endif { throw ESocket( "Cannot set nonblocking mode" ); } _NonBlocking = bm; } } /* * Sets the send buffer size */ void CSock::setSendBufferSize( sint32 size ) { setsockopt( _Sock, SOL_SOCKET, SO_SNDBUF, (char*)(&size), (socklen_t)sizeof(size) ); } /* * Gets the send buffer size */ sint32 CSock::getSendBufferSize() { int size = -1; socklen_t bufsize; getsockopt( _Sock, SOL_SOCKET, SO_SNDBUF, (char*)(&size), &bufsize ); return size; } } // NLNET