From b9172acc5cc92f53d7bad01e8bb130864e2c97c8 Mon Sep 17 00:00:00 2001 From: Mark Vejvoda Date: Tue, 16 Mar 2010 21:37:11 +0000 Subject: [PATCH] Bugfix for multiplayer binary and data checksum checking. --- .../glest_game/network/client_interface.cpp | 527 ++++++++++++ .../include/platform/posix/socket.h | 112 +++ .../include/platform/win32/socket.h | 114 +++ .../sources/platform/posix/socket.cpp | 704 ++++++++++++++++ .../sources/platform/win32/socket.cpp | 797 ++++++++++++++++++ 5 files changed, 2254 insertions(+) create mode 100755 source/glest_game/network/client_interface.cpp create mode 100644 source/shared_lib/include/platform/posix/socket.h create mode 100644 source/shared_lib/include/platform/win32/socket.h create mode 100644 source/shared_lib/sources/platform/posix/socket.cpp create mode 100644 source/shared_lib/sources/platform/win32/socket.cpp diff --git a/source/glest_game/network/client_interface.cpp b/source/glest_game/network/client_interface.cpp new file mode 100755 index 000000000..309174337 --- /dev/null +++ b/source/glest_game/network/client_interface.cpp @@ -0,0 +1,527 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2001-2008 Martiño Figueroa +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#include "client_interface.h" + +#include +#include + +#include "platform_util.h" +#include "game_util.h" +#include "conversion.h" +#include "config.h" +#include "lang.h" +#include "leak_dumper.h" + +#include "map.h" +#include "config.h" +#include "logger.h" + +using namespace std; +using namespace Shared::Platform; +using namespace Shared::Util; + +namespace Glest{ namespace Game{ + +// ===================================================== +// class ClientInterface +// ===================================================== + +const int ClientInterface::messageWaitTimeout= 10000; //10 seconds +const int ClientInterface::waitSleepTime= 50; + +ClientInterface::ClientInterface(){ + clientSocket= NULL; + launchGame= false; + introDone= false; + playerIndex= -1; + + networkGameDataSynchCheckOkMap = false; + networkGameDataSynchCheckOkTile = false; + networkGameDataSynchCheckOkTech = false; +} + +ClientInterface::~ClientInterface() +{ + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] START\n",__FILE__,__FUNCTION__); + + if(clientSocket != NULL && clientSocket->isConnected() == true) + { + string sQuitText = getHostName() + " has chosen to leave the game!"; + sendTextMessage(sQuitText,-1); + } + + delete clientSocket; + clientSocket = NULL; + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] END\n",__FILE__,__FUNCTION__); +} + +void ClientInterface::connect(const Ip &ip, int port) +{ + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] START\n",__FILE__,__FUNCTION__); + + delete clientSocket; + + this->ip = ip; + this->port = port; + + clientSocket= new ClientSocket(); + clientSocket->setBlock(false); + clientSocket->connect(ip, port); + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] END - socket = %d\n",__FILE__,__FUNCTION__,clientSocket->getSocketId()); +} + +void ClientInterface::reset() +{ + if(getSocket() != NULL) + { + string sQuitText = getHostName() + " has chosen to leave the game!"; + sendTextMessage(sQuitText,-1); + close(); + } +} + +void ClientInterface::update() +{ + NetworkMessageCommandList networkMessageCommandList; + + //send as many commands as we can + while(!requestedCommands.empty()){ + if(networkMessageCommandList.addCommand(&requestedCommands.back())){ + requestedCommands.pop_back(); + } + else{ + break; + } + } + if(networkMessageCommandList.getCommandCount()>0){ + sendMessage(&networkMessageCommandList); + } + + //clear chat variables + chatText.clear(); + chatSender.clear(); + chatTeamIndex= -1; +} + +void ClientInterface::updateLobby() +{ + //clear chat variables + chatText.clear(); + chatSender.clear(); + chatTeamIndex= -1; + + NetworkMessageType networkMessageType = getNextMessageType(true); + + switch(networkMessageType) + { + case nmtInvalid: + break; + + case nmtIntro: + { + NetworkMessageIntro networkMessageIntro; + + if(receiveMessage(&networkMessageIntro)) + { + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] got NetworkMessageIntro\n",__FILE__,__FUNCTION__); + + //check consistency + if(Config::getInstance().getBool("NetworkConsistencyChecks")) + { + if(networkMessageIntro.getVersionString()!=getNetworkVersionString()) + { + string sErr = "Server and client versions do not match (" + networkMessageIntro.getVersionString() + "). You have to use the same binaries."; + printf("%s\n",sErr.c_str()); + throw runtime_error(sErr); + } + } + + //send intro message + NetworkMessageIntro sendNetworkMessageIntro(getNetworkVersionString(), getHostName(), -1); + + playerIndex= networkMessageIntro.getPlayerIndex(); + serverName= networkMessageIntro.getName(); + sendMessage(&sendNetworkMessageIntro); + + assert(playerIndex>=0 && playerIndexsetNetworkGameDataSynchCheckOkTile((tilesetCRC == networkMessageSynchNetworkGameData.getTilesetCRC())); + if(this->getNetworkGameDataSynchCheckOkTile() == false) + { + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] tilesetCRC mismatch, local = %d, remote = %d\n", + __FILE__,__FUNCTION__,tilesetCRC,networkMessageSynchNetworkGameData.getTilesetCRC()); + } + + + //tech, load before map because of resources + int32 techCRC = getFolderTreeContentsCheckSumRecursively(string(GameConstants::folder_path_techs) + "/" + + networkMessageSynchNetworkGameData.getTech() + "/*", ".xml", NULL); + + this->setNetworkGameDataSynchCheckOkTech((techCRC == networkMessageSynchNetworkGameData.getTechCRC())); + + if(this->getNetworkGameDataSynchCheckOkTech() == false) + { + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] techCRC mismatch, local = %d, remote = %d\n", + __FILE__,__FUNCTION__,techCRC,networkMessageSynchNetworkGameData.getTechCRC()); + } + + //map + Checksum checksum; + string file = Map::getMapPath(networkMessageSynchNetworkGameData.getMap()); + checksum.addFile(file); + int32 mapCRC = checksum.getSum(); + //if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] file = [%s] checksum = %d\n",__FILE__,__FUNCTION__,file.c_str(),mapCRC); + + this->setNetworkGameDataSynchCheckOkMap((mapCRC == networkMessageSynchNetworkGameData.getMapCRC())); + + if(this->getNetworkGameDataSynchCheckOkMap() == false) + { + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] mapCRC mismatch, local = %d, remote = %d\n", + __FILE__,__FUNCTION__,mapCRC,networkMessageSynchNetworkGameData.getMapCRC()); + } + + + this->setNetworkGameDataSynchCheckOkFogOfWar((getFogOfWar() == networkMessageSynchNetworkGameData.getFogOfWar())); + + if(this->getNetworkGameDataSynchCheckOkFogOfWar() == false) + { + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] getFogOfWar mismatch, local = %d, remote = %d\n", + __FILE__,__FUNCTION__,getFogOfWar(),networkMessageSynchNetworkGameData.getFogOfWar()); + } + + NetworkMessageSynchNetworkGameDataStatus sendNetworkMessageSynchNetworkGameDataStatus(mapCRC,tilesetCRC,techCRC,getFogOfWar()); + sendMessage(&sendNetworkMessageSynchNetworkGameDataStatus); + } + } + break; + + case nmtSynchNetworkGameDataFileCRCCheck: + { + NetworkMessageSynchNetworkGameDataFileCRCCheck networkMessageSynchNetworkGameDataFileCRCCheck; + if(receiveMessage(&networkMessageSynchNetworkGameDataFileCRCCheck)) + { + /* + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] got nmtSynchNetworkGameDataFileCRCCheck totalfiles = %d, fileindex = %d, crc = %d, file [%s]\n", + __FILE__,__FUNCTION__,networkMessageSynchNetworkGameDataFileCRCCheck.getTotalFileCount(), + networkMessageSynchNetworkGameDataFileCRCCheck.getFileIndex(), + networkMessageSynchNetworkGameDataFileCRCCheck.getFileCRC(), + networkMessageSynchNetworkGameDataFileCRCCheck.getFileName().c_str()); + */ + Checksum checksum; + string file = networkMessageSynchNetworkGameDataFileCRCCheck.getFileName(); + checksum.addFile(file); + int32 fileCRC = checksum.getSum(); + + if(fileCRC != networkMessageSynchNetworkGameDataFileCRCCheck.getFileCRC()) + { + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] got nmtSynchNetworkGameDataFileCRCCheck localCRC = %d, remoteCRC = %d, file [%s]\n", + __FILE__,__FUNCTION__,fileCRC, + networkMessageSynchNetworkGameDataFileCRCCheck.getFileCRC(), + networkMessageSynchNetworkGameDataFileCRCCheck.getFileName().c_str()); + + // Here we initiate a download of missing or mismatched content + + NetworkMessageSynchNetworkGameDataFileGet sendNetworkMessageSynchNetworkGameDataFileGet(networkMessageSynchNetworkGameDataFileCRCCheck.getFileName()); + sendMessage(&sendNetworkMessageSynchNetworkGameDataFileGet); + + FileTransferInfo fileInfo; + fileInfo.hostType = eClient; + fileInfo.serverIP = this->ip.getString(); + fileInfo.serverPort = this->port; + fileInfo.fileName = networkMessageSynchNetworkGameDataFileCRCCheck.getFileName(); + + FileTransferSocketThread *fileXferThread = new FileTransferSocketThread(fileInfo); + fileXferThread->start(); + } + + if(networkMessageSynchNetworkGameDataFileCRCCheck.getFileIndex() < networkMessageSynchNetworkGameDataFileCRCCheck.getTotalFileCount()) + { + NetworkMessageSynchNetworkGameDataFileCRCCheck sendNetworkMessageSynchNetworkGameDataFileCRCCheck( + networkMessageSynchNetworkGameDataFileCRCCheck.getTotalFileCount(), + networkMessageSynchNetworkGameDataFileCRCCheck.getFileIndex() + 1, + 0, + ""); + sendMessage(&sendNetworkMessageSynchNetworkGameDataFileCRCCheck); + } + } + } + break; + + case nmtText: + { + NetworkMessageText networkMessageText; + if(receiveMessage(&networkMessageText)) + { + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] got nmtText\n",__FILE__,__FUNCTION__); + + chatText = networkMessageText.getText(); + chatSender = networkMessageText.getSender(); + chatTeamIndex = networkMessageText.getTeamIndex(); + } + } + break; + + case nmtLaunch: + { + NetworkMessageLaunch networkMessageLaunch; + + if(receiveMessage(&networkMessageLaunch)) + { + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] got NetworkMessageLaunch\n",__FILE__,__FUNCTION__); + + networkMessageLaunch.buildGameSettings(&gameSettings); + + //replace server player by network + for(int i= 0; i readyWaitTimeout) + { + throw runtime_error("Timeout waiting for server"); + } + else + { + if(chrono.getMillis() / 1000 > lastMillisCheck) + { + lastMillisCheck = (chrono.getMillis() / 1000); + + char szBuf[1024]=""; + sprintf(szBuf,"Waiting for network: %llu seconds elapsed (maximum wait time: %d seconds)",lastMillisCheck,int(readyWaitTimeout / 1000)); + logger.add(szBuf, true); + } + } + } + else + { + throw runtime_error(string(__FILE__) + "::" + string(__FUNCTION__) + " Unexpected network message: " + intToStr(networkMessageType) ); + } + + // sleep a bit + sleep(waitSleepTime); + } + + //check checksum + if(Config::getInstance().getBool("NetworkConsistencyChecks")) + { + if(networkMessageReady.getChecksum() != checksum->getSum()) + { + string sErr = "Checksum error, you don't have the same data as the server"; + //throw runtime_error("Checksum error, you don't have the same data as the server"); + printf("%s\n",sErr.c_str()); + throw runtime_error(sErr); + } + } + + //delay the start a bit, so clients have nore room to get messages + sleep(GameConstants::networkExtraLatency); + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] END\n",__FILE__,__FUNCTION__); +} + +void ClientInterface::sendTextMessage(const string &text, int teamIndex){ + NetworkMessageText networkMessageText(text, getHostName(), teamIndex); + sendMessage(&networkMessageText); +} + +string ClientInterface::getNetworkStatus() const{ + return Lang::getInstance().get("Server") + ": " + serverName; +} + +void ClientInterface::waitForMessage() +{ + Chrono chrono; + chrono.start(); + + while(getNextMessageType(true) == nmtInvalid) + { + if(!isConnected()) + { + throw runtime_error("Disconnected"); + } + + if(chrono.getMillis()>messageWaitTimeout) + { + throw runtime_error("Timeout waiting for message"); + } + + sleep(waitSleepTime); + } +} + +void ClientInterface::quitGame(bool userManuallyQuit) +{ + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] START\n",__FILE__,__FUNCTION__); + + if(clientSocket != NULL && userManuallyQuit == true) + { + string sQuitText = getHostName() + " has chosen to leave the game!"; + sendTextMessage(sQuitText,-1); + close(); + } + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] END\n",__FILE__,__FUNCTION__); +} + +void ClientInterface::close() +{ + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] START\n",__FILE__,__FUNCTION__); + + delete clientSocket; + clientSocket= NULL; +} + +bool ClientInterface::getFogOfWar() +{ + return Config::getInstance().getBool("FogOfWar"); +} + +}}//end namespace diff --git a/source/shared_lib/include/platform/posix/socket.h b/source/shared_lib/include/platform/posix/socket.h new file mode 100644 index 000000000..7812a4810 --- /dev/null +++ b/source/shared_lib/include/platform/posix/socket.h @@ -0,0 +1,112 @@ +// ============================================================== +// This file is part of Glest Shared Library (www.glest.org) +// +// Copyright (C) 2005 Matthias Braun +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#ifndef _SHARED_PLATFORM_SOCKET_H_ +#define _SHARED_PLATFORM_SOCKET_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using std::string; + +namespace Shared{ namespace Platform{ + +// ===================================================== +// class IP +// ===================================================== + +class Ip{ +private: + unsigned char bytes[4]; + +public: + Ip(); + Ip(unsigned char byte0, unsigned char byte1, unsigned char byte2, unsigned char byte3); + Ip(const string& ipString); + + unsigned char getByte(int byteIndex) {return bytes[byteIndex];} + string getString() const; +}; + +// ===================================================== +// class Socket +// ===================================================== + +class Socket { +protected: + int sock; + long lastDebugEvent; + +public: + Socket(int sock); + Socket(); + ~Socket(); + + static bool enableDebugText; + static bool enableNetworkDebugInfo; + + // Int lookup is socket fd while bool result is whether or not that socket was signalled for reading + static bool hasDataToRead(std::map &socketTriggeredList); + static bool hasDataToRead(int socket); + bool hasDataToRead(); + void disconnectSocket(); + + int getSocketId() const { return sock; } + + int getDataToRead(); + int send(const void *data, int dataSize); + int receive(void *data, int dataSize); + int peek(void *data, int dataSize); + + void setBlock(bool block); + bool isReadable(); + bool isWritable(bool waitOnDelayedResponse); + bool isConnected(); + + string getHostName() const; + string getIp() const; + +protected: + static void throwException(const string &str); +}; + +// ===================================================== +// class ClientSocket +// ===================================================== + +class ClientSocket: public Socket{ +public: + void connect(const Ip &ip, int port); +}; + +// ===================================================== +// class ServerSocket +// ===================================================== + +class ServerSocket: public Socket{ +public: + void bind(int port); + void listen(int connectionQueueSize= SOMAXCONN); + Socket *accept(); +}; + +}}//end namespace + +#endif diff --git a/source/shared_lib/include/platform/win32/socket.h b/source/shared_lib/include/platform/win32/socket.h new file mode 100644 index 000000000..0743cfb24 --- /dev/null +++ b/source/shared_lib/include/platform/win32/socket.h @@ -0,0 +1,114 @@ +// ============================================================== +// This file is part of Glest Shared Library (www.glest.org) +// +// Copyright (C) 2001-2008 Marti�o Figueroa +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#ifndef _SHARED_PLATFORM_SOCKET_H_ +#define _SHARED_PLATFORM_SOCKET_H_ + +#include +#include +#include + +using std::string; + +const char* WSAGetLastErrorMessage(const char* pcMessagePrefix,int nErrorID = 0); + +namespace Shared{ namespace Platform{ + +// ===================================================== +// class IP +// ===================================================== + +class Ip{ +private: + unsigned char bytes[4]; + +public: + Ip(); + Ip(unsigned char byte0, unsigned char byte1, unsigned char byte2, unsigned char byte3); + Ip(const string& ipString); + + unsigned char getByte(int byteIndex) {return bytes[byteIndex];} + string getString() const; +}; + +// ===================================================== +// class Socket +// ===================================================== + +class Socket{ +private: + class SocketManager{ + public: + SocketManager(); + ~SocketManager(); + }; + +protected: + static SocketManager socketManager; + SOCKET sock; + +public: + Socket(SOCKET sock); + Socket(); + ~Socket(); + + static bool enableDebugText; + static bool enableNetworkDebugInfo; + + // Int lookup is socket fd while bool result is whether or not that socket was signalled for reading + static bool hasDataToRead(std::map &socketTriggeredList); + static bool hasDataToRead(int socket); + bool hasDataToRead(); + void disconnectSocket(); + + int getSocketId() const { return sock; } + + int getDataToRead(); + int send(const void *data, int dataSize); + int receive(void *data, int dataSize); + int peek(void *data, int dataSize); + + void setBlock(bool block); + bool isReadable(); + bool isWritable(bool waitOnDelayedResponse); + bool isConnected(); + + string getHostName() const; + string getIp() const; + +protected: + static void throwException(const string &str); + +}; + +// ===================================================== +// class ClientSocket +// ===================================================== + +class ClientSocket: public Socket{ +public: + void connect(const Ip &ip, int port); +}; + +// ===================================================== +// class ServerSocket +// ===================================================== + +class ServerSocket: public Socket{ +public: + void bind(int port); + void listen(int connectionQueueSize= SOMAXCONN); + Socket *accept(); +}; + +}}//end namespace + +#endif diff --git a/source/shared_lib/sources/platform/posix/socket.cpp b/source/shared_lib/sources/platform/posix/socket.cpp new file mode 100644 index 000000000..1055c6cc8 --- /dev/null +++ b/source/shared_lib/sources/platform/posix/socket.cpp @@ -0,0 +1,704 @@ +//This file is part of Glest Shared Library (www.glest.org) +//Copyright (C) 2005 Matthias Braun + +//You can redistribute this code and/or modify it under +//the terms of the GNU General Public License as published by the Free Software +//Foundation; either version 2 of the License, or (at your option) any later +//version. + +#include "socket.h" + +#include +#include +#include +#include +#include +#if defined(HAVE_SYS_IOCTL_H) +#define BSD_COMP /* needed for FIONREAD on Solaris2 */ +#include +#endif +#if defined(HAVE_SYS_FILIO_H) /* needed for FIONREAD on Solaris 2.5 */ +#include +#endif + +#include "conversion.h" + +using namespace std; +using namespace Shared::Util; + +namespace Shared{ namespace Platform{ + +bool Socket::enableDebugText = true; +bool Socket::enableNetworkDebugInfo = true; + +// ===================================================== +// class Ip +// ===================================================== + +Ip::Ip(){ + bytes[0]= 0; + bytes[1]= 0; + bytes[2]= 0; + bytes[3]= 0; +} + +Ip::Ip(unsigned char byte0, unsigned char byte1, unsigned char byte2, unsigned char byte3){ + bytes[0]= byte0; + bytes[1]= byte1; + bytes[2]= byte2; + bytes[3]= byte3; +} + + +Ip::Ip(const string& ipString){ + int offset= 0; + int byteIndex= 0; + + for(byteIndex= 0; byteIndex<4; ++byteIndex){ + int dotPos= ipString.find_first_of('.', offset); + + bytes[byteIndex]= atoi(ipString.substr(offset, dotPos-offset).c_str()); + offset= dotPos+1; + } +} + +string Ip::getString() const{ + return intToStr(bytes[0]) + "." + intToStr(bytes[1]) + "." + intToStr(bytes[2]) + "." + intToStr(bytes[3]); +} + +// =============================================== +// class Socket +// =============================================== + +Socket::Socket(int sock){ + this->sock= sock; +} + +Socket::Socket() +{ + sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if(sock < 0) + { + throwException("Error creating socket"); + } +} + +Socket::~Socket() +{ + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] START closing socket = %d...\n",__FILE__,__FUNCTION__,sock); + + disconnectSocket(); + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] END closing socket = %d...\n",__FILE__,__FUNCTION__,sock); +} + +void Socket::disconnectSocket() +{ + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] START closing socket = %d...\n",__FILE__,__FUNCTION__,sock); + + if(sock > 0) + { + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] calling shutdown and close for socket = %d...\n",__FILE__,__FUNCTION__,sock); + ::shutdown(sock,2); + ::close(sock); + sock = -1; + } + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] END closing socket = %d...\n",__FILE__,__FUNCTION__,sock); +} + +// Int lookup is socket fd while bool result is whether or not that socket was signalled for reading +bool Socket::hasDataToRead(std::map &socketTriggeredList) +{ + bool bResult = false; + + if(socketTriggeredList.size() > 0) + { + /* Watch stdin (fd 0) to see when it has input. */ + fd_set rfds; + FD_ZERO(&rfds); + + int imaxsocket = 0; + for(std::map::iterator itermap = socketTriggeredList.begin(); + itermap != socketTriggeredList.end(); itermap++) + { + int socket = itermap->first; + if(socket > 0) + { + FD_SET(socket, &rfds); + imaxsocket = max(socket,imaxsocket); + } + } + + if(imaxsocket > 0) + { + /* Wait up to 0 seconds. */ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + int retval = select(imaxsocket + 1, &rfds, NULL, NULL, &tv); + if(retval < 0) + { + char szBuf[1024]=""; + sprintf(szBuf,"In [%s::%s] ERROR SELECTING SOCKET DATA retval = %d errno = %d [%s]",__FILE__,__FUNCTION__,retval,errno,strerror(errno)); + fprintf(stderr, "%s", szBuf); + + } + else if(retval) + { + bResult = true; + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] select detected data imaxsocket = %d...\n",__FILE__,__FUNCTION__,imaxsocket); + + for(std::map::iterator itermap = socketTriggeredList.begin(); + itermap != socketTriggeredList.end(); itermap++) + { + int socket = itermap->first; + if (FD_ISSET(socket, &rfds)) + { + if(Socket::enableNetworkDebugInfo) printf("In [%s] FD_ISSET true for socket %d...\n",__FUNCTION__,socket); + + itermap->second = true; + } + else + { + itermap->second = false; + } + } + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] socketTriggeredList->size() = %d\n",__FILE__,__FUNCTION__,socketTriggeredList.size()); + } + } + } + + return bResult; +} + +bool Socket::hasDataToRead() +{ + return Socket::hasDataToRead(sock) ; +} + +bool Socket::hasDataToRead(int socket) +{ + bool bResult = false; + + if(socket > 0) + { + fd_set rfds; + struct timeval tv; + + /* Watch stdin (fd 0) to see when it has input. */ + FD_ZERO(&rfds); + FD_SET(socket, &rfds); + + /* Wait up to 0 seconds. */ + tv.tv_sec = 0; + tv.tv_usec = 0; + + int retval = select(socket + 1, &rfds, NULL, NULL, &tv); + if(retval) + { + if (FD_ISSET(socket, &rfds)) + { + bResult = true; + } + } + } + + return bResult; +} + +int Socket::getDataToRead(){ + unsigned long size = 0; + + //fd_set rfds; + //struct timeval tv; + //int retval; + + /* Watch stdin (fd 0) to see when it has input. */ + //FD_ZERO(&rfds); + //FD_SET(sock, &rfds); + + /* Wait up to 0 seconds. */ + //tv.tv_sec = 0; + //tv.tv_usec = 0; + + //retval = select(sock + 1, &rfds, NULL, NULL, &tv); + //if(retval) + if(sock > 0) + { + /* ioctl isn't posix, but the following seems to work on all modern + * unixes */ + int err= ioctl(sock, FIONREAD, &size); + + if(err < 0 && errno != EAGAIN) + { + char szBuf[1024]=""; + sprintf(szBuf,"In [%s::%s] ERROR PEEKING SOCKET DATA, err = %d errno = %d [%s]\n",__FILE__,__FUNCTION__,err,errno,strerror(errno)); + //throwException(szBuf); + printf("%s",szBuf); + } + else if(err == 0) + { + //if(Socket::enableNetworkDebugInfo) printf("In [%s] ioctl returned = %d, size = %ld\n",__FUNCTION__,err,size); + } + } + + return static_cast(size); +} + +int Socket::send(const void *data, int dataSize) { + ssize_t bytesSent= 0; + if(sock > 0) + { + bytesSent = ::send(sock, reinterpret_cast(data), dataSize, 0); + } + if(bytesSent < 0 && errno != EAGAIN) + { + char szBuf[1024]=""; + sprintf(szBuf,"In [%s::%s] ERROR WRITING SOCKET DATA, err = %d errno = %d [%s]\n",__FILE__,__FUNCTION__,bytesSent,errno,strerror(errno)); + //throwException(szBuf); + printf("%s",szBuf); + } + else if(bytesSent < 0 && errno == EAGAIN) + { + printf("In [%s::%s] #1 EAGAIN during send, trying again...\n",__FILE__,__FUNCTION__); + + time_t tStartTimer = time(NULL); + while((bytesSent < 0 && errno == EAGAIN) && (difftime(time(NULL),tStartTimer) <= 5)) + { + if(Socket::isWritable(true) == true) + { + bytesSent = ::send(sock, reinterpret_cast(data), dataSize, 0); + + printf("In [%s::%s] #2 EAGAIN during send, trying again returned: %d\n",__FILE__,__FUNCTION__,bytesSent); + } + } + } + if(bytesSent <= 0) + { + int iErr = errno; + disconnectSocket(); + + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] DISCONNECTED SOCKET error while sending socket data, bytesSent = %d, errno = %d [%s]\n",__FILE__,__FUNCTION__,bytesSent,iErr,strerror(iErr)); + printf("%s",szBuf); + //throwException(szBuf); + } + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] sock = %d, bytesSent = %d\n",__FILE__,__FUNCTION__,sock,bytesSent); + + return static_cast(bytesSent); +} + +int Socket::receive(void *data, int dataSize) +{ + ssize_t bytesReceived = 0; + + if(sock > 0) + { + bytesReceived = recv(sock, reinterpret_cast(data), dataSize, 0); + } + if(bytesReceived < 0 && errno != EAGAIN) + { + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] ERROR READING SOCKET DATA error while sending socket data, bytesSent = %d, errno = %d [%s]\n",__FILE__,__FUNCTION__,bytesReceived,errno,strerror(errno)); + //throwException(szBuf); + printf("%s",szBuf); + } + else if(bytesReceived < 0 && errno == EAGAIN) + { + printf("In [%s::%s] #1 EAGAIN during receive, trying again...\n",__FILE__,__FUNCTION__); + + time_t tStartTimer = time(NULL); + while((bytesReceived < 0 && errno == EAGAIN) && (difftime(time(NULL),tStartTimer) <= 5)) + { + if(Socket::isReadable() == true) + { + bytesReceived = recv(sock, reinterpret_cast(data), dataSize, 0); + + printf("In [%s::%s] #2 EAGAIN during receive, trying again returned: %d\n",__FILE__,__FUNCTION__,bytesReceived); + } + } + } + + if(bytesReceived <= 0) + { + int iErr = errno; + disconnectSocket(); + + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] DISCONNECTED SOCKET error while receiving socket data, bytesReceived = %d, errno = %d [%s]\n",__FILE__,__FUNCTION__,bytesReceived,iErr,strerror(iErr)); + printf("%s",szBuf); + //throwException(szBuf); + } + return static_cast(bytesReceived); +} + +int Socket::peek(void *data, int dataSize){ + ssize_t err = 0; + if(sock > 0) + { + err = recv(sock, reinterpret_cast(data), dataSize, MSG_PEEK); + } + if(err < 0 && errno != EAGAIN) + { + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] ERROR PEEKING SOCKET DATA error while sending socket data, bytesSent = %d, errno = %d [%s]\n",__FILE__,__FUNCTION__,err,errno,strerror(errno)); + //throwException(szBuf); + + disconnectSocket(); + } + else if(err < 0 && errno == EAGAIN) + { + printf("In [%s::%s] #1 EAGAIN during peek, trying again...\n",__FILE__,__FUNCTION__); + + time_t tStartTimer = time(NULL); + while((err < 0 && errno == EAGAIN) && (difftime(time(NULL),tStartTimer) <= 5)) + { + if(Socket::isReadable() == true) + { + err = recv(sock, reinterpret_cast(data), dataSize, MSG_PEEK); + + printf("In [%s::%s] #2 EAGAIN during peek, trying again returned: %d\n",__FILE__,__FUNCTION__,err); + } + } + } + + if(err <= 0) + { + int iErr = errno; + disconnectSocket(); + + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] DISCONNECTED SOCKET error while peeking socket data, err = %d, errno = %d [%s]\n",__FILE__,__FUNCTION__,err,iErr,strerror(iErr)); + printf("%s",szBuf); + //throwException(szBuf); + } + + return static_cast(err); +} + +void Socket::setBlock(bool block){ + int err= fcntl(sock, F_SETFL, block ? 0 : O_NONBLOCK); + if(err<0){ + throwException("Error setting I/O mode for socket"); + } +} + +bool Socket::isReadable() +{ + if(sock <= 0) return false; + + struct timeval tv; + tv.tv_sec= 0; + tv.tv_usec= 1; + + fd_set set; + FD_ZERO(&set); + FD_SET(sock, &set); + + int i= select(sock+1, &set, NULL, NULL, &tv); + if(i < 0) + { + if(difftime(time(NULL),lastDebugEvent) >= 1) + { + lastDebugEvent = time(NULL); + + //throwException("Error selecting socket"); + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] error while selecting socket data, err = %d, errno = %d [%s]\n",__FILE__,__FUNCTION__,i,errno,strerror(errno)); + printf("%s",szBuf); + } + } + //return (i == 1 && FD_ISSET(sock, &set)); + return (i == 1); +} + +bool Socket::isWritable(bool waitOnDelayedResponse) +{ + if(sock <= 0) return false; + + struct timeval tv; + tv.tv_sec= 0; + tv.tv_usec= 1; + + fd_set set; + FD_ZERO(&set); + FD_SET(sock, &set); + + bool result = false; + do + { + int i = select(sock+1, NULL, &set, NULL, &tv); + if(i < 0 ) + { + if(difftime(time(NULL),lastDebugEvent) >= 1) + { + lastDebugEvent = time(NULL); + + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] error while selecting socket data, err = %d, errno = %d [%s]\n",__FILE__,__FUNCTION__,i,errno,strerror(errno)); + printf("%s",szBuf); + } + waitOnDelayedResponse = false; + + //throwException("Error selecting socket"); + } + else if(i == 0) + { + if(difftime(time(NULL),lastDebugEvent) >= 1) + { + lastDebugEvent = time(NULL); + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] TIMEOUT while selecting socket data, err = %d, errno = %d [%s]\n",__FILE__,__FUNCTION__,i,errno,strerror(errno)); + printf("%s",szBuf); + } + + if(waitOnDelayedResponse == false) + { + result = true; + } + } + else + { + result = true; + } + } while(waitOnDelayedResponse == true && result == false); + + //return (i == 1 && FD_ISSET(sock, &set)); + return result; +} + +bool Socket::isConnected() +{ + //if the socket is not writable then it is not conencted + if(isWritable(false) == false) + { + return false; + } + + //if the socket is readable it is connected if we can read a byte from it + if(isReadable()) + { + char tmp; + int err = peek(&tmp, sizeof(tmp)); + return (err > 0); + /* + int err = recv(sock, &tmp, sizeof(tmp), MSG_PEEK); + + if(err <= 0 && errno != EAGAIN) + { + int iErr = errno; + disconnectSocket(); + + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] DISCONNECTED SOCKET error while peeking isconnected socket data, err = %d, errno = %d [%s]\n",__FILE__,__FUNCTION__,err,iErr,strerror(iErr)); + printf("%s",szBuf); + + return false; + } + else if(err <= 0) + { + int iErr = errno; + //disconnectSocket(); + + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] #2 DISCONNECTED SOCKET error while peeking isconnected socket data, err = %d, errno = %d [%s]\n",__FILE__,__FUNCTION__,err,iErr,strerror(iErr)); + printf("%s",szBuf); + } + */ + } + + //otherwise the socket is connected + return true; +} + +string Socket::getHostName() const { + const int strSize= 256; + char hostname[strSize]; + gethostname(hostname, strSize); + return hostname; +} + +string Socket::getIp() const{ + hostent* info= gethostbyname(getHostName().c_str()); + unsigned char* address; + + if(info==NULL){ + throw runtime_error("Error getting host by name"); + } + + address= reinterpret_cast(info->h_addr_list[0]); + + if(address==NULL){ + throw runtime_error("Error getting host ip"); + } + + return + intToStr(address[0]) + "." + + intToStr(address[1]) + "." + + intToStr(address[2]) + "." + + intToStr(address[3]); +} + +void Socket::throwException(const string &str){ + std::stringstream msg; + msg << str << " (Error: " << strerror(errno) << ")"; + throw runtime_error(msg.str()); +} + +// =============================================== +// class ClientSocket +// =============================================== + +void ClientSocket::connect(const Ip &ip, int port) +{ + sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + + addr.sin_family= AF_INET; + addr.sin_addr.s_addr= inet_addr(ip.getString().c_str()); + addr.sin_port= htons(port); + + int err= ::connect(sock, reinterpret_cast(&addr), sizeof(addr)); + if(err < 0) + { + char szBuf[1024]=""; + sprintf(szBuf,"In [%s::%s] #2 Error connecting socket for IP: %s for Port: %d err = %d errno = %d [%s]\n",__FILE__,__FUNCTION__,ip.getString().c_str(),port,err,errno,strerror(errno)); + fprintf(stderr, "%s", szBuf); + + if (errno == EINPROGRESS) + { + fd_set myset; + struct timeval tv; + int valopt; + socklen_t lon; + + fprintf(stderr, "In [%s::%s] EINPROGRESS in connect() - selecting\n",__FILE__,__FUNCTION__); + + do + { + tv.tv_sec = 10; + tv.tv_usec = 0; + + FD_ZERO(&myset); + FD_SET(sock, &myset); + + err = select(sock+1, NULL, &myset, NULL, &tv); + + if (err < 0 && errno != EINTR) + { + sprintf(szBuf, "In [%s::%s] Error connecting %d - [%s]\n",__FILE__,__FUNCTION__,errno, strerror(errno)); + //throwException(szBuf); + fprintf(stderr, "%s", szBuf); + break; + } + else if (err > 0) + { + // Socket selected for write + lon = sizeof(int); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &lon) < 0) + { + sprintf(szBuf, "In [%s::%s] Error in getsockopt() %d - [%s]\n",__FILE__,__FUNCTION__,errno, strerror(errno)); + //throwException(szBuf); + fprintf(stderr, "%s", szBuf); + break; + } + // Check the value returned... + if (valopt) + { + sprintf(szBuf, "In [%s::%s] Error in delayed connection() %d - [%s]\n",__FILE__,__FUNCTION__,valopt, strerror(valopt)); + //throwException(szBuf); + fprintf(stderr, "%s", szBuf); + break; + } + + errno = 0; + fprintf(stderr, "In [%s::%s] Apparent recovery for connection sock = %d, err = %d, errno = %d\n",__FILE__,__FUNCTION__,sock,err,errno); + + break; + } + else + { + sprintf(szBuf, "In [%s::%s] Timeout in select() - Cancelling!\n",__FILE__,__FUNCTION__); + //throwException(szBuf); + fprintf(stderr, "%s", szBuf); + + disconnectSocket(); + break; + } + } while (1); + } + + if(err < 0) + { + fprintf(stderr, "In [%s::%s] Before END sock = %d, err = %d, errno = %d [%s]\n",__FILE__,__FUNCTION__,sock,err,errno,strerror(errno)); + //throwException(szBuf); + disconnectSocket(); + } + else + { + fprintf(stderr, "In [%s::%s] Valid recovery for connection sock = %d, err = %d, errno = %d\n",__FILE__,__FUNCTION__,sock,err,errno); + } + } +} + +// =============================================== +// class ServerSocket +// =============================================== + +void ServerSocket::bind(int port) +{ + //sockaddr structure + sockaddr_in addr; + addr.sin_family= AF_INET; + addr.sin_addr.s_addr= INADDR_ANY; + addr.sin_port= htons(port); + + int val = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + + int err= ::bind(sock, reinterpret_cast(&addr), sizeof(addr)); + if(err < 0) + { + char szBuf[1024]=""; + sprintf(szBuf, "In [%s::%s] Error binding socket sock = %d, err = %d, errno = %d\n",__FILE__,__FUNCTION__,sock,err,errno); + throwException(szBuf); + } +} + +void ServerSocket::listen(int connectionQueueSize) +{ + int err= ::listen(sock, connectionQueueSize); + if(err < 0) + { + char szBuf[1024]=""; + sprintf(szBuf, "In [%s::%s] Error listening socket sock = %d, err = %d, errno = %d\n",__FILE__,__FUNCTION__,sock,err,errno); + throwException(szBuf); + } +} + +Socket *ServerSocket::accept() +{ + int newSock= ::accept(sock, NULL, NULL); + if(newSock < 0) + { + char szBuf[1024]=""; + if(Socket::enableNetworkDebugInfo) printf(szBuf, "In [%s::%s] Error accepting socket connection sock = %d, err = %d, errno = %d\n",__FILE__,__FUNCTION__,sock,newSock,errno); + + if(errno == EAGAIN) + { + return NULL; + } + throwException(szBuf); + + } + return new Socket(newSock); +} + + +}}//end namespace + diff --git a/source/shared_lib/sources/platform/win32/socket.cpp b/source/shared_lib/sources/platform/win32/socket.cpp new file mode 100644 index 000000000..9911f2cde --- /dev/null +++ b/source/shared_lib/sources/platform/win32/socket.cpp @@ -0,0 +1,797 @@ +// ============================================================== +// This file is part of Glest Shared Library (www.glest.org) +// +// Copyright (C) 2001-2007 Marti�o Figueroa +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#include "socket.h" + +#include + +#include "conversion.h" + +#include "leak_dumper.h" +#include +#include +#include + +#define socklen_t int + +using namespace std; +using namespace Shared::Util; + +#define MAXHOSTNAME 254 +//// Constants ///////////////////////////////////////////////////////// +const int kBufferSize = 1024; + +//// Statics /////////////////////////////////////////////////////////// +// List of Winsock error constants mapped to an interpretation string. +// Note that this list must remain sorted by the error constants' +// values, because we do a binary search on the list when looking up +// items. + +static class ErrorEntry +{ +public: + int nID; + const char* pcMessage; + + ErrorEntry(int id, const char* pc = 0) : nID(id), pcMessage(pc) + { + } + + bool operator<(const ErrorEntry& rhs) + { + return nID < rhs.nID; + } + +} gaErrorList[] = + { + ErrorEntry(0, "No error"), + ErrorEntry(WSAEINTR, "Interrupted system call"), + ErrorEntry(WSAEBADF, "Bad file number"), + ErrorEntry(WSAEACCES, "Permission denied"), + ErrorEntry(WSAEFAULT, "Bad address"), + ErrorEntry(WSAEINVAL, "Invalid argument"), + ErrorEntry(WSAEMFILE, "Too many open sockets"), + ErrorEntry(WSAEWOULDBLOCK, "Operation would block"), + ErrorEntry(WSAEINPROGRESS, "Operation now in progress"), + ErrorEntry(WSAEALREADY, "Operation already in progress"), + ErrorEntry(WSAENOTSOCK, "Socket operation on non-socket"), + ErrorEntry(WSAEDESTADDRREQ, "Destination address required"), + ErrorEntry(WSAEMSGSIZE, "Message too long"), + ErrorEntry(WSAEPROTOTYPE, "Protocol wrong type for socket"), + ErrorEntry(WSAENOPROTOOPT, "Bad protocol option"), + ErrorEntry(WSAEPROTONOSUPPORT, "Protocol not supported"), + ErrorEntry(WSAESOCKTNOSUPPORT, "Socket type not supported"), + ErrorEntry(WSAEOPNOTSUPP, "Operation not supported on socket"), + ErrorEntry(WSAEPFNOSUPPORT, "Protocol family not supported"), + ErrorEntry(WSAEAFNOSUPPORT, "Address family not supported"), + ErrorEntry(WSAEADDRINUSE, "Address already in use"), + ErrorEntry(WSAEADDRNOTAVAIL, "Can't assign requested address"), + ErrorEntry(WSAENETDOWN, "Network is down"), + ErrorEntry(WSAENETUNREACH, "Network is unreachable"), + ErrorEntry(WSAENETRESET, "Net connection reset"), + ErrorEntry(WSAECONNABORTED, "Software caused connection abort"), + ErrorEntry(WSAECONNRESET, "Connection reset by peer"), + ErrorEntry(WSAENOBUFS, "No buffer space available"), + ErrorEntry(WSAEISCONN, "Socket is already connected"), + ErrorEntry(WSAENOTCONN, "Socket is not connected"), + ErrorEntry(WSAESHUTDOWN, "Can't send after socket shutdown"), + ErrorEntry(WSAETOOMANYREFS, "Too many references, can't splice"), + ErrorEntry(WSAETIMEDOUT, "Connection timed out"), + ErrorEntry(WSAECONNREFUSED, "Connection refused"), + ErrorEntry(WSAELOOP, "Too many levels of symbolic links"), + ErrorEntry(WSAENAMETOOLONG, "File name too long"), + ErrorEntry(WSAEHOSTDOWN, "Host is down"), + ErrorEntry(WSAEHOSTUNREACH, "No route to host"), + ErrorEntry(WSAENOTEMPTY, "Directory not empty"), + ErrorEntry(WSAEPROCLIM, "Too many processes"), + ErrorEntry(WSAEUSERS, "Too many users"), + ErrorEntry(WSAEDQUOT, "Disc quota exceeded"), + ErrorEntry(WSAESTALE, "Stale NFS file handle"), + ErrorEntry(WSAEREMOTE, "Too many levels of remote in path"), + ErrorEntry(WSASYSNOTREADY, "Network system is unavailable"), + ErrorEntry(WSAVERNOTSUPPORTED, "Winsock version out of range"), + ErrorEntry(WSANOTINITIALISED, "WSAStartup not yet called"), + ErrorEntry(WSAEDISCON, "Graceful shutdown in progress"), + ErrorEntry(WSAHOST_NOT_FOUND, "Host not found"), + ErrorEntry(WSANO_DATA, "No host data of that type was found") +}; + +bool operator<(const ErrorEntry& rhs1,const ErrorEntry& rhs2) +{ + return rhs1.nID < rhs2.nID; +} + +const int kNumMessages = sizeof(gaErrorList) / sizeof(ErrorEntry); + +//// WSAGetLastErrorMessage //////////////////////////////////////////// +// A function similar in spirit to Unix's perror() that tacks a canned +// interpretation of the value of WSAGetLastError() onto the end of a +// passed string, separated by a ": ". Generally, you should implement +// smarter error handling than this, but for default cases and simple +// programs, this function is sufficient. +// +// This function returns a pointer to an internal static buffer, so you +// must copy the data from this function before you call it again. It +// follows that this function is also not thread-safe. +const char* WSAGetLastErrorMessage(const char* pcMessagePrefix, + int nErrorID /* = 0 */) +{ + // Build basic error string + static char acErrorBuffer[256]; + std::ostrstream outs(acErrorBuffer, sizeof(acErrorBuffer)); + outs << pcMessagePrefix << ": "; + + // Tack appropriate canned message onto end of supplied message + // prefix. Note that we do a binary search here: gaErrorList must be + // sorted by the error constant's value. + ErrorEntry* pEnd = gaErrorList + kNumMessages; + ErrorEntry Target(nErrorID ? nErrorID : WSAGetLastError()); + ErrorEntry* it = std::lower_bound(gaErrorList, pEnd, Target); + if ((it != pEnd) && (it->nID == Target.nID)) + { + outs << it->pcMessage; + } + else + { + // Didn't find error in list, so make up a generic one + outs << "unknown error"; + } + outs << " (" << Target.nID << ")"; + + // Finish error message off and return it. + outs << std::ends; + acErrorBuffer[sizeof(acErrorBuffer) - 1] = '\0'; + return acErrorBuffer; +} + +namespace Shared{ namespace Platform{ + +bool Socket::enableDebugText = true; +bool Socket::enableNetworkDebugInfo = true; + +// ===================================================== +// class Ip +// ===================================================== + +Ip::Ip(){ + bytes[0]= 0; + bytes[1]= 0; + bytes[2]= 0; + bytes[3]= 0; +} + +Ip::Ip(unsigned char byte0, unsigned char byte1, unsigned char byte2, unsigned char byte3){ + bytes[0]= byte0; + bytes[1]= byte1; + bytes[2]= byte2; + bytes[3]= byte3; +} + +Ip::Ip(const string& ipString){ + int offset= 0; + int byteIndex= 0; + + for(byteIndex= 0; byteIndex<4; ++byteIndex){ + int dotPos= ipString.find_first_of('.', offset); + + bytes[byteIndex]= atoi(ipString.substr(offset, dotPos-offset).c_str()); + offset= dotPos+1; + } +} + +string Ip::getString() const{ + return intToStr(bytes[0]) + "." + intToStr(bytes[1]) + "." + intToStr(bytes[2]) + "." + intToStr(bytes[3]); +} + +// ===================================================== +// class Socket +// ===================================================== + +Socket::SocketManager Socket::socketManager; + +Socket::SocketManager::SocketManager(){ + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(2, 0); + WSAStartup(wVersionRequested, &wsaData); + //dont throw exceptions here, this is a static initializacion + printf("Winsock initialized.\n"); +} + +Socket::SocketManager::~SocketManager(){ + WSACleanup(); + printf("Winsock cleanup complete.\n"); +} + +Socket::Socket(SOCKET sock){ + this->sock= sock; +} + +Socket::Socket(){ + sock= socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if(sock==INVALID_SOCKET){ + throwException("Error creating socket"); + } +} + +Socket::~Socket() +{ + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] START closing socket = %d...\n",__FILE__,__FUNCTION__,sock); + + disconnectSocket(); + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] END closing socket = %d...\n",__FILE__,__FUNCTION__,sock); +} + +void Socket::disconnectSocket() +{ + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] START closing socket = %d...\n",__FILE__,__FUNCTION__,sock); + + if(sock > 0) + { + ::shutdown(sock,2); + ::closesocket(sock); + sock = -1; + } + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] END closing socket = %d...\n",__FILE__,__FUNCTION__,sock); +} + +// Int lookup is socket fd while bool result is whether or not that socket was signalled for reading +bool Socket::hasDataToRead(std::map &socketTriggeredList) +{ + bool bResult = false; + + if(socketTriggeredList.size() > 0) + { + /* Watch stdin (fd 0) to see when it has input. */ + fd_set rfds; + FD_ZERO(&rfds); + + int imaxsocket = 0; + for(std::map::iterator itermap = socketTriggeredList.begin(); + itermap != socketTriggeredList.end(); itermap++) + { + int socket = itermap->first; + if(socket > 0) + { + FD_SET(socket, &rfds); + imaxsocket = max(socket,imaxsocket); + } + } + + if(imaxsocket > 0) + { + /* Wait up to 0 seconds. */ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + int retval = select(imaxsocket + 1, &rfds, NULL, NULL, &tv); + if(retval < 0) + { + char szBuf[1024]=""; + sprintf(szBuf,"In [%s::%s] ERROR SELECTING SOCKET DATA retval = %d WSAGetLastError() = %d",__FILE__,__FUNCTION__,retval,WSAGetLastError()); + fprintf(stderr, "%s", szBuf); + } + else if(retval) + { + bResult = true; + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] select detected data imaxsocket = %d...\n",__FILE__,__FUNCTION__,imaxsocket); + + for(std::map::iterator itermap = socketTriggeredList.begin(); + itermap != socketTriggeredList.end(); itermap++) + { + int socket = itermap->first; + if (FD_ISSET(socket, &rfds)) + { + if(Socket::enableNetworkDebugInfo) printf("In [%s] FD_ISSET true for socket %d...\n",__FUNCTION__,socket); + + itermap->second = true; + } + else + { + itermap->second = false; + } + } + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] socketTriggeredList->size() = %d\n",__FILE__,__FUNCTION__,socketTriggeredList.size()); + } + } + } + + return bResult; +} + +bool Socket::hasDataToRead() +{ + return Socket::hasDataToRead(sock) ; +} + +bool Socket::hasDataToRead(int socket) +{ + bool bResult = false; + + if(socket > 0) + { + fd_set rfds; + struct timeval tv; + + /* Watch stdin (fd 0) to see when it has input. */ + FD_ZERO(&rfds); + FD_SET(socket, &rfds); + + /* Wait up to 0 seconds. */ + tv.tv_sec = 0; + tv.tv_usec = 0; + + int retval = select(socket + 1, &rfds, NULL, NULL, &tv); + if(retval) + { + if (FD_ISSET(socket, &rfds)) + { + bResult = true; + } + } + } + + return bResult; +} + +int Socket::getDataToRead(){ + unsigned long size = 0; + + //fd_set rfds; + //struct timeval tv; + //int retval; + + /* Watch stdin (fd 0) to see when it has input. */ + //FD_ZERO(&rfds); + //FD_SET(sock, &rfds); + + /* Wait up to 0 seconds. */ + //tv.tv_sec = 0; + //tv.tv_usec = 0; + + //retval = select(sock + 1, &rfds, NULL, NULL, &tv); + //if(retval) + if(sock > 0) + { + /* ioctl isn't posix, but the following seems to work on all modern + * unixes */ + int err= ioctlsocket(sock, FIONREAD, &size); + + if(err < 0 && WSAGetLastError() != WSAEWOULDBLOCK) + { + char szBuf[1024]=""; + sprintf(szBuf,"In [%s::%s] ERROR PEEKING SOCKET DATA, err = %d WSAGetLastError() = %d\n",__FILE__,__FUNCTION__,err,WSAGetLastError()); + printf("%s",szBuf); + //throwException(szBuf); + } + else if(err == 0) + { + //if(Socket::enableNetworkDebugInfo) printf("In [%s] ioctl returned = %d, size = %ld\n",__FUNCTION__,err,size); + } + } + + return static_cast(size); +} + +int Socket::send(const void *data, int dataSize) { + int bytesSent= 0; + if(sock > 0) + { + bytesSent = ::send(sock, reinterpret_cast(data), dataSize, 0); + } + if(bytesSent < 0 && WSAGetLastError() != WSAEWOULDBLOCK) + { + char szBuf[1024]=""; + sprintf(szBuf,"In [%s::%s] ERROR WRITING SOCKET DATA, err = %d WSAGetLastError() = %d\n",__FILE__,__FUNCTION__,bytesSent,WSAGetLastError()); + //throwException(szBuf); + printf("%s",szBuf); + } + else if(bytesSent < 0 && WSAGetLastError() == WSAEWOULDBLOCK) + { + printf("In [%s::%s] #1 WSAEWOULDBLOCK during send, trying again...\n",__FILE__,__FUNCTION__); + + time_t tStartTimer = time(NULL); + while((bytesSent < 0 && WSAGetLastError() == WSAEWOULDBLOCK) && (difftime(time(NULL),tStartTimer) <= 5)) + { + if(Socket::isWritable(true) == true) + { + bytesSent = ::send(sock, reinterpret_cast(data), dataSize, 0); + + printf("In [%s::%s] #2 WSAEWOULDBLOCK during send, trying again returned: %d\n",__FILE__,__FUNCTION__,bytesSent); + } + } + } + if(bytesSent <= 0) + { + int iErr = WSAGetLastError(); + disconnectSocket(); + + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] DISCONNECTED SOCKET error while sending socket data, bytesSent = %d, WSAGetLastError() = %d\n",__FILE__,__FUNCTION__,bytesSent,iErr); + printf("%s",szBuf); + //throwException(szBuf); + } + + if(Socket::enableNetworkDebugInfo) printf("In [%s::%s] sock = %d, bytesSent = %d\n",__FILE__,__FUNCTION__,sock,bytesSent); + + return static_cast(bytesSent); +} + +int Socket::receive(void *data, int dataSize) +{ + int bytesReceived = 0; + + if(sock > 0) + { + bytesReceived = recv(sock, reinterpret_cast(data), dataSize, 0); + } + if(bytesReceived < 0 && WSAGetLastError() != WSAEWOULDBLOCK) + { + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] ERROR READING SOCKET DATA error while sending socket data, bytesSent = %d, WSAGetLastError() = %d\n",__FILE__,__FUNCTION__,bytesReceived,WSAGetLastError()); + //throwException(szBuf); + printf("%s",szBuf); + } + else if(bytesReceived < 0 && WSAGetLastError() == WSAEWOULDBLOCK) + { + printf("In [%s::%s] #1 WSAEWOULDBLOCK during receive, trying again...\n",__FILE__,__FUNCTION__); + + time_t tStartTimer = time(NULL); + while((bytesReceived < 0 && WSAGetLastError() == WSAEWOULDBLOCK) && (difftime(time(NULL),tStartTimer) <= 5)) + { + if(Socket::isReadable() == true) + { + bytesReceived = recv(sock, reinterpret_cast(data), dataSize, 0); + + printf("In [%s::%s] #2 WSAEWOULDBLOCK during receive, trying again returned: %d\n",__FILE__,__FUNCTION__,bytesReceived); + } + } + } + + if(bytesReceived <= 0) + { + int iErr = WSAGetLastError(); + disconnectSocket(); + + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] DISCONNECTED SOCKET error while receiving socket data, bytesReceived = %d, WSAGetLastError() = %d\n",__FILE__,__FUNCTION__,bytesReceived,iErr); + printf("%s",szBuf); + //throwException(szBuf); + } + return static_cast(bytesReceived); +} + +int Socket::peek(void *data, int dataSize){ + int err = 0; + if(sock > 0) + { + err = recv(sock, reinterpret_cast(data), dataSize, MSG_PEEK); + } + if(err < 0 && WSAGetLastError() != WSAEWOULDBLOCK) + { + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] ERROR PEEKING SOCKET DATA error while sending socket data, bytesSent = %d, WSAGetLastError() = %d\n",__FILE__,__FUNCTION__,err,WSAGetLastError()); + //throwException(szBuf); + printf("%s",szBuf); + } + else if(err < 0 && WSAGetLastError() == WSAEWOULDBLOCK) + { + printf("In [%s::%s] #1 WSAEWOULDBLOCK during peek, trying again...\n",__FILE__,__FUNCTION__); + + time_t tStartTimer = time(NULL); + while((err < 0 && WSAGetLastError() == WSAEWOULDBLOCK) && (difftime(time(NULL),tStartTimer) <= 5)) + { + if(Socket::isReadable() == true) + { + err = recv(sock, reinterpret_cast(data), dataSize, MSG_PEEK); + + printf("In [%s::%s] #2 WSAEWOULDBLOCK during peek, trying again returned: %d\n",__FILE__,__FUNCTION__,err); + } + } + } + + if(err <= 0) + { + int iErr = WSAGetLastError(); + disconnectSocket(); + + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] DISCONNECTED SOCKET error while peeking socket data, err = %d, WSAGetLastError() = %d\n",__FILE__,__FUNCTION__,err,iErr); + printf("%s",szBuf); + //throwException(szBuf); + } + + return static_cast(err); +} + +void Socket::setBlock(bool block){ + u_long iMode= ! + block; + int err= ioctlsocket(sock, FIONBIO, &iMode); + if(err==SOCKET_ERROR) + { + throwException("Error setting I/O mode for socket"); + } +} + +bool Socket::isReadable() +{ + if(sock <= 0) return false; + + TIMEVAL tv; + tv.tv_sec= 0; + tv.tv_usec= 1; + + fd_set set; + FD_ZERO(&set); + FD_SET(sock, &set); + + int i= select(sock+1, &set, NULL, NULL, &tv); + if(i==SOCKET_ERROR) + { + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] error while selecting socket data, err = %d, errno = %d\n",__FILE__,__FUNCTION__,i,WSAGetLastError()); + printf("%s",szBuf); + } + //return (i == 1 && FD_ISSET(sock, &set)); + return (i == 1); +} + +bool Socket::isWritable(bool waitOnDelayedResponse) +{ + if(sock <= 0) return false; + + TIMEVAL tv; + tv.tv_sec= 0; + tv.tv_usec= 1; + + fd_set set; + FD_ZERO(&set); + FD_SET(sock, &set); + + bool result = false; + do + { + int i= select(sock+1, NULL, &set, NULL, &tv); + if(i==SOCKET_ERROR) + { + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] error while selecting socket data, err = %d, errno = %d\n",__FILE__,__FUNCTION__,i,WSAGetLastError()); + printf("%s",szBuf); + waitOnDelayedResponse = false; + } + else if(i == 0) + { + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] TIMEOUT while selecting socket data, err = %d, errno = %d\n",__FILE__,__FUNCTION__,i,WSAGetLastError()); + printf("%s",szBuf); + + if(waitOnDelayedResponse == false) + { + result = true; + } + } + else + { + result = true; + } + } while(waitOnDelayedResponse == true && result == false); + + return result; +} + +bool Socket::isConnected(){ + + //if the socket is not writable then it is not conencted + if(isWritable(false) == false) + { + return false; + } + + //if the socket is readable it is connected if we can read a byte from it + if(isReadable()) + { + char tmp; + int err = peek(&tmp, sizeof(tmp)); + return (err > 0); + /* + int err = recv(sock, &tmp, sizeof(tmp), MSG_PEEK); + + if(err <= 0 && WSAGetLastError() != WSAEWOULDBLOCK) + { + int iErr = WSAGetLastError(); + disconnectSocket(); + + char szBuf[1024]=""; + sprintf(szBuf,"[%s::%s] DISCONNECTED SOCKET error while peeking isconnected socket data, err = %d, WSAGetLastError() = %d\n",__FILE__,__FUNCTION__,err,iErr); + printf("%s",szBuf); + + return false; + } + */ + } + + //otherwise the socket is connected + return true; +} + +string Socket::getHostName() const{ + const int strSize= 256; + char hostname[strSize]; + gethostname(hostname, strSize); + return hostname; +} + +string Socket::getIp() const{ + hostent* info= gethostbyname(getHostName().c_str()); + unsigned char* address; + + if(info==NULL) + { + throwException("Error getting host by name"); + } + + address= reinterpret_cast(info->h_addr_list[0]); + + if(address==NULL) + { + throwException("Error getting host ip"); + } + + return + intToStr(address[0]) + "." + + intToStr(address[1]) + "." + + intToStr(address[2]) + "." + + intToStr(address[3]); +} + +void Socket::throwException(const string &str){ + throw runtime_error("Network error: " + str+" (Code: " + intToStr(WSAGetLastError())+")"); +} + +// ===================================================== +// class ClientSocket +// ===================================================== + +void ClientSocket::connect(const Ip &ip, int port) +{ + sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + + addr.sin_family= AF_INET; + addr.sin_addr.s_addr= inet_addr(ip.getString().c_str()); + addr.sin_port= htons(port); + + fprintf(stderr, "Connecting to host [%s] on port = %d\n", ip.getString().c_str(),port); + + int err= ::connect(sock, reinterpret_cast(&addr), sizeof(addr)); + if(err < 0) + { + char szBuf[1024]=""; + sprintf(szBuf,"#2 Error connecting socket for IP: %s for Port: %d err = %d WSAGetLastError() = %d",ip.getString().c_str(),port,err,WSAGetLastError()); + fprintf(stderr, "%s\n", WSAGetLastErrorMessage(szBuf)); + //fprintf(stderr, "%s", szBuf); + + if (WSAGetLastError() == WSAEINPROGRESS || WSAGetLastError() == WSAEWOULDBLOCK) + { + fd_set myset; + struct timeval tv; + int valopt; + socklen_t lon; + + fprintf(stderr, "WSAEINPROGRESS or WSAEWOULDBLOCK in connect() - selecting\n"); + do { + tv.tv_sec = 10; + tv.tv_usec = 0; + + FD_ZERO(&myset); + FD_SET(sock, &myset); + + err = select(0, NULL, &myset, NULL, &tv); + + if (err < 0 && WSAGetLastError() != WSAEWOULDBLOCK && WSAGetLastError() != WSAEWOULDBLOCK) + { + sprintf(szBuf, "Error connecting %d\n", WSAGetLastError()); + //throwException(szBuf); + fprintf(stderr, "%s", szBuf); + break; + } + else if (err > 0) { + // Socket selected for write + lon = sizeof(int); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)(&valopt), &lon) < 0) + { + sprintf(szBuf, "Error in getsockopt() %d\n", WSAGetLastError()); + //throwException(szBuf); + fprintf(stderr, "%s", szBuf); + break; + } + // Check the value returned... + if (valopt) + { + sprintf(szBuf, "Error in delayed connection() %d\n", valopt); + //throwException(szBuf); + fprintf(stderr, "%s", szBuf); + break; + } + + fprintf(stderr, "Apparent recovery for connection sock = %d, err = %d, WSAGetLastError() = %d\n",sock,err,WSAGetLastError()); + + break; + } + else + { + sprintf(szBuf, "Timeout in select() - Cancelling!\n"); + //throwException(szBuf); + fprintf(stderr, "%s", szBuf); + + disconnectSocket(); + + break; + } + } while (1); + } + + if(err < 0) + { + fprintf(stderr, "In [%s::%s] Before END sock = %d, err = %d, errno = %d\n",__FILE__,__FUNCTION__,sock,err,WSAGetLastError()); + //throwException(szBuf); + disconnectSocket(); + } + + fprintf(stderr, "Valid recovery for connection sock = %d, err = %d, WSAGetLastError() = %d\n",sock,err,WSAGetLastError()); + } + else + { + fprintf(stderr, "Connected to host [%s] on port = %d sock = %d err = %d", ip.getString().c_str(),port,err); + } +} + +// ===================================================== +// class ServerSocket +// ===================================================== + +void ServerSocket::bind(int port){ + //sockaddr structure + sockaddr_in addr; + addr.sin_family= AF_INET; + addr.sin_addr.s_addr= INADDR_ANY; + addr.sin_port= htons(port); + + int err= ::bind(sock, reinterpret_cast(&addr), sizeof(addr)); + if(err==SOCKET_ERROR){ + throwException("Error binding socket"); + } +} + +void ServerSocket::listen(int connectionQueueSize){ + int err= ::listen(sock, connectionQueueSize); + if(err==SOCKET_ERROR){ + throwException("Error listening socket"); + } +} + +Socket *ServerSocket::accept(){ + SOCKET newSock= ::accept(sock, NULL, NULL); + if(newSock==INVALID_SOCKET){ + if(WSAGetLastError()==WSAEWOULDBLOCK){ + return NULL; + } + throwException("Error accepting socket connection"); + } + return new Socket(newSock); +} + +}}//end namespace