Author | Message | Time |
---|---|---|
Mephisto | In my Battle.net bot I am attempting to use overlapped I/O (not I/O completion ports). However, I am running into a very annoying runtime error which I have never really been able to resolve, and AFAIK it occurs randomly, though more frequently under high stress conditions (such as several chat events and server traffic). The problem is that the buffer is overflowing causing potential errors, and often times leads to the bot disconnecting (not crashing). So in this post I've included the key parts to my networking/buffering code. If you need more information, I'd be happy to post it, just tell me what you need. This is the Connection class (Header/Source file inclusive): [code] #include "Includes.h" #include "BufferWriter.h" #ifndef CONNECTION_H #define CONNECTION_H const unsigned long BUFFER_SIZE = 1024; class Connection { public: explicit __stdcall Connection(void) : _port(0), _buflen(0), _bufstart(0), _socket((unsigned int)-1), _socketEvent(0), _bytes(0), _flags(0), _sentPackets(0), _recvPackets(0) { _server = new char[50]; ZeroMemory(_server, sizeof(_server)); memset(&_sendOv, 0, sizeof(_sendOv)); memset(&_recvOv, 0, sizeof(_recvOv)); memset(&_WSASendBuf, 0, sizeof(_WSASendBuf)); memset(&_WSARecvBuf, 0, sizeof(_WSARecvBuf)); } virtual __stdcall ~Connection(void) { delete [] _server; } // non-generic class function; may remove or use with PacketWriter.h and its derived classes void __stdcall SendPacket(BufferWriter* message); // pure virtual functions; override these functions in your derived connection classes virtual bool __stdcall DoEvents() = 0; // processes network events on the socket event virtual void __stdcall ProcessData(char* buffer) = 0; // processes the data into a message format then iterates to the next message if necessary virtual void __stdcall ProcessMessages(unsigned char packetID, char* data, unsigned short len) = 0; // receives the message and processes it accordingly bool __stdcall ConnectSocket(); // connects the socket to the server specified bool __stdcall DisconnectSocket(); // disconnects the socket from the server specified // overlapped receive completion routine __inline static void CALLBACK StaticRecvComplete(unsigned long dwError, unsigned long dwTransferred, LPWSAOVERLAPPED lpOverlapped, unsigned long flags) { reinterpret_cast<Connection *>(lpOverlapped->hEvent)->RecvComplete(dwError, dwTransferred, lpOverlapped, flags); } // overlapped send completion routine __inline static void CALLBACK StaticSendComplete(unsigned long dwError, unsigned long dwTransferred, LPWSAOVERLAPPED lpOverlapped, unsigned long flags) { reinterpret_cast<Connection *>(lpOverlapped->hEvent)->SendComplete(dwError, dwTransferred, lpOverlapped, flags); } void __stdcall RecvComplete(unsigned long dwError, unsigned long dwTransferred, LPWSAOVERLAPPED lpOverlapped, unsigned long flags); // handles the completion routine data; called by static receive completion routine void __stdcall SendComplete(unsigned long dwError, unsigned long dwTransferred, LPWSAOVERLAPPED lpOverlapped, unsigned long flags); // handles the completion routine data; called by static send completion routine void __stdcall SendMessage(char* message); // sends the buffered message to the server; you may want to make this pure virtual and implement your own method in the base class or create a new function with a new argument list for sending messages __inline char* GetServer(void) { return _server; } __inline unsigned short GetPort(void) { return _port; } __inline HANDLE GetSocketEvent(void) { return _socketEvent; } unsigned long _sentPackets; unsigned long _recvPackets; protected: char* _server; // pointer to the address of the server unsigned short _port; // the port the server listens on unsigned long _buflen; // the length of the buffer when receiving data unsigned long _bufstart; // the buffer start position to read and store data SOCKET _socket; // the socket that will be connected to the server HANDLE _socketEvent; // the socket event used in a wait function char _recvBuffer[BUFFER_SIZE]; // the buffer in which data is stored for receiving char _sendBuffer[BUFFER_SIZE]; // the buffer in which data is stored for sending OVERLAPPED _sendOv; // the overlapped struct used for sending routines OVERLAPPED _recvOv; // the overlapped struct used for receiving routines WSABUF _WSARecvBuf; // the standard WSA buffer used in WSARecv WSABUF _WSASendBuf; // the standard WSA buffer used in WSASend unsigned long _bytes; // argument passed to WSARecv & WSASend (not used) unsigned long _flags; // argument passed to WSARecv & WSASend (not used) }; #endif #include "Includes.h" #include "Connection.h" #include "BufferWriter.h" #include "GlobalData.h" #include "Functions.h" bool __stdcall Connection::ConnectSocket() { struct hostent *host_entry; struct sockaddr_in serverAttributes; if((_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == SOCKET_ERROR) { cout << Timestamp() << "Error: Unable to open socket: " << WSAGetLastError() << endl; return false; } if((host_entry = gethostbyname(_server)) == NULL) { cout << Timestamp() << "Error: Could not find host for server " << _server << ": " << WSAGetLastError() << endl; return false; } serverAttributes.sin_family = AF_INET; serverAttributes.sin_port = htons((u_short)_port); serverAttributes.sin_addr.s_addr = *(unsigned long*)host_entry->h_addr; if(connect(_socket, (sockaddr *)&serverAttributes, sizeof(serverAttributes)) == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) { cout << Timestamp() << "Error: could not connect to server " << _server << ": " << WSAGetLastError() << endl; return false; } if((_socketEvent = CreateEvent(0, 0, 0, 0)) == NULL) { cout << Timestamp() << "Error: could not create a valid socket event: " << GetLastError() << endl; return false; } if(WSAEventSelect(_socket, _socketEvent, FD_CONNECT|FD_CLOSE) == SOCKET_ERROR) { // set specified event flags to whatever... cout << Timestamp() << "Error: could not select events: " << WSAGetLastError() << endl; return false; } return true; // output connection success message in DoEvents under flag FD_CONNECT } bool __stdcall Connection::DisconnectSocket() { if(_socket == SOCKET_ERROR) { cout << Timestamp() << "Error: Socket is already disconnected!" << endl; return false; } if(shutdown(_socket, SD_BOTH) == SOCKET_ERROR) { cout << Timestamp() << "Error: Could not disconnect socket from server " << _server << ": " << WSAGetLastError() << endl; return false; } return true; // output disconnection success message in DoEvents under flag FD_CLOSE } void __stdcall Connection::SendPacket(BufferWriter* message) { if(send(_socket, message->BuildPacket(), (int)message->GetBufferPosition(), 0) == SOCKET_ERROR) { cout << Timestamp() << "Error: Could not send data to server " << _server << ": " << GetLastError() << endl; DisconnectSocket(); } else global->_bnetConnect->_sentPackets++; } void __stdcall Connection::RecvComplete(unsigned long dwError, unsigned long dwTransferred, LPWSAOVERLAPPED lpOverlapped, unsigned long flags) { if(dwTransferred == 0) // disconnected from remote host return; // disconnect handling code in FD_CLOSE in DoEvents _buflen += dwTransferred; // the size of our current buffer (new data only) ProcessData(&_recvBuffer[_bufstart]); // pass the buffer to ProcessData for action starting at _bufstart if((signed)((sizeof(_recvBuffer) - (_bufstart + _buflen)) < (dwTransferred << 1) + 120)) { memmove(_recvBuffer, &_recvBuffer[_bufstart], _buflen); _bufstart = 0; } _WSARecvBuf.buf = &_recvBuffer[_bufstart + _buflen]; _WSARecvBuf.len = sizeof(_recvBuffer) - (_bufstart + _buflen); if(WSARecv(_socket, &_WSARecvBuf, 1, &_bytes, &_flags, &_recvOv, StaticRecvComplete) == SOCKET_ERROR && GetLastError() != WSA_IO_PENDING) { cout << Timestamp() << "Error: Could not receive data from server " << _server << ": " << GetLastError() << endl; DisconnectSocket(); } } void __stdcall Connection::SendComplete(unsigned long dwError, unsigned long dwTransferred, LPWSAOVERLAPPED lpOverlapped, unsigned long flags) { if(dwTransferred) // connection refused to accept data return; // disconnect handling code in FD_CLOSE in DoEvents // ... } [/code] These is the BufferReader and BufferWriter classes; all functions are inlined into the header file: [code] #include "Includes.h" #ifndef BUFFERREADER_H #define BUFFERREADER_H class BufferReader { public: explicit __stdcall BufferReader(const PVOID data, SIZE_T size) : _Buffer((const char*)data), _BufferSize(size), _BufferPosition(0) { // ... } __stdcall ~BufferReader() { // ... } // all functions explicitly declared with __inline are to be noted as inline // extract a unsigned char from the current buffer position and advance __inline void __stdcall ExtractData(unsigned char& data) { CheckBuffer(sizeof(unsigned char)); data = (unsigned char)_Buffer[_BufferPosition]; _BufferPosition += sizeof(unsigned char); } // extract a unsigned short from the current buffer position and advance __inline void __stdcall ExtractData(unsigned short& data) { CheckBuffer(sizeof(unsigned short)); data = *(const unsigned short*)&_Buffer[_BufferPosition]; _BufferPosition += sizeof(unsigned short); } // extract a unsigned long from the current buffer position and advance __inline void __stdcall ExtractData(unsigned long& data) { CheckBuffer(sizeof(unsigned long)); data = *(const unsigned long*)&_Buffer[_BufferPosition]; _BufferPosition += sizeof(unsigned long); } // extract a ULONGLONG from the current buffer position and advance __inline void __stdcall ExtractData(ULONGLONG& data) { CheckBuffer(sizeof(ULONGLONG)); data = *(const ULONGLONG*)&_Buffer[_BufferPosition]; _BufferPosition += sizeof(ULONGLONG); } // extract untyped data from the current buffer position and advance __inline void __stdcall ExtractData(PVOID data, SIZE_T size) { CheckBuffer(size); memcpy(data, &_Buffer[_BufferPosition], size); _BufferPosition += size; } // extract a null-terminated ANSI string from the current buffer position and advance __inline void __stdcall ExtractData(const char*& data) { data = (const char*)&_Buffer[_BufferPosition]; const char* StrEnd = (const char*)memchr(&_Buffer[_BufferPosition], '\0', _BufferSize - _BufferPosition); if(!StrEnd) { cout << "Buffer overrun error! Data extraction failed!" << endl; return; } _BufferPosition += StrEnd - &_Buffer[_BufferPosition] + 1; } // extract a null-terminated std::string from the current buffer position and advance __inline void __stdcall ExtractData(std::string& data) { data = (const char*)&_Buffer[_BufferPosition]; const char* StrEnd = (const char*)memchr(&_Buffer[_BufferPosition], '\0', _BufferSize - _BufferPosition); if(!StrEnd) { cout << "Buffer overrun error! Data extraction failed!" << endl; return; } _BufferPosition += StrEnd - &_Buffer[_BufferPosition] + 1; } // extract a null-terminated Unicode string from the current buffer position and advance __inline void __stdcall ExtractData(PCWSTR& data) { PCWSTR StrEnd = wmemchr((const PWCHAR)&_Buffer[_BufferPosition], L'\0', (_BufferSize - _BufferPosition) >> 1); data = (PCWSTR)&_Buffer[_BufferPosition]; if(!StrEnd) { cout << "Buffer overrun error! Data extraction failed!" << endl; return; } _BufferPosition += (const char*)StrEnd - &_Buffer[_BufferPosition] + 2; } // extract a null-terminated std::wstring from the current buffer position and advance __inline void __stdcall ExtractData(std::wstring& data) { PCWSTR StrEnd = wmemchr((const PWCHAR)&_Buffer[_BufferPosition], L'\0', (_BufferSize - _BufferPosition) >> 1); data = (PCWSTR)&_Buffer[_BufferPosition]; if(!StrEnd) { cout << "Buffer overrun error! Data extraction failed!" << endl; return; } _BufferPosition += (const char*)StrEnd - &_Buffer[_BufferPosition] + 2; } const char* GetBuffer() const { return _Buffer; } SIZE_T GetBufferSize() const { return _BufferSize; } SIZE_T GetBufferPosition() const { return _BufferPosition; } void SetBufferPosition(SIZE_T pos) { if(pos > _BufferSize) return; _BufferPosition = pos; } private: __inline void __stdcall CheckBuffer(SIZE_T bytes) { if(_BufferPosition + bytes > _BufferSize) cout << "Buffer overrun error! Attempted to read past the buffer allocation!" << endl; } // returns false if a buffer overrun would occur __inline bool __stdcall CanAdvance(SIZE_T bytes) { return _BufferPosition + bytes <= _BufferSize; } const char* _Buffer; SIZE_T _BufferSize; mutable SIZE_T _BufferPosition; }; #endif #include "Includes.h" #ifndef BUFFERWRITER_H #define BUFFERWRITER_H class BufferWriter { public: explicit __stdcall BufferWriter(SIZE_T size = 1024) : _BufferSize(size), _BufferPosition() { _Buffer = new char[size]; if(!_Buffer) cout << "Out of memory error in attempt to allocate a buffer" << endl; ZeroMemory(_Buffer, sizeof(_Buffer)); } virtual __stdcall ~BufferWriter(void) { delete [] _Buffer; } // all functions explicitly declared with __inline are to be noted as inline // used to build a buffered packet; overrided as a pure virtual function in derived classes __inline virtual char* __stdcall BuildPacket(void) = 0; // insert an UCHARacter (byte) into the buffer __inline void __stdcall InsertData(unsigned char data) { CheckBuffer(sizeof(unsigned char)); _Buffer[_BufferPosition] = data; _BufferPosition += sizeof(unsigned char); } // insert an unsigned short (unsigned short) into the buffer __inline void __stdcall InsertData(unsigned short data) { CheckBuffer(sizeof(unsigned short)); *(PUSHORT)&_Buffer[_BufferPosition] = data; _BufferPosition += sizeof(unsigned short); } // insert an unsigned long (unsigned long) into the buffer __inline void __stdcall InsertData(unsigned long data) { CheckBuffer(sizeof(unsigned long)); *(PULONG)&_Buffer[_BufferPosition] = data; _BufferPosition += sizeof(unsigned long); } // insert a 64-bit unsigned long (__int64 && ULONGLONG) into the buffer __inline void __stdcall InsertData(ULONGLONG data) { CheckBuffer(sizeof(ULONGLONG)); *(PULONGLONG)&_Buffer[_BufferPosition] = data; _BufferPosition += sizeof(ULONGLONG); } // insert untyped data of any specified size into the buffer; if default size is 0 the function returns __inline void __stdcall InsertData(const PVOID data, SIZE_T size) { CheckBuffer(size); memcpy(&_Buffer[_BufferPosition], data, size); _BufferPosition += size; } // insert an ANSI string (CHARacter array) into the buffer __inline void __stdcall InsertData(const char* data) { SIZE_T len = (SIZE_T)strlen(data)+1; CheckBuffer(len); memcpy(&_Buffer[_BufferPosition], data, len); _BufferPosition += len; } // insert an ANSI string (std::string) into the buffer __inline void __stdcall InsertData(const std::string& data) { SIZE_T len = (SIZE_T)data.size()+1; CheckBuffer(len); memcpy(&_Buffer[_BufferPosition], data.c_str(), len); _BufferPosition += len; } // insert a Unicode string (CHARacter array) into the buffer __inline void __stdcall InsertData(PCWSTR data) { SIZE_T len = ((SIZE_T)wcslen(data) << 1) + 2; CheckBuffer(len); memcpy(&_Buffer[_BufferPosition], data, len); _BufferPosition += len; } // insert a Unicode string (std::string) into the buffer __inline void __stdcall InsertData(const std::wstring& data) { SIZE_T len = (data.size() << 1) + 2; CheckBuffer(len); memcpy(&_Buffer[_BufferPosition], data.c_str(), len); _BufferPosition += len; } // return the size of the allocated buffer SIZE_T GetBufferSize(void) const { return _BufferSize; } // return the current buffer insert position SIZE_T GetBufferPosition(void) const { return _BufferPosition; } // return a pointer to the buffer char* GetBuffer(void) const { return _Buffer; } void SetBufferPosition(unsigned long Position) { if(Position > _BufferSize) CheckBuffer(Position); _BufferPosition = Position; } // resize the current buffer to make room for more data __inline void __stdcall ResizeBuffer(SIZE_T size) { char* temp_buf = (char*)realloc(_Buffer, size); if(!temp_buf) { cout << "Out of memory error in attempt to allocate a buffer! Buffer resize failed!" << endl; return; } _Buffer = temp_buf; } protected: // check to see if the buffer needs to be resized __inline void __stdcall CheckBuffer(SIZE_T requiredBytes) { if(_BufferPosition + requiredBytes > _BufferSize) ResizeBuffer((_BufferSize << 1) > _BufferPosition + requiredBytes ? _BufferSize << 1 : _BufferPosition + requiredBytes + (_BufferSize << 1)); } char* _Buffer; SIZE_T _BufferSize, _BufferPosition; }; #endif [/code] This is my BotMain() function called after all the prerequisites are completed (irrelevant to the problem)in main(): [code] void __cdecl BotMain(void) { global->_bnetConnect = new BnetConnection(global->_server, 6112); if(global->_bnls) global->_bnlsConnect = new BNLSConnection(global->_bnlsServer, 9367); strcpy(global->_botData._username, global->_username); global->_queueThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)QueueThread, NULL, NULL, NULL); WSAData wsaData; if(WSAStartup(MAKEWORD(2, 2), &wsaData) == SOCKET_ERROR) { cout << Timestamp() << "Error: could not startup Winsock: " << WSAGetLastError() << endl; } cout << Timestamp() << "Connecting to Battle.net server " << global->_server << "..." << endl; while(global->_bnetConnect->ConnectSocket() == false) Sleep(5000); global->_socketEvents[0] = global->_bnetConnect->GetSocketEvent(); if(global->_bnls) { cout << Timestamp() << "Connecting to BNLS server " << global->_bnlsServer << "..." << endl; while(global->_bnlsConnect->ConnectSocket() == false) Sleep(5000); global->_socketEvents[1] = global->_bnlsConnect->GetSocketEvent(); } if(global->_bnls) { while(global->_shutdownBot == false) { unsigned long waitResult = WaitForMultipleObjectsEx(2, global->_socketEvents, false, INFINITE, true); switch(waitResult) { case WAIT_OBJECT_0 + (1 - 1): global->_bnetConnect->DoEvents(); break; case WAIT_OBJECT_0 + (2 - 1): global->_bnlsConnect->DoEvents(); break; case WAIT_FAILED: cout << Timestamp() << "Error: Could not wait on event handles: " << GetLastError() << endl; break; case WAIT_TIMEOUT: cout << Timestamp() << "Error: A timeout occured on waiting for an object event: " << GetLastError() << endl; break; case WAIT_IO_COMPLETION: //cout << Timestamp() << "Warning: A wait function returned to an asynchronous procedure call (APC): " << GetLastError() << endl; break; case WAIT_ABANDONED_0: cout << Timestamp() << "Warning: An object handle was abandoned while waiting on it: " << GetLastError() << endl; break; default: cout << Timestamp() << "Error: An unknown error occured when waiting on the object handles: " << GetLastError() << endl; break; } Sleep(0); } } else if(!global->_bnls) { while(global->_shutdownBot == false) { unsigned long waitResult = WaitForMultipleObjectsEx(1, &global->_socketEvents[0], false, INFINITE, true); switch(waitResult) { case WAIT_OBJECT_0 + (1 - 1): global->_bnetConnect->DoEvents(); break; case WAIT_FAILED: cout << Timestamp() << "Error: Could not wait on event handles: " << GetLastError() << endl; break; case WAIT_TIMEOUT: cout << Timestamp() << "Error: A timeout occured on waiting for an object event: " << GetLastError() << endl; break; case WAIT_IO_COMPLETION: //cout << Timestamp() << "Warning: A wait function returned to an asynchronous procedure call (APC): " << GetLastError() << endl; break; case WAIT_ABANDONED_0: cout << Timestamp() << "Warning: An object handle was abandoned while waiting on it: " << GetLastError() << endl; break; default: cout << Timestamp() << "Error: An unknown error occured when waiting on the object handles: " << GetLastError() << endl; break; } Sleep(0); } } } [/code] These are the relevant functions from BnetConnection which derives from Connection: [code] bool __stdcall BnetConnection::DoEvents() { WSANETWORKEVENTS *events = new WSANETWORKEVENTS; WSAEnumNetworkEvents(_socket, _socketEvent, events); if(events->lNetworkEvents & FD_CONNECT) { cout << Timestamp() << "Connected to server " << _server << "!" << endl; SendProtocolByte(); memset(&_recvOv, 0, sizeof(_recvOv)); _recvOv.hEvent = (HANDLE)this; _WSARecvBuf.buf = _recvBuffer; _WSARecvBuf.len = sizeof(_recvBuffer); if(WSARecv(_socket, &_WSARecvBuf, 1, &_bytes, &_flags, &_recvOv, StaticRecvComplete) == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) { cout << Timestamp() << "Initial WSARecv failed: " << WSAGetLastError() << endl; DisconnectSocket(); if(global->_autoreconnect) { Sleep(5000); BotMain(); } return false; } } else if(events->lNetworkEvents & FD_CLOSE) { cout << Timestamp() << "Disconnected from server " << _server << "!" << endl; closesocket(_socket); if(global->_autoreconnect) { Sleep(5000); BotMain(); } } return true; } void __stdcall BnetConnection::ProcessData(char* buffer) { unsigned char serverID = '\0'; unsigned char packetID = '\0'; unsigned short packetlen = 0; char data[BUFFER_SIZE] = {0}; BufferReader *pRead = new BufferReader(buffer, _buflen); while(_buflen >= 4 && _buflen >= packetlen) { pRead->ExtractData(serverID); pRead->ExtractData(packetID); pRead->ExtractData(packetlen); pRead->ExtractData(data, packetlen-4); //cout << Timestamp() << "Received packet 0x" << hex << (unsigned long)packetID << " from Battle.net." << endl << dec; if(serverID != 0xFF) { cout << Timestamp() << "Error: Received invalid packet class: " << hex << (unsigned long)serverID << endl << dec; DisconnectSocket(); return; } ProcessMessages(packetID, data, packetlen-4); _buflen -= packetlen; _bufstart += packetlen; } delete pRead; } [/code] I realize that it's a lot to go through and potential impossible to determine the error without being able to debug, but yeah...If decide to go through it and propose any solutions I'd appreciate it (and even point out coding inconsistancies, bad coding practice, etc. I'd appreciate that as well). Again the problem is that there are some disconnects caused by buffering problems; in the logs it stores (from redirecting output from std) it has a lot of errors reported by the BufferReader class which is almost always followed by a disconnection. | February 6, 2006, 7:26 PM |
Skywing | I saw a couple problems here: - There was one spot where you were setting _recvOv.hEvent to refer to this. However, you then immediately zeroed it. This looks wrong. - You should not be using WSAEnumNetworkEvents in conjunction with the overlapped socket I/O support. This is not compatible with the overlapped I/O model. The way you are supposed to do it is by pending one or more read (receive) operations. When handling a data indication, you should resubmit the read (receive) operation as quickly as possible. - Because you are incorrectly using WSAEnumNetworkEvents, it might be possible for you to inadvertently start multiple overlapped receive operations on the same buffer and OVERLAPPED structure simultaneously; this is not allowed. | February 6, 2006, 8:28 PM |
Mephisto | I guess it wasn't necessary to have the OVERLAPPED objects set to this; I thought it was. :) [quote author=Skywing link=topic=14172.msg144840#msg144840 date=1139257728] - You should not be using WSAEnumNetworkEvents in conjunction with the overlapped socket I/O support. This is not compatible with the overlapped I/O model. The way you are supposed to do it is by pending one or more read (receive) operations. When handling a data indication, you should resubmit the read (receive) operation as quickly as possible.[/quote] I understand the error, I just don't understand the solution. Could you explain better for me, please? | February 6, 2006, 9:31 PM |
Skywing | With overlapped socket I/O, you proactively start doing your receives and the operating system will complete them when there is data available. This differs from event-driven I/O (like select), where you wait until you get some kind of indication from the system that there is data available and then try to read it. | February 7, 2006, 2:40 PM |
Mephisto | I thought I was doing what you're saying by waiting on the handle connected to the socket with the wait function (see BotMain) and then when it does fire I'd use EnumNetworkEvents to figure out what fired the handle. With my program design, DoEvents is only called once and begins the initial Recv operation; after that it's called by the RecvComplete function of the class. The only additional times the function is called is on network disconnects (or connects). | February 7, 2006, 3:09 PM |