#include "TcpConnection.h" #include "TcpManager.h" #include "Clock.h" #include #ifdef EXTERNAL_DISTRO namespace NAMESPACE { #endif //used when want to open new connection with this socket TcpConnection::TcpConnection(TcpManager *tcpManager, TcpBlockAllocator *sendAlloc, TcpManager::TcpParams ¶ms, const IPAddress &destIP, short destPort, unsigned timeout) : m_nextConnection(NULL), m_prevConnection(NULL), m_socket(INVALID_SOCKET), m_nextKeepAliveConnection(NULL), m_prevKeepAliveConnection(NULL), m_aliveListId(tcpManager->m_aliveList.m_listID), m_nextRecvDataConnection(NULL), m_prevRecvDataConnection(NULL), m_recvDataListId(tcpManager->m_dataList.m_listID), m_manager(tcpManager), m_status(StatusNegotiating), m_handler(NULL), m_destIP(destIP), m_destPort(destPort), m_refCount(0), m_sendAllocator(sendAlloc), m_head(NULL), m_tail(NULL), m_bytesRead(0), m_bytesNeeded(0), m_params(params), m_recvBuff(NULL), m_connectTimeout(timeout), m_connectTimer(), m_wasConRemovedFromMgr(false) { //start connection timer m_connectTimer.start(); memset(&m_addr, 0, sizeof(m_addr)); if (m_params.maxRecvMessageSize != 0) { m_recvBuff = new char[m_params.maxRecvMessageSize]; } m_socket = socket(AF_INET, SOCK_STREAM, 0); setOptions(); m_addr.sin_family = AF_INET; m_addr.sin_port = htons(m_destPort); m_addr.sin_addr.s_addr = m_destIP.GetAddress(); int err = connect(m_socket, (sockaddr *)&m_addr, sizeof(m_addr)); if(err == SOCKET_ERROR) { #ifdef WIN32 int sockerr = WSAGetLastError(); if(sockerr != WSAEWOULDBLOCK) { //a real error m_status = StatusDisconnected; } else { m_status = StatusNegotiating; } #else // UNIX if (errno != EINPROGRESS) { m_status = StatusDisconnected; } else { m_status = StatusNegotiating; } #endif } else { //we are connected, wow m_status = StatusConnected; } } //used when server mode creates new connection object representing a connect request TcpConnection::TcpConnection(TcpManager *tcpManager, TcpBlockAllocator *sendAlloc, TcpManager::TcpParams ¶ms, SOCKET socket, const IPAddress &destIP, short destPort) : m_nextConnection(NULL), m_prevConnection(NULL), m_socket(socket), m_nextKeepAliveConnection(NULL), m_prevKeepAliveConnection(NULL), m_aliveListId(tcpManager->m_aliveList.m_listID), m_nextRecvDataConnection(NULL), m_prevRecvDataConnection(NULL), m_recvDataListId(tcpManager->m_dataList.m_listID), m_manager(tcpManager), m_status(StatusConnected), m_handler(NULL), m_destIP(destIP), m_destPort(destPort), m_refCount(0), m_sendAllocator(sendAlloc), m_head(NULL), m_tail(NULL), m_bytesRead(0), m_bytesNeeded(0), m_params(params), m_recvBuff(NULL), m_connectTimeout(0), m_connectTimer(), m_wasConRemovedFromMgr(false) { memset(&m_addr, 0, sizeof(m_addr)); if (m_params.maxRecvMessageSize != 0) { m_recvBuff = new char[m_params.maxRecvMessageSize]; } setOptions(); } void TcpConnection::setOptions() { if (m_socket != INVALID_SOCKET) { #if defined(WIN32) unsigned long isNonBlocking = 1; int outBufSize = m_params.outgoingBufferSize; int inBufSize = m_params.incomingBufferSize; int keepAlive = 1; int reuseAddr = 1; struct linger ld; ld.l_onoff = 0; ld.l_linger = 0; if (ioctlsocket(m_socket, FIONBIO, &isNonBlocking) != 0 || setsockopt(m_socket, SOL_SOCKET, SO_SNDBUF, (char *)&outBufSize, sizeof(outBufSize)) != 0 || setsockopt(m_socket, SOL_SOCKET, SO_RCVBUF, (char *)&inBufSize, sizeof(inBufSize)) != 0 || setsockopt(m_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepAlive, sizeof(keepAlive)) != 0 || setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&reuseAddr, sizeof(reuseAddr)) != 0 || setsockopt(m_socket, SOL_SOCKET, SO_LINGER, (char *)&ld, sizeof(ld)) != 0 ) { //bummer, but no need to crash now.... ? } #else // linux is to remain the default compile mode unsigned long isNonBlocking = 1; unsigned long keepAlive = 1; unsigned long outBufSize = m_params.outgoingBufferSize; unsigned long inBufSize = m_params.incomingBufferSize; unsigned long reuseAddr = 1; struct linger ld; ld.l_onoff = 0; ld.l_linger = 0; if (ioctl(m_socket, FIONBIO, &isNonBlocking) != 0 || setsockopt(m_socket, SOL_SOCKET, SO_SNDBUF, &outBufSize, sizeof(outBufSize)) != 0 || setsockopt(m_socket, SOL_SOCKET, SO_RCVBUF, &inBufSize, sizeof(inBufSize)) != 0 || setsockopt(m_socket, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(keepAlive)) != 0 || setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)) != 0 || setsockopt(m_socket, SOL_SOCKET, SO_LINGER, &ld, sizeof(ld)) != 0) { //bummer, but no need to crash now.... ? } #endif } } int TcpConnection::finishConnect() { AddRef(); int returnVal = 0; /**< returns < 0 if fatal error and connect will not work, =0 if need more time, >0 if connect completed */ switch (m_status) { case StatusDisconnected: { //something went wrong Disconnect(false); returnVal = -1; } break; case StatusNegotiating: { #ifdef WIN32 //try to finish connection fd_set wrSet; FD_ZERO(&wrSet); FD_SET(m_socket, &wrSet); timeval t; t.tv_sec = 0; t.tv_usec = 0; int err = select(m_socket + 1, NULL, &wrSet, NULL, &t); if (err == 0) { //needs more time returnVal = 0; } else if (err == SOCKET_ERROR) { //huhoh, let's hope it needs more time int sockerr = WSAGetLastError(); if (sockerr == WSAEINPROGRESS || sockerr == WSAEWOULDBLOCK || sockerr == WSAEALREADY || sockerr == WSAEINVAL) { //yep returnVal = 0; } else { Disconnect(false); returnVal = -1; } } else { //check if write bit set for socket if (FD_ISSET(m_socket, &wrSet)) { //connection complete m_status = StatusConnected; returnVal = 1; } else { //give it more time?? returnVal = 0; } } #else // not WIN32 int err = connect(m_socket, (sockaddr *)&m_addr, sizeof(m_addr)); if(err == SOCKET_ERROR) { if (errno != EINPROGRESS && errno != EALREADY) { Disconnect(false); returnVal = -1;//failure } else { returnVal = 0;//need to wait } } else { m_status = StatusConnected; returnVal = 1;//connect success } #endif } break; case StatusConnected: { //wierd, shouldn't be trying to do this here Disconnect(true); returnVal = -1; } break; } if (returnVal == 0 && m_connectTimeout != 0 && m_connectTimer.isDone(m_connectTimeout)) { Disconnect(false); returnVal = -1; } else if (returnVal ==1/* && m_connectTimeout != 0*/) { //need to give, onConnect callback if (m_manager->m_handler) m_manager->m_handler->OnConnectRequest(this); } Release(); return returnVal; } TcpConnection::~TcpConnection() { if (m_recvBuff != NULL) { delete [] m_recvBuff; } while(m_head != NULL) { data_block *tmp = m_head; m_head = m_head->m_next; m_sendAllocator->returnBlock(tmp); } //TODO: need to notify app if are currently connected } void TcpConnection::Send(const char *data, unsigned int dataLen) { //add msg to buf int totalLen = dataLen + sizeof(int); if(m_status == StatusDisconnected) { return; } if (m_params.keepAliveDelay > 0 && m_aliveListId == m_manager->m_aliveList.m_listID) { m_aliveListId = m_manager->m_keepAliveList.m_listID; if (m_prevKeepAliveConnection != NULL) m_prevKeepAliveConnection->m_nextKeepAliveConnection = m_nextKeepAliveConnection; if (m_nextKeepAliveConnection != NULL) m_nextKeepAliveConnection->m_prevKeepAliveConnection = m_prevKeepAliveConnection; if (m_manager->m_keepAliveList.m_beginList == this) m_manager->m_keepAliveList.m_beginList = m_nextKeepAliveConnection; m_nextKeepAliveConnection = m_manager->m_aliveList.m_beginList; m_prevKeepAliveConnection = NULL; if (m_manager->m_aliveList.m_beginList != NULL) m_manager->m_aliveList.m_beginList->m_prevKeepAliveConnection = this; m_manager->m_aliveList.m_beginList = this; } data_block *work = NULL; // this connection has no send buffer. Get a block if(!m_tail) { m_head = m_sendAllocator->getBlock(); m_tail = m_head; } work = m_tail; //send message len first unsigned nLen = htonl(totalLen); unsigned lenLength = sizeof(int); unsigned lenIndex = 0; while(lenIndex < lenLength) { if ((lenLength - lenIndex) <= (work->m_totalSize - work->m_usedSize)) { //size will fit in this block memcpy(work->m_data + work->m_usedSize, (char *)(&nLen) + lenIndex, lenLength - lenIndex); work->m_usedSize += (lenLength - lenIndex); lenIndex += (lenLength - lenIndex); } else { //size will not fit in this block memcpy(work->m_data + work->m_usedSize, (char *)(&nLen) + lenIndex, work->m_totalSize - work->m_usedSize); lenIndex += work->m_totalSize - work->m_usedSize; work->m_usedSize += work->m_totalSize - work->m_usedSize; work->m_next = m_sendAllocator->getBlock(); work = work->m_next; m_tail = work; } } //now send message payload unsigned messageIndex = 0; while(messageIndex < dataLen) { if((dataLen - messageIndex) <= (work->m_totalSize - work->m_usedSize)) { // data will fit in this block memcpy(work->m_data + work->m_usedSize, data + messageIndex, (dataLen - messageIndex)); work->m_usedSize += (dataLen - messageIndex); messageIndex += (dataLen - messageIndex); } else { // data will not fit in this block. Fill this block and get another block memcpy(work->m_data + work->m_usedSize, data + messageIndex, work->m_totalSize - work->m_usedSize); messageIndex += work->m_totalSize - work->m_usedSize; work->m_usedSize += work->m_totalSize - work->m_usedSize; work->m_next = m_sendAllocator->getBlock(); work = work->m_next; m_tail = work; } } return; } void TcpConnection::Disconnect(bool notifyApplication) { AddRef(); m_status = StatusDisconnected; if (!m_wasConRemovedFromMgr) { m_manager->removeConnection(this); m_wasConRemovedFromMgr = true; } if(m_socket != INVALID_SOCKET) { #if defined(WIN32) closesocket(m_socket); #else close(m_socket); #endif m_socket = INVALID_SOCKET; } if (notifyApplication && m_handler) m_handler->OnTerminated(this); Release(); } void TcpConnection::AddRef() { m_refCount++; } void TcpConnection::Release() { if (--m_refCount == 0) { //make sure manager knows I'm gone if (m_status != StatusDisconnected) Disconnect(false); delete this; } } int TcpConnection::processIncoming() { /**< returns < 0 if fatal error and socket has been closed, =0 if read anything (full or partial message), >0 if nothing to read now, or would block so shouldn't try again immediately. */ if (m_status != StatusConnected) { //wait until connect succeeds return 1; } if (m_params.noDataTimeout > 0 && m_recvDataListId == m_manager->m_dataList.m_listID) { m_recvDataListId = m_manager->m_noDataList.m_listID; if (m_prevRecvDataConnection != NULL) m_prevRecvDataConnection->m_nextRecvDataConnection = m_nextRecvDataConnection; if (m_nextRecvDataConnection != NULL) m_nextRecvDataConnection->m_prevRecvDataConnection = m_prevRecvDataConnection; if (m_manager->m_noDataList.m_beginList == this) m_manager->m_noDataList.m_beginList = m_nextRecvDataConnection; m_nextRecvDataConnection = m_manager->m_dataList.m_beginList; m_prevRecvDataConnection = NULL; if (m_manager->m_dataList.m_beginList != NULL) m_manager->m_dataList.m_beginList->m_prevRecvDataConnection = this; m_manager->m_dataList.m_beginList = this; } int newMsg = 0; if (m_bytesRead < sizeof(int)) { //new msg newMsg = 1; //printf("socket: %d\n", m_socket); int ret = recv(m_socket, ((char *)(&m_bytesNeeded) + m_bytesRead), 4 - m_bytesRead, 0); //fprintf(stderr, "READ: %d\n", ret); if (ret == 0) { //We did a select, so there should be data. Socket was closed. Disconnect(); return -1; } else if (ret == -1) { if (translateRecvSocketEror()) { //fatal error return -1; } else { //need to wait return 1; } } else { m_bytesRead += ret; if (m_bytesRead < 4) { return 1;//need to wait } else { m_bytesNeeded = ntohl(m_bytesNeeded); //printf("m_bytesNeeded = %i\n", m_bytesNeeded); if (m_bytesNeeded == sizeof(int)) { //keepalive, ignore m_bytesRead = 0; m_bytesNeeded = 0; return 0; } else if (m_bytesNeeded < sizeof(int)) { //major protocol violation Disconnect(); return -1; } else if (m_params.maxRecvMessageSize == 0) { if (m_recvBuff!=NULL) delete [] m_recvBuff; m_recvBuff = new char[m_bytesNeeded-4]; } else if (m_params.maxRecvMessageSize != 0 && (m_bytesNeeded-4) > m_params.maxRecvMessageSize) { //error, maxRecvMeessageSize exceeded, Disconnect Disconnect(); return -1; } } } } int msgBytesRead = m_bytesRead - 4; int msgBytesNeeded = m_bytesNeeded - 4; int ret = recv(m_socket, (char *)(m_recvBuff + msgBytesRead), msgBytesNeeded - msgBytesRead, 0); if (ret == 0 && !newMsg) { //We did a select, so there should be data. Socket was closed. Disconnect(); return -1; } if (ret == -1) { if (translateRecvSocketEror()) { //fatal error return -1; } else { //need to wait return 1; } } else { m_bytesRead += ret; } if (m_bytesRead == m_bytesNeeded) { m_bytesRead = 0; m_bytesNeeded = 0; if (m_handler) { AddRef();//could get deleted during this callback m_handler->OnRoutePacket(this, (unsigned char *)m_recvBuff, msgBytesNeeded); if (m_status == StatusDisconnected) { Release(); return -1; } Release(); } //entire message received return 0; } else { return 1;//couldn't get entire msg } } int TcpConnection::processOutgoing() { /**< returns < 0 if fatal error and socket has been closed, =0 if sent data, call again immediately if want to, >0 may have sent data, but calling again would do no good because there is either no more data to send, or would block. */ if (m_status != StatusConnected) { //wait until connect succeeds return 0; } int sendError = 1; // If m_head is not null, then this connection has something to send if(m_head) { int amt = ::send(m_socket, m_head->m_data + m_head->m_sentSize, m_head->m_usedSize - m_head->m_sentSize, 0); if(amt < 0) { #ifdef WIN32 switch(WSAGetLastError()) { case WSAEWOULDBLOCK: case WSAEINTR: case WSAEINPROGRESS: case WSAEALREADY: case WSA_IO_PENDING: case WSA_NOT_ENOUGH_MEMORY: case WSATRY_AGAIN: //try again sendError = 1; break; default: //assume broken, Disconnect Disconnect(); sendError = -1; break; } #else //not WIN32 // error condition, EAGAIN is recoverable, otherwise raise an error condition. Break from loop switch(errno) { case EAGAIN: //try again sendError = 1; break; default: //assume broken, Disconnect Disconnect(); sendError = -1; break; } #endif } else if(static_cast(amt) < (m_head->m_usedSize - m_head->m_sentSize)) { // partial send: trying to do anything more now would be a waste of time. Break from loop m_head->m_sentSize += amt; sendError = 1; } else if(amt == 0) { Disconnect(); sendError = -1; // client closed connection } else { // everything was sent from this block. Return it to the pool, advance m_head. Attempt to continue // sending data_block *tmp = m_head; if(m_tail == m_head) { m_tail = m_tail->m_next; m_head = m_head->m_next; } else { m_head = m_head->m_next; } m_sendAllocator->returnBlock(tmp); sendError = 0; } } return sendError; } bool TcpConnection::translateRecvSocketEror() { /**< returns false if fatal error and socket has been closed, true if should try again. */ bool fatalError=false; #ifdef WIN32 switch(WSAGetLastError()) { case WSAENOBUFS: case WSAEINPROGRESS: case WSAEINTR: case WSAEWOULDBLOCK: case WSABASEERR: fatalError=false; break; case WSANOTINITIALISED: case WSAENETDOWN: case WSAEFAULT: case WSAENOTCONN: case WSAENETRESET: case WSAENOTSOCK: case WSAEOPNOTSUPP: case WSAESHUTDOWN: case WSAEMSGSIZE: case WSAEINVAL: case WSAECONNABORTED: case WSAETIMEDOUT: case WSAECONNRESET: default: //fatal fatalError=true; Disconnect(); break; } #else //not WIN32 switch(errno) { case EWOULDBLOCK: case EINTR: case ETIMEDOUT: case ENOBUFS: //try later fatalError=false; break; case EBADF: case ECONNRESET: case EFAULT: case EINVAL: case ENOTCONN: case ENOTSOCK: case EOPNOTSUPP: case EPIPE: case EIO: case ENOMEM: case ENOSR: default: //fatal fatalError=true; Disconnect(); break; } #endif return fatalError; } #ifdef EXTERNAL_DISTRO }; #endif