/*********************************************************** * WindowMud - Window Mud Server * * File: Communication.cpp * * Usage: Winsock tcp/ip telnet player communications * * * * No warranty is given or implied. * * There is no license associated with this code. * * There are no restrictions on the use of this code. * * This code may be used in any way the reader wishes. * * No credit or credits are given and none are required. * * The reader may take this code and claim they wrote it. * ************************************************************/ /*********************************************************** * Includes * ************************************************************/ #include "stdafx.h" // precompiled headers #include "Communication.h" // Server #include "Dnode.h" #include "Descriptor.h" #include "Log.h" #include "Utility.h" /*********************************************************** * Globals * ************************************************************/ extern CString ErrorMsg; extern bool StateConnections; extern bool StateRunning; extern bool StateStopping; extern bool WinSockCleanUp; Dnode *pDnodeActor; fd_set ExcSet; fd_set InpSet; int ListenSocket; fd_set OutSet; /*********************************************************** * Communication class constructor * ***********************************************************/ Communication::Communication() { } /*********************************************************** * Communication class destructor * ***********************************************************/ Communication::~Communication() { } //////////////////////////////////////////////////////////// // Public functions static // //////////////////////////////////////////////////////////// /*********************************************************** * Check for new connections * ***********************************************************/ void Communication::SockCheckForNewConnections() { Dnode *pDnodeActor; int SocketCount; static struct timeval TimeOut; TimeOut.tv_sec = 0; TimeOut.tv_usec = 1; FD_ZERO(&InpSet); FD_ZERO(&OutSet); FD_ZERO(&ExcSet); FD_SET(ListenSocket, &InpSet); // Check status of connections Descriptor::SetpDnodeCursorFirst(); while (!Descriptor::EndOfDnodeList()) { // Loop thru all connections pDnodeActor = Descriptor::GetDnode(); FD_SET(pDnodeActor->DnodeFd, &InpSet); FD_SET(pDnodeActor->DnodeFd, &OutSet); FD_SET(pDnodeActor->DnodeFd, &ExcSet); Descriptor::SetpDnodeCursorNext(); } // Detect socket state (pg 159-161) SocketCount = select(-1, &InpSet, &OutSet, &ExcSet, &TimeOut); if (SocketCount == -1) { // Select failed ErrorMsg.Format("%s", strerror(errno)); ErrorMsg = "Communication::SockCheckForNewConnections: select: " + ErrorMsg; Utility::BlowUp(); } if (FD_ISSET(ListenSocket, &InpSet)) { // Process new connection SockNewConnection(); } } /*********************************************************** * Close port * ***********************************************************/ void Communication::SockClosePort(int Port) { CString LogBuf; int Result; // Close the socket (pg 70) Result = ::closesocket(ListenSocket); if (Result!= 0) { // Sock error on close ErrorMsg = "Communication::SockClosePort - Error: closesocket"; Utility::BlowUp(); } LogBuf.Format("%d", Port); LogBuf = "Closed port " + LogBuf; Log::LogIt(LogBuf); // Free WinSock resources WinSockCleanUp = false; Result = WSACleanup(); if (Result!= 0) { // WinSock cleanup failed ErrorMsg = "Communication::SockClosePort - Error: WSACleanup"; Utility::BlowUp(); } } /*********************************************************** * Open port * ***********************************************************/ void Communication::SockOpenPort(int Port) { unsigned long FionbioParm; struct linger ld; CString LogBuf; int OptionValue; int Result; struct sockaddr_in sa; WORD VersionRequested; WSADATA WsaData; FionbioParm = 1; ld.l_onoff = 0; ld.l_linger = 0; OptionValue = 1; VersionRequested = MAKEWORD(1, 1); // Initialize WinSock API (pg 320) Result = WSAStartup(VersionRequested, &WsaData); if (Result != 0) { // WinSock is not available ErrorMsg.Format("%s", strerror(errno)); ErrorMsg = "Communication::SockOpenPort - WinSock not available: " + ErrorMsg; Utility::BlowUp(); } WinSockCleanUp = true; // Establish a streaming socket (pg 51) ListenSocket = socket(AF_INET, SOCK_STREAM, 0); if (ListenSocket == SOCKET_ERROR) { // No socket, time to die ErrorMsg.Format("%s", strerror(errno)); ErrorMsg = "Communication:SockOpenPort - Error: initializing socket: " + ErrorMsg; Utility::BlowUp(); } // Enable reuse of local socket name (pg 305) Result = setsockopt(ListenSocket, SOL_SOCKET, SO_REUSEADDR, (char *)&OptionValue, sizeof(OptionValue)); if (Result != 0) { // Set socket option failed ErrorMsg.Format("%s", strerror(errno)); ErrorMsg = "Communication:SockOpenPort - Error: setsockopt: SOL_SOCKET SO_REUSEADDR" + ErrorMsg; Utility::BlowUp(); } // Establish underlying TCP/IP buffer size (pg 305) Result = setsockopt(ListenSocket, SOL_SOCKET, SO_SNDBUF, (char *) &OptionValue, sizeof(OptionValue)); if (Result != 0) { // Set socket option failed ErrorMsg.Format("%s", strerror(errno)); ErrorMsg = "Communication:SockOpenPort - Error: setsockopt SNDBUF: " + ErrorMsg; Utility::BlowUp(); } // Disable linger and set timeout to zero (pg 301) Result = setsockopt(ListenSocket, SOL_SOCKET, SO_LINGER, (char *) &ld, sizeof(ld)); if (Result != 0) { // Set socket option failed ErrorMsg.Format("%s", strerror(errno)); ErrorMsg = "Communication:SockOpenPort - Error: setsockopt SO_LINGER: " + ErrorMsg; Utility::BlowUp(); } // Initialize sockaddr structure (pg 53) memset((char *)&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = INADDR_ANY; sa.sin_port = htons(Port); // Associate a local address with the socket (pg 56) Result = bind(ListenSocket, (struct sockaddr *)&sa, sizeof(sa)); if (Result != 0) { // Bind socket failed ErrorMsg.Format("%s", strerror(errno)); ErrorMsg = "Communication:SockOpenPort - Error: bind " + ErrorMsg; Utility::BlowUp(); } // Make socket nonblocking (pg 286) Result = ioctlsocket(ListenSocket, FIONBIO, &FionbioParm); if (Result != 0) { // Failed to make socket nonblocking ErrorMsg.Format("%s", strerror(errno)); ErrorMsg = "Communication:SockOpenPort ioctlsocket: " + ErrorMsg; Utility::BlowUp(); } // Listen on port and limit pending connections (pg 60) Result = listen(ListenSocket, 20); if (Result != 0) { // Listen failed ErrorMsg.Format("%s", strerror(errno)); ErrorMsg = "Communication:SockOpenPort - Error: listen " + ErrorMsg; Utility::BlowUp(); } LogBuf.Format("%d", Port); LogBuf = "Listening on port " + LogBuf; Log::LogIt(LogBuf); } /*********************************************************** * Receive player input, check player status, send output * ***********************************************************/ void Communication::SockRecv() { int ConnectionCount; int DnodeFdSave; char InpStr[MAX_INPUT_LENGTH]; CString LogBuf; int RecvByteCount; //************************ //* Is Game is stopping? * //************************ if (StateStopping) { // Game is stopping Descriptor::SetpDnodeCursorFirst(); while (!Descriptor::EndOfDnodeList()) { // Loop thru all connections pDnodeActor = Descriptor::GetDnode(); if (!pDnodeActor->PlayerStateBye) { // If player is not already going Bye Bye pDnodeActor->PlayerStateBye = true; pDnodeActor->PlayerStateSendBanner = false; pDnodeActor->PlayerOut += "\r\n"; pDnodeActor->PlayerOut += "Game is stopping ... Bye Bye!"; pDnodeActor->PlayerOut += "\r\n"; LogBuf = "Unknown"; LogBuf += " will be force disconnected"; Log::LogIt(LogBuf); } Descriptor::SetpDnodeCursorNext(); } } //*********************** //* Service connections * //*********************** Descriptor::SetpDnodeCursorFirst(); while (!Descriptor::EndOfDnodeList()) { // Loop thru all connections pDnodeActor = Descriptor::GetDnode(); //*************************** //* Check connection status * //*************************** if (FD_ISSET(pDnodeActor->DnodeFd, &OutSet)) { // This code don't do nothin', sample OutSet check int dummy = 0; } if (FD_ISSET(pDnodeActor->DnodeFd, &ExcSet)) { // Kick out connections with exceptions pDnodeActor->PlayerStateBye = true; } else { // Good connection if (FD_ISSET(pDnodeActor->DnodeFd, &InpSet)) { // Receive memset(InpStr, '\0', sizeof(InpStr)); RecvByteCount = ::recv(pDnodeActor->DnodeFd, InpStr, MAX_INPUT_LENGTH-1, 0); if (RecvByteCount == 0) { // Should be input but there is none -- disconnected ?? pDnodeActor->PlayerStateBye = true; } if (WSAGetLastError() == WSAEWOULDBLOCK || WSAGetLastError() == WSAEINTR) { // Nothing worth processing RecvByteCount = 0; } if (RecvByteCount > 0) { // Got something ... append it to player input pDnodeActor->PlayerInp += InpStr; } } } //***************************** //* Banner for new connection * //***************************** if (pDnodeActor->PlayerStateSendBanner) { // New connection pDnodeActor->PlayerStateSendBanner = false; // Send greeting pDnodeActor->PlayerOut += "\r\n"; pDnodeActor->PlayerOut += "Create a new character Y-N?"; pDnodeActor->PlayerOut += "\r\n"; } //********************** //* Send player output * //********************** if (pDnodeActor->PlayerOut.GetLength() > 0) { // 'send' resquires a standard c string SockSend((LPCTSTR) pDnodeActor->PlayerOut); } //********************** //* Is player quiting? * //********************** if (pDnodeActor->PlayerStateBye) { // Player leaving the game ... disconnect them DnodeFdSave = pDnodeActor->DnodeFd; if (Descriptor::DeleteNode()) { // When connection is deleted from list, log it LogBuf.Format("%d", DnodeFdSave); LogBuf = "Closed connection on descriptor " + LogBuf; Log::LogIt(LogBuf); ConnectionCount = Dnode::GetCount(); if (ConnectionCount == 1) { // Connection count is one means no players are connected if (StateStopping) { // OMugs is stopping StateRunning = false; } } } // Skip to next dnode, this player's dnode has been deleted Descriptor::SetpDnodeCursorNext(); continue; } // Get the next Dnode to process Descriptor::SetpDnodeCursorNext(); } } //////////////////////////////////////////////////////////// // Private functions static // //////////////////////////////////////////////////////////// /*********************************************************** * New connection * ***********************************************************/ void Communication::SockNewConnection() { unsigned long FionbioParm; CString LogBuf; int Result; struct sockaddr_in Sock; int SocketHandle; int SocketSize; CString IpAddress; CString TmpStr; FionbioParm = 1; SocketSize = sizeof(Sock); // Return a new socket for a newly created connection (pg 63) SocketHandle = accept(ListenSocket, (struct sockaddr *)&Sock, &SocketSize); if (!SocketHandle) { // Accept failed ErrorMsg.Format("%s", strerror(errno)); ErrorMsg = "Communication::SockNewConnection - Error: accept: " + ErrorMsg; Utility::BlowUp(); } IpAddress = inet_ntoa(Sock.sin_addr); // Make socket nonblocking (pg 286) Result = ioctlsocket(SocketHandle, FIONBIO, &FionbioParm); if (Result != 0) { // Failed to make socket nonblocking ErrorMsg.Format("%s", strerror(errno)); ErrorMsg = "Communication::SockNewConnection - Error: ioctlsocket " + ErrorMsg; Utility::BlowUp(); } TmpStr.Format("%d", SocketHandle); LogBuf = "New connection with socket handle "; LogBuf += TmpStr; LogBuf += " and address "; LogBuf += IpAddress; Log::LogIt(LogBuf); pDnodeActor = new Dnode(SocketHandle, IpAddress); Descriptor::AppendIt(); StateConnections = true; } /*********************************************************** * Send message * ***********************************************************/ void Communication::SockSend(const char *SendBuffer) { int Length; int Written; if (!SendBuffer || SendBuffer[0] == '\0') { // Nothing to send return; } Length = strlen(SendBuffer); Written = ::send(pDnodeActor->DnodeFd, SendBuffer, Length, 0); if (Written == SOCKET_ERROR) { // Send failed ErrorMsg.Format("%s", strerror(errno)); ErrorMsg = "Communication::SockSend - Error: send: " + ErrorMsg; Utility::BlowUp(); } if (Written == Length) { // Everything was sent pDnodeActor->PlayerOut = ""; } else { // Some was not sent pDnodeActor->PlayerOut = pDnodeActor->PlayerOut.Right(Length-Written); } }