/**************************************************************************** Copyright (c) 2006, Radon Labs GmbH Copyright (c) 2011-2013,WebJet Business Division,CYOU http://www.genesis-3d.com.cn Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ #if WIN32 #include "stdneb.h" #include "net/socket/socket.h" #if __XBOX360__ #include "net/xbox360/xbox360network.h" #endif namespace Win360 { __ImplementClass(Win360::Win360Socket, 'XSCK', Core::RefCounted); using namespace Util; bool Win360Socket::NetworkInitialized = false; //------------------------------------------------------------------------------ /** */ Win360Socket::Win360Socket() : error(ErrorNone), sock(0), isBlocking(true), isBound(false) { n_assert(NetworkInitialized); } //------------------------------------------------------------------------------ /** */ Win360Socket::~Win360Socket() { if (this->IsOpen()) { this->Close(); } } //------------------------------------------------------------------------------ /** This is a one-time init for the Windows Sockets system. The method is called from SysFunc::Setup() once at startup before any threads are launched. */ void Win360Socket::InitNetwork() { n_assert(!NetworkInitialized); // first setup the Xbox networking stuff #if __XBOX360__ Xbox360::Xbox360Network::SetupNetwork(); #endif // now proceed as usual WSADATA wsaData; int result = WSAStartup(MAKEWORD(2,2), &wsaData); if (0 != result) { n_error("WSAStartup() failed with '%s'!", WSAErrorToString(result).AsCharPtr()); } NetworkInitialized = true; } //------------------------------------------------------------------------------ /** */ bool Win360Socket::Open(Protocol protocol) { n_assert(!this->IsOpen()); this->ClearError(); int sockType; int protType; switch (protocol) { case TCP: sockType = SOCK_STREAM; protType = IPPROTO_TCP; break; case UDP: sockType = SOCK_DGRAM; protType = IPPROTO_UDP; break; default: // can't happen. n_error("Invalid socket type!"); sockType = SOCK_STREAM; protType = IPPROTO_TCP; break; } this->sock = socket(AF_INET, sockType, protType); if (INVALID_SOCKET == this->sock) { this->sock = 0; this->SetToLastWSAError(); return false; } return true; } //------------------------------------------------------------------------------ /** Open the socket object with an existing initialized system socket. This is a private method and only called by Accept() on the new socket created by the accept() function. */ void Win360Socket::OpenWithExistingSocket(SOCKET s) { n_assert(INVALID_SOCKET != s); n_assert(!this->IsOpen()); this->sock = s; } //------------------------------------------------------------------------------ /** */ void Win360Socket::Close() { n_assert(this->IsOpen()); this->ClearError(); int res = 0; if (this->IsConnected()) { res = shutdown(this->sock, SD_BOTH); if (SOCKET_ERROR == res) { // note: the shutdown function may return NotConnected, this // is not really an error this->SetToLastWSAError(); if (ErrorNotConnected != this->error) { n_printf("Win360Socket::Close(): shutdown() failed with '%s'.\n", this->GetErrorString().AsCharPtr()); } } } res = closesocket(this->sock); if (SOCKET_ERROR == res) { this->SetToLastWSAError(); n_printf("Win360Socket::Close(): closesocket() failed with '%s'.\n", this->GetErrorString().AsCharPtr()); } this->sock = 0; } //------------------------------------------------------------------------------ /** Set a boolean option on the socket. This is a private helper function. */ void Win360Socket::SetBoolOption(int optName, bool val) { n_assert(this->IsOpen()); this->ClearError(); int level = SOL_SOCKET; if (optName == TCP_NODELAY) { level = IPPROTO_TCP; } int optVal = val ? 1 : 0; int res = setsockopt(this->sock, level, optName, (const char*) &optVal, sizeof(optVal)); if (SOCKET_ERROR == res) { this->SetToLastWSAError(); n_printf("Win360Socket::SetBoolOption(): setsockopt() failed with '%s'.\n", this->GetErrorString().AsCharPtr()); } } //------------------------------------------------------------------------------ /** Get a boolean option on the socket. This is a private helper function. */ bool Win360Socket::GetBoolOption(int optName) { n_assert(this->IsOpen()); this->ClearError(); int level = SOL_SOCKET; if (optName == TCP_NODELAY) { level = IPPROTO_TCP; } int optVal = 0; int optValSize = sizeof(optVal); int res = getsockopt(this->sock, level, optName, (char*) &optVal, &optValSize); if (SOCKET_ERROR == res) { this->SetToLastWSAError(); n_printf("Win360Socket::GetBoolOption(): getsockopt() failed with '%s'.\n", this->GetErrorString().AsCharPtr()); } n_assert(sizeof(optVal) == optValSize); return (0 != optVal); } //------------------------------------------------------------------------------ /** Set an int socket option. This is a private helper function. */ void Win360Socket::SetIntOption(int optName, int val) { n_assert(this->IsOpen()); this->ClearError(); int res = setsockopt(this->sock, SOL_SOCKET, optName, (const char*) &val, sizeof(val)); if (SOCKET_ERROR == res) { this->SetToLastWSAError(); n_printf("Win360Socket::SetIntOption(): setsockopt() failed with '%s'.\n", this->GetErrorString().AsCharPtr()); } } //------------------------------------------------------------------------------ /** Get an int socket option. This is a private helper function. */ int Win360Socket::GetIntOption(int optName) { n_assert(this->IsOpen()); this->ClearError(); int optVal = 0; int optValSize = sizeof(optVal); int res = getsockopt(this->sock, SOL_SOCKET, optName, (char*) &optVal, &optValSize); if (SOCKET_ERROR == res) { this->SetToLastWSAError(); n_printf("Win360Socket::GetIntOption(): getsockopt() failed with '%s'.\n", this->GetErrorString().AsCharPtr()); } n_assert(sizeof(optVal) == optValSize); return optVal; } //------------------------------------------------------------------------------ /** Set the socket to blocking mode. */ void Win360Socket::SetBlocking(bool b) { n_assert(this->IsOpen()); this->ClearError(); u_long arg = b ? 0 : 1; int res = ioctlsocket(this->sock, FIONBIO, &arg); if (SOCKET_ERROR == res) { this->SetToLastWSAError(); n_printf("Win360Socket::SetBlocking(): ioctlsocket() failed with '%s'.\n", this->GetErrorString().AsCharPtr()); } this->isBlocking = b; } //------------------------------------------------------------------------------ /** Bind the socket to its ip address set with SetAddress() and SetPort(). After binding the socket to an address, call the Listen() method to wait for incoming connections. This method only makes sense for server sockets. */ bool Win360Socket::Bind() { n_assert(this->IsOpen()); n_assert(!this->IsBound()); this->ClearError(); const sockaddr_in& sockAddr = this->addr.GetSockAddr(); int res = bind(this->sock, (const sockaddr*) &sockAddr, sizeof(sockAddr)); if (SOCKET_ERROR == res) { this->SetToLastWSAError(); n_printf("Win360Socket::Bind(): bind() failed with '%s'!\n", this->GetErrorString().AsCharPtr()); return false; } this->isBound = true; return true; } //------------------------------------------------------------------------------ /** Wait for incoming connections to a server socket. Call this method on server side after binding the socket to its address. */ bool Win360Socket::Listen() { n_assert(this->IsOpen()); n_assert(this->IsBound()); this->ClearError(); int res = listen(this->sock, SOMAXCONN); if (SOCKET_ERROR == res) { this->SetToLastWSAError(); n_printf("Win360Socket::Listen(): listen() failed with '%s'!\n", this->GetErrorString().AsCharPtr()); return false; } return true; } //------------------------------------------------------------------------------ /** Accept an incoming connection to a server socket. This will spawn a new socket for the connection which will be returned in the provided pointer reference. The address of the returned socket will be set to the address of the "connecting entity". */ bool Win360Socket::Accept(GPtr& outSocket) { n_assert(this->IsOpen()); n_assert(this->IsBound()); this->ClearError(); outSocket = 0; sockaddr_in sockAddr; int sockAddrSize = sizeof(sockAddr); SOCKET newSocket = accept(this->sock, (sockaddr*) &sockAddr, &sockAddrSize); if (INVALID_SOCKET == newSocket) { this->SetToLastWSAError(); n_printf("Win360Socket::Accept(): accept() failed with '%s'!\n", this->GetErrorString().AsCharPtr()); return false; } outSocket = Net::Socket::Create(); outSocket->SetAddress(Win360IpAddress(sockAddr)); outSocket->OpenWithExistingSocket(newSocket); outSocket->SetBlocking(this->isBlocking); return true; } //------------------------------------------------------------------------------ /** Connect to a server socket. This method is called by a client socket to connect to a server socket identified by the socket object's address. A non-blocking socket will return immediately with WouldBlock, since the connection cannot be established immediately. In this case, just continue to call Connect() until the method returns Success, or alternative, check the IsConnected() method, which will also return true once the connection has been establish. */ Win360Socket::Result Win360Socket::Connect() { n_assert(this->IsOpen()); n_assert(!this->IsBound()); this->ClearError(); const sockaddr_in& sockAddr = this->addr.GetSockAddr(); int res = connect(this->sock, (const sockaddr*) &sockAddr, sizeof(sockAddr)); if (SOCKET_ERROR == res) { // special handling for non-blocking sockets int wsaError = WSAGetLastError(); if (!this->GetBlocking()) { if (WSAEWOULDBLOCK == wsaError) { return WouldBlock; } else if (WSAEALREADY == wsaError) { // connection is underway but not finished yet return WouldBlock; } else if (WSAEISCONN == wsaError) { // the connection is established return Success; } // fallthrough: a normal error } this->SetWSAError(wsaError); n_printf("Win360Socket::Connect(): connect() failed with '%s'!\n", this->GetErrorString().AsCharPtr()); return Error; } return Success; } //------------------------------------------------------------------------------ /** This tests if the socket is actually connected by doing a select() on the socket to probe for writability. So the IsConnected() method basically checks whether data can be sent through the socket. */ bool Win360Socket::IsConnected() { n_assert(this->IsOpen()); fd_set writeSet = { 1, { this->sock } }; TIMEVAL timeVal = { 0, 0 }; int res = select(0, 0, &writeSet, 0, &timeVal); if (SOCKET_ERROR == res) { this->SetToLastWSAError(); n_printf("Win360Socket::IsConnected(): select() failed with '%s'!\n", this->GetErrorString().AsCharPtr()); return false; } else if (0 == res) { return false; } else { return true; } } //------------------------------------------------------------------------------ /** Send raw data into the socket. Note that depending on the buffer size of the underlying socket implementation and other sockets, the method may not be able to send all provided data. In this case, the returned content of bytesSent will be less then numBytes, even though the return value will be Success. It is up to the caller to handle the extra data which hasn't been sent with the current call. */ Win360Socket::Result Win360Socket::Send(const void* buf, SizeT numBytes, SizeT& bytesSent) { n_assert(this->IsOpen()); n_assert(0 != buf); this->ClearError(); bytesSent = 0; int res = send(this->sock, (const char*) buf, numBytes, 0); if (SOCKET_ERROR == res) { if (WSAEWOULDBLOCK == res) { return WouldBlock; } else { this->SetToLastWSAError(); n_printf("Win360Socket::Send(): send() failed with '%s'\n", this->GetErrorString().AsCharPtr()); return Error; } } else { bytesSent = res; return Success; } } //------------------------------------------------------------------------------ /** This method checks if the socket has received data available. Use this method in a loop with Recv() to get all data waiting at the socket. This method will never block. */ bool Win360Socket::HasRecvData() { n_assert(this->IsOpen()); fd_set readSet = { 1, { this->sock } }; TIMEVAL timeVal = { 0, 0 }; int res = select(0, &readSet, 0, 0, &timeVal); if (SOCKET_ERROR == res) { this->SetToLastWSAError(); n_printf("Win360Socket::HasRecvData(): select() failed with '%s'!\n", this->GetErrorString().AsCharPtr()); return false; } else if (0 == res) { return false; } else { return true; } } //------------------------------------------------------------------------------ /** Receive raw data from a socket and write the received data into the provided buffer. On a blocking socket this method will block until data arrives at the socket. A non-blocking socket would immediately return in this case with a WouldBlock result. When valid data has been received the method will return with a Success result and the bytesReceived argument will contain the number of received bytes. It is not guaranteed that a single receive will return all data waiting on the socket. To make sure that the socket is really empty, call Recv() in a loop until HasRecvData() returns false. When the socket has been gracefully closed by the other side, the method will return with a Closed return value. Everything else will return with an Error return code. Call GetErrorCode() or GetErrorString() to find out more in this case. */ Win360Socket::Result Win360Socket::Recv(void* buf, SizeT bufSize, SizeT& bytesReceived) { n_assert(this->IsOpen()); n_assert(0 != buf); this->ClearError(); bytesReceived = 0; int res = recv(this->sock, (char*) buf, bufSize, 0); if (0 == res) { // connection has been gracefully closed return Closed; } else if (SOCKET_ERROR == res) { // catch special error conditions int wsaError = WSAGetLastError(); if (WSAEMSGSIZE == wsaError) { // more data is pending bytesReceived = bufSize; // FIXME: is this correct? return Success; } if (!this->isBlocking) { // socket is non-blocking and no data is available if (WSAEWOULDBLOCK == wsaError) { return WouldBlock; } } // fallthrough: a real error this->SetToLastWSAError(); n_printf("Win360Socket::Recv(): recv() failed with '%s'\n", this->GetErrorString().AsCharPtr()); return Error; } else { bytesReceived = res; return Success; } } //------------------------------------------------------------------------------ /** FIXME: this is the send method for connectionless sockets using the UDP protocol. */ Win360Socket::Result Win360Socket::SendTo(const void* /*buf*/, SizeT /*numBytes*/, uint /*addr*/, ushort /*port*/, SizeT& /*bytesSent*/) { n_error("Win360Socket::SendTo(): IMPLEMENT ME!"); return Error; } //------------------------------------------------------------------------------ /** FIXME: this is the recv method for connectionless socket using the UDP protocol. */ Win360Socket::Result Win360Socket::RecvFrom(void* /*buf*/, SizeT /*bufSize*/, uint /*addr*/, ushort /*port*/, SizeT& /*bytesReceived*/) { n_error("Win360Socket::RecvFrom(): IMPLEMENT ME!"); return Error; } //------------------------------------------------------------------------------ /** Sets the internal error code to NoError. */ void Win360Socket::ClearError() { this->error = ErrorNone; } //------------------------------------------------------------------------------ /** Sets the internal error code to WSAGetLastError(). */ void Win360Socket::SetToLastWSAError() { this->error = WSAErrorToErrorCode(WSAGetLastError()); } //------------------------------------------------------------------------------ /** Sets the provided WSA error as error code. */ void Win360Socket::SetWSAError(int wsaError) { this->error = WSAErrorToErrorCode(wsaError); } //------------------------------------------------------------------------------ /** This method converts an Windows Socket error code into a portable error code used by Win360Socket. */ Win360Socket::ErrorCode Win360Socket::WSAErrorToErrorCode(int wsaErrorCode) { switch (wsaErrorCode) { case WSAEINTR: return ErrorInterrupted; break; case WSAEACCES: return ErrorPermissionDenied; break; case WSAEFAULT: return ErrorBadAddress; break; case WSAEINVAL: return ErrorInvalidArgument; break; case WSAEMFILE: return ErrorTooManyOpenFiles; break; case WSAEWOULDBLOCK: return ErrorWouldBlock; break; case WSAEINPROGRESS: return ErrorInProgress; break; case WSAEALREADY: return ErrorAlreadyInProgress; break; case WSAENOTSOCK: return ErrorNotASocket; break; case WSAEDESTADDRREQ: return ErrorDestAddrRequired; break; case WSAEMSGSIZE: return ErrorMsgTooLong; break; case WSAEPROTOTYPE: return ErrorInvalidProtocol; break; case WSAENOPROTOOPT: return ErrorBadProtocolOption; break; case WSAEPROTONOSUPPORT: return ErrorProtocolNotSupported; break; case WSAESOCKTNOSUPPORT: return ErrorSocketTypeNotSupported; break; case WSAEOPNOTSUPP: return ErrorOperationNotSupported; break; case WSAEPFNOSUPPORT: return ErrorProtFamilyNotSupported; break; case WSAEAFNOSUPPORT: return ErrorAddrFamilyNotSupported; break; case WSAEADDRINUSE: return ErrorAddrInUse; break; case WSAEADDRNOTAVAIL: return ErrorAddrNotAvailable; break; case WSAENETDOWN: return ErrorNetDown; break; case WSAENETUNREACH: return ErrorNetUnreachable; break; case WSAENETRESET: return ErrorNetReset; break; case WSAECONNABORTED: return ErrorConnectionAborted; break; case WSAECONNRESET: return ErrorConnectionReset; break; case WSAENOBUFS: return ErrorNoBufferSpace; break; case WSAEISCONN: return ErrorIsConnected; break; case WSAENOTCONN: return ErrorNotConnected; break; case WSAESHUTDOWN: return ErrorIsShutdown; break; case WSAETIMEDOUT: return ErrorIsTimedOut; break; case WSAECONNREFUSED: return ErrorConnectionRefused; break; case WSAEHOSTDOWN: return ErrorHostDown; break; case WSAEHOSTUNREACH: return ErrorHostUnreachable; break; case WSAEPROCLIM: return ErrorTooManyProcesses; break; case WSASYSNOTREADY: return ErrorSystemNotReady; break; case WSAVERNOTSUPPORTED: return ErrorVersionNotSupported; break; case WSANOTINITIALISED: return ErrorNotInitialized; break; case WSAEDISCON: return ErrorDisconnecting; break; case WSATYPE_NOT_FOUND: return ErrorTypeNotFound; break; case WSAHOST_NOT_FOUND: return ErrorHostNotFound; break; case WSATRY_AGAIN: return ErrorTryAgain; break; case WSANO_RECOVERY: return ErrorNoRecovery; break; case WSANO_DATA: return ErrorNoData; break; default: return ErrorUnknown; break; } } //------------------------------------------------------------------------------ /** Convert an error code to a human readable error message. */ String Win360Socket::ErrorAsString(ErrorCode err) { switch (err) { case ErrorNone: return "No error."; case ErrorUnknown: return "Unknown error (not mapped by Win360Socket class)."; case ErrorInterrupted: return "Interrupted function call."; case ErrorPermissionDenied: return "Permission denied."; case ErrorBadAddress: return "Bad address."; case ErrorInvalidArgument: return "Invalid argument."; case ErrorTooManyOpenFiles: return "Too many open files (sockets)."; case ErrorWouldBlock: return "Operation would block."; case ErrorInProgress: return "Operation now in progress."; case ErrorAlreadyInProgress: return "Operation already in progress."; case ErrorNotASocket: return "Socket operation on non-socket."; case ErrorDestAddrRequired: return "Destination address required"; case ErrorMsgTooLong: return "Message too long."; case ErrorInvalidProtocol: return "Protocol wrong type for socket."; case ErrorBadProtocolOption: return "Bad protocal option."; case ErrorProtocolNotSupported: return "Protocol not supported."; case ErrorSocketTypeNotSupported: return "Socket type not supported."; case ErrorOperationNotSupported: return "Operation not supported."; case ErrorProtFamilyNotSupported: return "Protocol family not supported."; case ErrorAddrFamilyNotSupported: return "Address family not supported by protocol family."; case ErrorAddrInUse: return "Address already in use."; case ErrorAddrNotAvailable: return "Cannot assign requested address."; case ErrorNetDown: return "Network is down."; case ErrorNetUnreachable: return "Network is unreachable."; case ErrorNetReset: return "Network dropped connection on reset."; case ErrorConnectionAborted: return "Software caused connection abort."; case ErrorConnectionReset: return "Connection reset by peer."; case ErrorNoBufferSpace: return "No buffer space available."; case ErrorIsConnected: return "Socket is already connected."; case ErrorNotConnected: return "Socket is not connected."; case ErrorIsShutdown: return "Cannot send after socket shutdown."; case ErrorIsTimedOut: return "Connection timed out."; case ErrorConnectionRefused: return "Connection refused."; case ErrorHostDown: return "Host is down."; case ErrorHostUnreachable: return "No route to host."; case ErrorTooManyProcesses: return "Too many processes."; case ErrorSystemNotReady: return "Network subsystem is unavailable."; case ErrorVersionNotSupported: return "Winsock.dll version out of range."; case ErrorNotInitialized: return "Successful WSAStartup not yet performed."; case ErrorDisconnecting: return "Graceful shutdown in progress."; case ErrorTypeNotFound: return "Class type not found."; case ErrorHostNotFound: return "Host not found."; case ErrorTryAgain: return "Nonauthoritative host not found."; case ErrorNoRecovery: return "This is a nonrecoverable error."; case ErrorNoData: return "Valid name, no data record of requested type."; default: n_error("Win360Socket::ErrorAsString(): unhandled error code!"); return ""; } } //------------------------------------------------------------------------------ /** */ String Win360Socket::WSAErrorToString(int wsaError) { return ErrorAsString(WSAErrorToErrorCode(wsaError)); } //------------------------------------------------------------------------------ /** */ String Win360Socket::GetErrorString() const { return ErrorAsString(this->error); } //------------------------------------------------------------------------------ /** */ bool Win360Socket::IsNetworkInitialized() { return NetworkInitialized; } } // namespace Win360 #endif