#include "DomainConnection.h" #include using namespace std; //SOCKET <-> DomainConnection lookup and maintenance map DomainConnection::m_instances; fd_set DomainConnection::m_rfds, DomainConnection::m_wfds, DomainConnection::m_xfds; SOCKET DomainConnection::m_max = 0; unsigned int DomainConnection::m_count = 0; pthread_t DomainConnection::m_eventLoopThread; bool DomainConnection::m_eventLoopStarted = false; pthread_mutex_t DomainConnection::m_hEventLoop_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t DomainConnection::m_hDNS_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t DomainConnection::m_hasReadWriteFDs_cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t DomainConnection::m_hasReadWriteFDs_mutex = PTHREAD_MUTEX_INITIALIZER; time_t DomainConnection::m_lastSelectTime = time(0); unsigned int DomainConnection::m_lastInterval = 0; void catch_SIGPIPE(int sig_num) { DEBUGERROR0("[DomainConnection]: catch_sig(SIGPIPE) (Broken pipe:ignored)"); } DomainConnection::DomainConnection(const char *_domain, DomainConnectionEventSink *_listener, unsigned int _keepAlive, unsigned int _linger): m_consecutiveConnectionFails(0), m_millisecondsBetweenRequests(0), m_lastEvent(time(0)), m_DNSResolved(false), m_s(0), m_listener(_listener), m_readyForReading(false), m_readyForWriting(false), m_threwException(false), m_somethingReady(false), m_timedout(false), m_connected(false), //connecting and connected have a complex relationship, not an overall staus m_connecting(false), //connect cannot start with m_connecting=true. It is only set true by the connect function m_socketopen(false), //socket not initialised m_readbuffer(0), m_readbufsize(0), m_writebuffer(0), m_writebufsize(0), m_mode(waitingForNothing), m_keepAlive(_keepAlive), //0 default disables keep-alive m_linger(_linger), //0 default disables linger m_domain(strdupCheck(_domain)) //make a copy, caller responsible for releasing their _domain { MEMBER_INIT_MUTEX(m_DCOwn); claim(_listener); //the DomainConnection does not automatically listen for the connection as the controller may want to wait //by not adding the socket to the m_wfds group the connection will not trigger anything //as soon as it is added a readyForWrite event will occur if (!m_count) { //static single threaded //initialise the socket library DEBUGPRINT0("[DomainConnection]: sockets initialisation", DEBUG_LINE); INITSOCKET; FD_ZERO(&m_rfds); FD_ZERO(&m_wfds); FD_ZERO(&m_xfds); DEBUGPRINT("[DomainConnection]: zero fd_sets (%u/%u/%u)", DEBUG_LINE, FD_COUNT(m_rfds), FD_COUNT(m_wfds), FD_COUNT(m_xfds)); IGNORE_SIGPIPE; } if (++m_count > FD_SETSIZE) {DEBUGERROR0("[DomainConnection]: ExceededFDSETMaxSize"); throw ExceededFDSETMaxSize();} //fd_sets have a maximum size (1024 bits on Linux, 64 bytes on Windows) } DomainConnection::~DomainConnection() { if (m_domain) {free((void*)m_domain);m_domain = 0;} if (m_socketopen) closeConnectedSocket(); //force this ok because we no longer need this Domain if (!--m_count) { endEventLoop(); //synchronous (joins the event loop thread) //FINALISESOCKET; //would be re-initialised on the next new(). other things might be using the socket layer (like WebServer) pthread_mutex_destroy(&m_hEventLoop_mutex); pthread_mutex_destroy(&m_hDNS_mutex); pthread_mutex_destroy(&m_hasReadWriteFDs_mutex); pthread_cond_destroy (&m_hasReadWriteFDs_cond); } pthread_mutex_destroy(&m_DCOwn); } void DomainConnection::claim(DomainConnectionEventSink *_listener) { //no need for an atmoic section here because the null listener check will not cause a problem if assigned after if (_listener) { pthread_mutex_lock(&m_DCOwn); //wait for release() if (m_listener) { DEBUGERROR("[%s]: DCAlreadyClaimed", m_domain); throw DCAlreadyClaimed(); } m_listener = _listener; } } void DomainConnection::release(DomainConnectionEventSink *listener) { //waitForNothing() use the m_hasReadWriteFDs_mutex critical section waitForNothing(); //implicitly stop waiting for events on this socket as there is no listener anymore //in a critical section otherwise the listener may be null within the eventloop after the non-null check //because of this, release(...) *must not* be called from the event loop thread, only the listener thread e.g. ~Protocol() //allow a double release m_listener already == 0 //the eventLoop finishConversation(...) call must exit and, in fact, the entire listener event system, before the ~Protocol() //can enter the section below and complete the delete listener. pthread_mutex_lock(&m_hEventLoop_mutex); if (m_listener && m_listener != listener) { DEBUGERROR("[%s]: DCNotYours", m_domain); throw DCNotYours(); } m_listener = 0; pthread_mutex_unlock(&m_hEventLoop_mutex); pthread_mutex_unlock(&m_DCOwn); //allow someone else to claim() } const int DomainConnection::DNSLookup() { DEBUGPRINT("[%s]: Synchronous DNSLookup", DEBUG_LINE, m_domain); //address family memset(&m_sin, 0, sizeof(m_sin)); m_sin.sin_port = htons(80); m_sin.sin_family = AF_INET; //DNS resolution //gethostbyname() returns a pointer to STATIC data! hence static mutex pthread_mutex_lock(&m_hDNS_mutex); m_hp = gethostbyname(m_domain); if (m_hp) { DEBUG_RESULT_OK; memcpy(&m_sin.sin_addr, m_hp->h_addr, sizeof m_sin.sin_addr); pthread_mutex_unlock(&m_hDNS_mutex); m_DNSResolved = true; } else { pthread_mutex_unlock(&m_hDNS_mutex); DEBUG_RESULT_FAIL; m_DNSResolved = false; DEBUGERROR("[%s]: DNSLookup Failed!", m_domain); throw DNSFailure(); } return !m_DNSResolved; //0=ok, !0=failed } const int DomainConnection::connect() { int status = 0; if (m_connected) DEBUGERROR("[%s]: Still connected!", m_domain); if (!m_DNSResolved) { DEBUGERROR("[%s]: DNSFailure", m_domain); throw DNSFailure(); } if (!m_connecting && !m_connected && m_DNSResolved) { DEBUGPRINT("[%s]: connecting", DEBUG_LINE, m_domain); //setup the socket if (m_socketopen) closeDisconnectedSocket(); //will remove the entry in the global map m_s = socket(AF_INET, SOCK_STREAM, 0); if (m_s == INVALID_SOCKET) { DEBUGERROR("[%s]: Invalid Socket", m_domain); throw InvalidSocket(); } SETASYNC(m_s); if (m_keepAlive) {DEBUGPRINT("[%s]: set keep-alive [%u]", DEBUG_LINE, m_domain, m_keepAlive);SETKEEPALIVE(m_s, m_keepAlive);} if (m_linger) {DEBUGPRINT("[%s]: set linger [%u]", DEBUG_LINE, m_domain, m_linger );SETLINGER(m_s, m_linger);} m_socketopen = true; //just means that the FD is valid if (m_s >= m_max) m_max = m_s; //update the maximum socket value for the select statement (EventLoop()) m_instances.insert(make_pair(m_s, this)); //add this instance to the global map DEBUGPRINT("[%s]: Socket allocated from system pool: [%i]", DEBUG_LINE, m_domain, m_s); //connect (SOCKET_ERROR == -1) int ret = ::connect(m_s, (const sockaddr*) &m_sin, sizeof(m_sin)); if (ret == SOCKET_ERROR) { //start asynchronous connection attempt if (SOCKET_INPROGRESS) { //everything is ok. The connect function has reported that it is in the process of an asynchronous connection FD_SET(m_s, &m_xfds); //listen for exceptions: complements the disconnect FD_CLR() DEBUGPRINT("[%s]: Connection attempt started", DEBUG_LINE, m_domain); m_connecting = true; m_connected = false; //reset mode (the m_s will have changed thus resetting the FD_SETS) //only write needs to be maintained because read attempts will be lost if (m_mode == waitingForWrite) waitForWrite(); if (!m_eventLoopStarted) {m_eventLoopStarted = true; beginEventLoop();} } else { //another error: report it m_connecting = false; m_connected = false; DEBUGERROR("[%s]: Socket connection error:%i (%s)", m_domain, status, (WSAGetLastErrorText()?WSAGetLastErrorText():"unknown")); } } else DEBUGERROR("[%s]: Socket connection error: not async [%i](%s)", m_domain, ret, WSAGetLastErrorText()); } //again, no events will be triggered upon connect unless the controller has explicitly asked to waitForConnect/waitForWrite //in order to add the socket to the m_wfds group return !m_connecting; //0=ok, !0=failed } const bool DomainConnection::closeSocket() { //private function: use //closeDisconnectedSocket() for sockets that have been dropped by the other end //closeConnectedSocket() for sockets still open at the other end or not known state //see TCP notes at top of this file: 2 minute socket close times may occur if the // other end has not initiated the closure DEBUGPRINT("[%s]: disconnecting this side socket", DEBUG_LINE, m_domain); //stop listening in subsequent select calls for m_s because it is being changed pthread_mutex_lock(&m_hasReadWriteFDs_mutex); FD_CLR(m_s, &m_rfds); FD_CLR(m_s, &m_wfds); FD_CLR(m_s, &m_xfds); pthread_mutex_unlock(&m_hasReadWriteFDs_mutex); int ret; if (ret = CLOSESOCKET(m_s)) { //return the socket to the system pool //probably an EINPROGRESS problem: the server has not disconnected DEBUGERROR("[%s]: Socket close error:%i (%s)", m_domain, ret, (WSAGetLastErrorText()?WSAGetLastErrorText():"unknown")); } else { m_socketopen = false; } m_instances.erase(m_s); //delayed removal outside of the event loop from the in-use map m_s = 0; //m_s will be removed m_connecting = false; return m_socketopen; } const bool DomainConnection::closeConnectedSocket() { //disconnects even if the other end doesn't know. clears all FD_SETS and status //server will hold the socket open for 2 minutes //thus re-connects require a new socket FILE descriptor //(there are only 1024 on UNIX and 64 of Windows) if (m_s && m_socketopen) closeSocket(); return m_socketopen; } const bool DomainConnection::closeDisconnectedSocket() { //remove the socket completely if (m_connected) DEBUGERROR("[%s]: cannot close a connected socket!", m_domain); if (m_s && m_socketopen && !m_connected) closeSocket(); return m_socketopen; } const int DomainConnection::send(const char *sBufOut, const size_t _sizeofbuf) const { size_t sizeofbuf = _sizeofbuf; return ::send(m_s, sBufOut, (int)sizeofbuf, 0); } const int DomainConnection::read(char sBufIn[], const size_t sizeofbuf) const { return ::recv(m_s, sBufIn, (int)sizeofbuf - 1, 0); } void DomainConnection::waitForRead(char *_buffer, const size_t _bufsize) { //DomainConnection handles the buffer because a 0 byte read indicates that the other end has closed the connection: thus an event is fired if (_buffer) { m_readbuffer = _buffer; m_readbufsize = _bufsize; } if (m_readbuffer) { DEBUGPRINT("[%s]: waitForRead(%u)", DEBUG_LINE, m_domain, m_readbufsize); m_mode = waitingForRead; //signals the select statement when to run pthread_mutex_lock(&m_hasReadWriteFDs_mutex); FD_SET(m_s, &m_rfds); FD_CLR(m_s, &m_wfds); pthread_cond_signal(&m_hasReadWriteFDs_cond); pthread_mutex_unlock(&m_hasReadWriteFDs_mutex); } else { DEBUGERROR("[%s]: NoReadBuffer", m_domain); throw NoReadBuffer(); } } void DomainConnection::waitForWrite(const char *_buffer, const size_t _bufsize) { //DomainConnection handles the buffer because waitForRead does as well: consistency if (_buffer) { m_writebuffer = _buffer; m_writebufsize = _bufsize ? _bufsize : strlen(m_writebuffer); } if (m_writebuffer && *m_writebuffer) { //re-connect here if connection has been signalled lost DEBUGPRINT("[%s]: waitForWrite(%u)", DEBUG_LINE, m_domain, m_writebufsize); if (!(m_connected || m_connecting)) connect(); //the reconnect will wipe the m_?fds m_mode = waitingForWrite; //signals the select statement when to run pthread_mutex_lock(&m_hasReadWriteFDs_mutex); FD_CLR(m_s, &m_rfds); FD_SET(m_s, &m_wfds); pthread_cond_signal(&m_hasReadWriteFDs_cond); pthread_mutex_unlock(&m_hasReadWriteFDs_mutex); } else { DEBUGERROR("[%s]: NoWriteBuffer", m_domain); throw NoWriteBuffer(); } } void DomainConnection::waitForNothing() { DEBUGPRINT("[%s]: waitForNothing", DEBUG_LINE, m_domain); pthread_mutex_lock(&m_hasReadWriteFDs_mutex); FD_CLR(m_s, &m_rfds); FD_CLR(m_s, &m_wfds); pthread_mutex_unlock(&m_hasReadWriteFDs_mutex); m_mode = waitingForNothing; } void DomainConnection::ignoreReadData() { DEBUGERROR("[%s]: ignoreReadData", m_domain); //will trigger appropriate Close Connection events etc. like a normal read //if ignoreReadData() is called when there is no read data it will wait for read data char ignorebuf[1024]; //save buffers just in case char *oldreadbuffer = m_readbuffer; size_t oldreadsize = m_readbufsize; m_readbuffer = ignorebuf; m_readbufsize = 1024; readyForReading(); //only reads one buffer, waits for the select to re-trigger the event again m_readbuffer = oldreadbuffer; m_readbufsize = oldreadsize; } const size_t DomainConnection::readyForException() {m_listener->threwException();return 0;} const size_t DomainConnection::readyForSomething() {m_listener->somethingReady();return 0;} //--------------------------------------- static ------------------------------------------------------ void DomainConnection::beginEventLoop() { //start the event loop pthread_create(&m_eventLoopThread, NULL, DomainConnection::eventLoop, NULL); } void DomainConnection::endEventLoop() { if (pthread_isvalid(m_eventLoopThread)) { pthread_cancel(m_eventLoopThread); //asynchronous signal to cancel at next pthread_testcancel(); pthread_join(m_eventLoopThread, 0); //synchronous wait for cancel } } void DomainConnection::staticrunCleanup(LPVOID lpParam) { pthread_invalidate(m_eventLoopThread); } THREAD_CALLBACK_TYPE DomainConnection::eventLoop(LPVOID lpParam) { DEBUGPRINT0("event loop", DEBUG_LINE); pthread_cleanup_push(DomainConnection::staticrunCleanup, 0); do { DomainConnection::waitForEvent(); pthread_testcancel(); } while (true); pthread_cleanup_pop(1); //and invoke return 0; } const vector DomainConnection::waitForEvent(const u_int timeout) { //caller responsible for releasing return value vector changed;changed.reserve(m_count); fd_set rfds, wfds, xfds; bool selectTimedout, completeFailure; int fdsChanged; //can be negative map m_instancesCopy; map::iterator itConn; DomainConnection *dConn; time_t now = time(0); timeval timeoutval; unsigned int oldConsecutiveConnectionFails; //wait for FDs in the sets otherwise the select will error pthread_mutex_lock(&m_hasReadWriteFDs_mutex); //no FDs can be changed during this mutex { if (FD_EMPTY(m_rfds) && FD_EMPTY(m_wfds)) { DEBUGPRINT0("no read/write FDs: entering infinite condition wait", DEBUG_LINE); pthread_cond_wait (&m_hasReadWriteFDs_cond, &m_hasReadWriteFDs_mutex); //infinite wait for something to do DEBUGPRINT0("a read/write FD has been set: continue", DEBUG_LINE); } //make a copy of all inputs because select modifies the original timeoutval.tv_sec = timeout; timeoutval.tv_usec = 0; FD_COPY(&m_rfds, &rfds); FD_COPY(&m_wfds, &wfds); FD_COPY(&m_xfds, &xfds); //additions to the instances list are added outside of the loop so as not to disrupt the loop iterator //CloseConnection:1 connections will create a new SOCKET descriptor //events will trigger on these on the next select (immediately) m_instancesCopy.insert(m_instances.begin(), m_instances.end()); } pthread_mutex_unlock(&m_hasReadWriteFDs_mutex); //main select poll (continue if the select returns an WSAEINTR/EINTR indicating that a signal interrupted the loop) m_lastInterval = now - m_lastSelectTime; m_lastSelectTime = now; DEBUGPRINT("selected: (%u/%u/%u)", DEBUG_LINE, FD_COUNT(rfds), FD_COUNT(wfds), FD_COUNT(xfds)); do {fdsChanged = select((int)m_max+1, &rfds, &wfds, &xfds, &timeoutval);} while (fdsChanged == SOCKET_ERROR && errno == EINTR); DEBUGPRINT("-------------- select:%i (%u/%u/%u) changed in (%u/%u/%u) errno=%i", DEBUG_LINE, fdsChanged, FD_COUNT(rfds), FD_COUNT(wfds), FD_COUNT(xfds), FD_COUNT(m_rfds), FD_COUNT(m_wfds), FD_COUNT(m_xfds), errno); selectTimedout = (fdsChanged == 0); if (selectTimedout) DEBUGPRINT0("select timeout", DEBUG_LINE); completeFailure = (fdsChanged == SOCKET_ERROR); if (completeFailure) DEBUGERROR0("select complete failure"); //loop through ALL DCs checking data read/write and exceptions //note that ALL DCs are checked. pthread_mutex_lock(&m_hEventLoop_mutex); for (itConn = m_instancesCopy.begin(); itConn != m_instancesCopy.end(); itConn++) { dConn = itConn->second; //the dc in question (static function) if (completeFailure) { //subsystem collapse: DEBUGERROR("[all]: Select error:%i (%s)", errno, WSAGetLastErrorText()); throw CompleteSocketFailure(); } else { //raw results: check the result fs_sets using FD_ISSET() wrapper functions //note that exceptions are a *real event* and cancel timeouts //something ready is only a *real event* occurrence (rwx) //timeouts include lastAction but also selectTimeouts (whole select on all fd_set entries timedout) //timeouts only happend on DCs whose SOCKETS have not reported any ready states. Ready states take precedence always //select responses occur regularly, not after timeout //timeout will be set *until* a real event occurs on the DC oldConsecutiveConnectionFails = dConn->m_consecutiveConnectionFails; //to check to see if anything increased it //*real events* dConn->m_readyForReading = dConn->FD_ISSET_IN(rfds); //read data available dConn->m_readyForWriting = dConn->FD_ISSET_IN(wfds); //write data available dConn->m_threwException = dConn->FD_ISSET_IN(xfds); //exception data available //calculated events dConn->m_somethingReady = (dConn->m_readyForReading || dConn->m_readyForWriting || dConn->m_threwException); //*real event* happened dConn->m_timedout = (!dConn->m_somethingReady && (now - dConn->m_lastEvent >= timeout || selectTimedout)); //no *real event*, only timeout //flag extra processing if (dConn->m_somethingReady || dConn->m_timedout) changed.push_back(dConn); //need to do this even without an event listener if (dConn->m_threwException) dConn->m_consecutiveConnectionFails++; //event sink notifications //note that the events may change the dc status during these calculations: thus only one event at a time //only readyForReading *OR* readyForWrinting should trigger //listeners should implement somethingReady() *OR* readyForReading() and readyForWriting() if (dConn->m_listener) { //if something is listening (always a Protocol) try { //these functions can throw exceptions on this event loop thread //e.g. InvalidSocket() on reconnect, NoReadBuffer() //We DO NOT want to crash this event loop just because of one faulty connection //so pass the exception object through to the listener //it (Protocol) will, by default, pass the Exception through to its thread and on to its owner if (dConn->m_timedout) { //no *real events*, only a timeout if (dConn->m_mode == waitingForRead ) dConn->m_listener->timedoutOnRead(); else if (dConn->m_mode == waitingForWrite) dConn->m_listener->timedoutOnWrite(); else dConn->m_listener->timedoutForNowt(); } else { dConn->m_lastEvent = time(0); //last time read, write or exception ocurred //one event at a time: event handlers may change the mode and trigger two events if (dConn->m_somethingReady ) dConn->readyForSomething(); //one at a time because the event handlers change the DC mode //*real events* - in sync with mode if (dConn->m_threwException ) dConn->readyForException(); else if (dConn->m_readyForReading && dConn->m_mode == waitingForRead ) dConn->readyForReading(); else if (dConn->m_readyForWriting && dConn->m_mode == waitingForWrite) dConn->readyForWriting(); //*real events* - out of sync with mode else if (dConn->m_readyForReading && dConn->m_mode == waitingForWrite) dConn->m_listener->outOfSyncRead(); else if (dConn->m_readyForWriting && dConn->m_mode == waitingForRead ) dConn->m_listener->outOfSyncWrite(); } } catch (DomainConnectionException &e) { //trap the exceptions here and ask the listener (Protocol) what to do dConn->m_listener->customException(e); } } else if (dConn->m_somethingReady) DEBUGERROR("[%s]: No listener for (%i/%i/%i) events", dConn->m_domain, dConn->m_readyForReading, dConn->m_readyForWriting, dConn->m_threwException); //reset the consecutive failures if nothing has incremented it: that way anything can register a fail if (dConn->m_somethingReady && oldConsecutiveConnectionFails == dConn->m_consecutiveConnectionFails) dConn->m_consecutiveConnectionFails = 0; } } //the Report_Full will trigger here as it waits for the Mutex //m_readyFor*s will have been set but events will change the wait modes pthread_mutex_unlock(&m_hEventLoop_mutex); return changed; } const size_t DomainConnection::readyForWriting() { if (!m_connected) DEBUGPRINT("[%s]: connected", DEBUG_LINE, m_domain); int bytes = 0; m_connected = true; m_connecting = false; if (m_writebuffer) { //m_writebuffer is registered by the wait function (much like the read system) bytes = send(m_writebuffer, m_writebufsize); if (bytes > 0) { //success, bytes=num sent #ifdef _DEBUG char debug[4096]; _SNPRINTF(debug, 4096, "[%s]:%i/%u sent:\n%s", m_domain, bytes, m_writebufsize, m_writebuffer); //remove double returns \r\n char *change = debug; while (*change++) {if (*change == '\r') *change = ' ';} DEBUGPRINT0(debug, DEBUG_LINE); #endif //continue write until finished whole buffer m_writebuffer += bytes; m_writebufsize -= bytes; m_listener->finishedWrite(m_writebufsize); //parameter = 0 when finished } else if (!bytes) { m_connected = false; //this false will cause an auto-reconnect on waitForWrite() (ignored if already re-connecting) m_consecutiveConnectionFails++; DEBUGERROR("[%s]: Socket closed on write [%s] (%u) consecutive fails: [%u]", m_domain, (m_writebuffer == 0 ? "" : m_writebuffer), m_writebufsize, m_consecutiveConnectionFails); m_listener->connectionClosedOnWrite(); //a 0 byte read means that the connection has been closed //needs to re-connect a re-try the write operation } else { //bytes==SOCKET_ERROR //note that socket errors are a base of WSABASEERR==10000. Some common probs (from winsock2.h): //http://msdn2.microsoft.com/en-us/library/ms740121.aspx // WSAECONNABORTED 10053 DEBUGERROR("[%s]: Socket write error:%i (%s)", m_domain, errno, (WSAGetLastErrorText()?WSAGetLastErrorText():"unknown")); m_connected = false; //this false will cause an auto-reconnect on waitForWrite() (ignored if already re-connecting) m_listener->connectionClosedOnWrite(); //a 0 byte read means that the connection has been closed //let the caller waitForWrite(m_writebuffer, m_writebufsize); again to reconnect in the case of closed connection } } else m_listener->readyForWriting(); return bytes; } const bool DomainConnection::closeConnectionWithZeroRead() { //this will cause the other end to drop the connection: 0 byte read if (read(m_readbuffer, 1)) return false; else { m_connected = false; return true; } } const size_t DomainConnection::readyForReading() { int bytes = 0; if (m_readbuffer) { if (m_readbufsize <= 2) { //space for zero terminator and a byte if (m_listener->outOfBufferSpace()) closeConnectionWithZeroRead(); } else { bytes = read(m_readbuffer, m_readbufsize-1); if (bytes > 0) { //sucessfull read m_readbuffer += bytes; //get ready for next read m_readbufsize -= bytes; *m_readbuffer = 0; //null terminate buffer m_listener->finishedRead(bytes); } else if (!bytes) { //bytes can be 0 indicating that the socket was gracefully reset at the server m_connected = false; //this false will cause an auto-reconnect on waitForWrite() (ignored if already re-connecting) m_listener->connectionClosedOnRead(m_readbufsize); //a 0 byte read means that the connection has been closed //DO NOT attempt the read again because, unlike write, the closed connection signifies end-of-conversation } else { //bytes==SOCKET_ERROR //note that socket errors are a base of WSABASEERR==10000. Some common probs (from winsock2.h): //http://msdn2.microsoft.com/en-us/library/ms740121.aspx // WSAECONNABORTED 10053 - server aborts the connection if it really doesn't like the request DEBUGERROR("[%s]: Socket read error:%i (%s)", m_domain, errno, (WSAGetLastErrorText()?WSAGetLastErrorText():"unknown")); m_connected = false; //this false will cause an auto-reconnect on waitForWrite() (ignored if already re-connecting) m_listener->connectionClosedOnRead(m_readbufsize); //a 0 byte read means that the connection has been closed } } } else m_listener->readyForReading(); return bytes; } const char *DomainConnection::WSAGetLastErrorText() { switch (errno) { #ifdef _LINUX case EACCES: return "EACCES"; case EPERM: return "EPERM"; case EADDRINUSE: return "EADDRINUSE"; case EAFNOSUPPORT: return "EAFNOSUPPORT"; case EAGAIN: return "EAGAIN"; case EALREADY: return "EALREADY"; case EBADF: return "EBADF"; case ECONNREFUSED: return "ECONNREFUSED"; case EFAULT: return "EFAULT"; case EINPROGRESS: return "EINPROGRESS"; case EINTR: return "EINTR"; case EISCONN: return "EISCONN"; case ENETUNREACH: return "ENETUNREACH"; case ENOTSOCK: return "ENOTSOCK"; case ETIMEDOUT: return "ETIMEDOUT"; #else case WSAEINTR: return "WSAEINTR"; case WSAEBADF: return "WSAEBADF"; case WSAEACCES: return "WSAEACCES"; case WSAEFAULT: return "WSAEFAULT"; case WSAEINVAL: return "WSAEINVAL"; case WSAEMFILE: return "WSAEMFILE"; case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK"; case WSAEINPROGRESS: return "WSAEINPROGRESS"; case WSAEALREADY: return "WSAEALREADY"; case WSAENOTSOCK: return "WSAENOTSOCK"; case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ"; case WSAEMSGSIZE: return "WSAEMSGSIZE"; case WSAEPROTOTYPE: return "WSAEPROTOTYPE"; case WSAENOPROTOOPT: return "WSAENOPROTOOPT"; case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP"; case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT"; case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT"; case WSAEADDRINUSE: return "WSAEADDRINUSE"; case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL"; case WSAENETDOWN: return "WSAENETDOWN"; case WSAENETUNREACH: return "WSAENETUNREACH"; case WSAENETRESET: return "WSAENETRESET"; case WSAECONNABORTED: return "WSAECONNABORTED"; case WSAECONNRESET: return "WSAECONNRESET"; case WSAENOBUFS: return "WSAENOBUFS"; case WSAEISCONN: return "WSAEISCONN"; case WSAENOTCONN: return "WSAENOTCONN"; case WSAESHUTDOWN: return "WSAESHUTDOWN"; case WSAETOOMANYREFS: return "WSAETOOMANYREFS"; case WSAETIMEDOUT: return "WSAETIMEDOUT"; case WSAECONNREFUSED: return "WSAECONNREFUSED"; case WSAELOOP: return "WSAELOOP"; case WSAENAMETOOLONG: return "WSAENAMETOOLONG"; case WSAEHOSTDOWN: return "WSAEHOSTDOWN"; case WSAEHOSTUNREACH: return "WSAEHOSTUNREACH"; case WSAENOTEMPTY: return "WSAENOTEMPTY"; case WSAEPROCLIM: return "WSAEPROCLIM"; case WSAEUSERS: return "WSAEUSERS"; case WSAEDQUOT: return "WSAEDQUOT"; case WSAESTALE: return "WSAESTALE"; case WSAEREMOTE: return "WSAEREMOTE"; case WSASYSNOTREADY: return "WSASYSNOTREADY"; case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED"; case WSANOTINITIALISED: return "WSANOTINITIALISED"; case WSAEDISCON: return "WSAEDISCON"; case WSAENOMORE: return "WSAENOMORE"; case WSAECANCELLED: return "WSAECANCELLED"; case WSAEINVALIDPROCTABLE: return "WSAEINVALIDPROCTABLE"; case WSAEINVALIDPROVIDER: return "WSAEINVALIDPROVIDER"; case WSAEPROVIDERFAILEDINIT: return "WSAEPROVIDERFAILEDINIT"; case WSASYSCALLFAILURE: return "WSASYSCALLFAILURE"; case WSASERVICE_NOT_FOUND: return "WSASERVICE_NOT_FOUND"; case WSATYPE_NOT_FOUND: return "WSATYPE_NOT_FOUND"; case WSA_E_NO_MORE: return "WSA_E_NO_MORE"; case WSA_E_CANCELLED: return "WSA_E_CANCELLED"; case WSAEREFUSED: return "WSAEREFUSED"; #endif default: return "unknown"; } return 0; }