X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=src%2Fapi%2Finternal%2Fio%2FTcpSocket_p.cpp;h=d3909326dbd238823cb4bcaa9bf699cbbbc104c2;hb=153d8f44a0ae6aebd0323289d961e5c00ea2b212;hp=eb9e760532262a45aa0a0aae3e5f8c19e4db675b;hpb=aace28299671f066bf2135ef28652f24fa1d8d26;p=bamtools.git diff --git a/src/api/internal/io/TcpSocket_p.cpp b/src/api/internal/io/TcpSocket_p.cpp index eb9e760..d390932 100644 --- a/src/api/internal/io/TcpSocket_p.cpp +++ b/src/api/internal/io/TcpSocket_p.cpp @@ -2,16 +2,19 @@ // TcpSocket_p.cpp (c) 2011 Derek Barnett // Marth Lab, Department of Biology, Boston College // --------------------------------------------------------------------------- -// Last modified: 25 October 2011 (DB) +// Last modified: 5 January 2012 (DB) // --------------------------------------------------------------------------- -// Provides generic TCP socket (buffered) I/O +// Provides basic TCP I/O interface // *************************************************************************** +#include "api/internal/io/ByteArray_p.h" #include "api/internal/io/TcpSocket_p.h" #include "api/internal/io/TcpSocketEngine_p.h" using namespace BamTools; using namespace BamTools::Internal; +#include +#include #include #include using namespace std; @@ -24,7 +27,7 @@ namespace BamTools { namespace Internal { // constants -static const size_t DEFAULT_BUFFER_SIZE = 0x8000; +static const size_t DEFAULT_BUFFER_SIZE = 0x10000; } // namespace Internal } // namespace BamTools @@ -40,7 +43,7 @@ TcpSocket::TcpSocket(void) , m_engine(0) , m_cachedSocketDescriptor(-1) , m_readBuffer(DEFAULT_BUFFER_SIZE) - , m_error(TcpSocket::UnknownSocketError) + , m_error(TcpSocket::NoError) , m_state(TcpSocket::UnconnectedState) { } @@ -68,14 +71,15 @@ bool TcpSocket::ConnectImpl(const HostInfo& hostInfo, // skip if we're already connected if ( m_state == TcpSocket::ConnectedState ) { m_error = TcpSocket::SocketResourceError; + m_errorString = "socket already connected"; return false; } // reset socket state - m_mode = mode; - m_hostName = hostInfo.HostName(); - m_state = TcpSocket::UnconnectedState; - m_error = TcpSocket::UnknownSocketError; + m_hostName = hostInfo.HostName(); + m_mode = mode; + m_state = TcpSocket::UnconnectedState; + m_error = TcpSocket::NoError; // m_localPort = 0; m_remotePort = 0; // m_localAddress.Clear(); @@ -86,6 +90,7 @@ bool TcpSocket::ConnectImpl(const HostInfo& hostInfo, vector addresses = hostInfo.Addresses(); if ( addresses.empty() ) { m_error = TcpSocket::HostNotFoundError; + m_errorString = "no IP addresses found for host"; return false; } @@ -124,6 +129,7 @@ bool TcpSocket::ConnectImpl(const HostInfo& hostInfo, // if we get here, no connection could be made m_error = TcpSocket::HostNotFoundError; + m_errorString = "could not connect to any host addresses"; return false; } @@ -171,6 +177,9 @@ void TcpSocket::DisconnectFromHost(void) { m_remoteAddress.Clear(); m_hostName.clear(); m_cachedSocketDescriptor = -1; + + // for future, make sure there's outgoing data that needs to be flushed + m_readBuffer.Clear(); } TcpSocket::SocketError TcpSocket::GetError(void) const { @@ -235,71 +244,137 @@ int64_t TcpSocket::Read(char* data, const unsigned int numBytes) { } // fetch data from socket, return 0 for success, -1 for failure - // since this should be called in a loop, we'll pull the actual bytes on next iteration - return ( ReadFromSocket() ? 0 : -1 ); + // since this should be called in a loop, + // we'll pull the actual bytes from the buffer on next iteration + const int64_t socketBytesRead = ReadFromSocket(); + if ( socketBytesRead < 0 ) { + // TODO: set error string/state ? + return -1; + } + + // we should have data now in buffer, try to fetch requested amount + // if nothing in buffer, we will return 0 bytes read (signals EOF reached) + const size_t numBytesRead = m_readBuffer.Read(data, numBytes); + return static_cast(numBytesRead); } -bool TcpSocket::ReadFromSocket(void) { +int64_t TcpSocket::ReadFromSocket(void) { + + // check for any socket engine errors + if ( !m_engine->IsValid() ) { + m_errorString = "TcpSocket::ReadFromSocket - socket disconnected"; + ResetSocketEngine(); + return -1; + } // wait for ready read bool timedOut; - bool isReadyRead = m_engine->WaitForRead(5000, &timedOut); + const bool isReadyRead = m_engine->WaitForRead(5000, &timedOut); // if not ready if ( !isReadyRead ) { // if we simply timed out if ( timedOut ) { - // TODO: set error string - return false; + // TODO: get add'l error info from engine ? + m_errorString = "TcpSocket::ReadFromSocket - timed out waiting for ready read"; } - // otherwise, there was an error + // otherwise, there was some other error else { - // TODO: set error string - return false; + // TODO: get add'l error info from engine ? + m_errorString = "TcpSocket::ReadFromSocket - encountered error while waiting for ready read"; } + + // return failure + return -1; } - // ######################################################################### - // clean this up - smells funky, but it's a key step so it has to be right - // ######################################################################### // get number of bytes available from socket - // (if 0, still try to read some data so we don't trigger any OS event behavior - // that respond to repeated access to a remote closed socket) - int64_t bytesToRead = m_engine->NumBytesAvailable(); - if ( bytesToRead < 0 ) - return false; - else if ( bytesToRead == 0 ) - bytesToRead = 4096; + const int64_t bytesToRead = m_engine->NumBytesAvailable(); + if ( bytesToRead < 0 ) { + // TODO: get add'l error info from engine ? + m_errorString = "TcpSocket::ReadFromSocket - encountered error while determining numBytesAvailable"; + return -1; + } // make space in buffer & read from socket char* buffer = m_readBuffer.Reserve(bytesToRead); - int64_t numBytesRead = m_engine->Read(buffer, bytesToRead); - - // (Qt uses -2 for no data, not error) - // squeeze buffer back down & return success - if ( numBytesRead == -2 ) { - m_readBuffer.Chop(bytesToRead); - return true; + const int64_t numBytesRead = m_engine->Read(buffer, bytesToRead); + if ( numBytesRead == -1 ) { + // TODO: get add'l error info from engine ? + m_errorString = "TcpSocket::ReadFromSocket - encountered error while reading bytes"; } - // ######################################################################### - // check for any socket engine errors - if ( !m_engine->IsValid() ) { - // TODO: set error string - ResetSocketEngine(); - return false; - } + // return number of bytes actually read + return numBytesRead; +} - // return success - return true; +string TcpSocket::ReadLine(int64_t max) { + + // prep result byte buffer + ByteArray result; + size_t bufferMax = ((max > static_cast(UINT_MAX)) + ? UINT_MAX : static_cast(max)); + result.Resize(bufferMax); + + // read data + int64_t readBytes(0); + if ( result.Size() == 0 ) { + + if ( bufferMax == 0 ) + bufferMax = UINT_MAX; + + result.Resize(1); + + int64_t readResult; + do { + result.Resize( static_cast(min(bufferMax, result.Size() + DEFAULT_BUFFER_SIZE)) ); + readResult = ReadLine(result.Data()+readBytes, result.Size()-readBytes); + if ( readResult > 0 || readBytes == 0 ) + readBytes += readResult; + } while ( readResult == DEFAULT_BUFFER_SIZE && result[static_cast(readBytes-1)] != '\n' ); + + } else + readBytes = ReadLine(result.Data(), result.Size()); + + // clean up byte buffer + if ( readBytes <= 0 ) + result.Clear(); + else + result.Resize(static_cast(readBytes)); + + // return byte buffer as string + return string( result.ConstData(), result.Size() ); } -string TcpSocket::ReadLine(void) { - if ( m_readBuffer.CanReadLine() ) - return m_readBuffer.ReadLine(); - return string(); +int64_t TcpSocket::ReadLine(char* dest, size_t max) { + + // wait for buffer to contain line contents + if ( !WaitForReadLine() ) { + m_errorString = "TcpSocket::ReadLine - error waiting for read line"; + return -1; + } + + // leave room for null term + if ( max < 2 ) + return -1; + --max; + + // read from buffer, handle newlines + int64_t readSoFar = m_readBuffer.ReadLine(dest, max); + if ( readSoFar && dest[readSoFar-1] == '\n' ) { + + // adjust for windows-style '\r\n' + if ( readSoFar > 1 && dest[readSoFar-2] == '\r') { + --readSoFar; + dest[readSoFar-1] = '\n'; + } + } + + // null terminate & return number of bytes read + dest[readSoFar] = '\0'; + return readSoFar; } void TcpSocket::ResetSocketEngine(void) { @@ -316,27 +391,40 @@ void TcpSocket::ResetSocketEngine(void) { m_cachedSocketDescriptor = -1; } +bool TcpSocket::WaitForReadLine(void) { + + // wait until we can read a line (will return immediately if already capable) + while ( !CanReadLine() ) { + if ( !ReadFromSocket() ) + return false; + } + + // if we get here, success + return true; +} + int64_t TcpSocket::Write(const char* data, const unsigned int numBytes) { // single-shot attempt at write (not buffered, just try to shove the data through socket) // this method purely exists to send 'small' HTTP requests/FTP commands from client to server - int64_t bytesWritten(0); - // wait for our socket to be write-able bool timedOut; - bool isReadyWrite = m_engine->WaitForWrite(3000, &timedOut); + const bool isReadyWrite = m_engine->WaitForWrite(3000, &timedOut); + + // if ready, return number of bytes written if ( isReadyWrite ) - bytesWritten = m_engine->Write(data, numBytes); + return m_engine->Write(data, numBytes); + + // otherwise, socket not ready for writing + // set error string depending on reason & return failure + if ( !timedOut ) { + // TODO: get add'l error info from engine ?? + m_errorString = "TcpSocket::Write - timed out waiting for ready-write"; + } else { - // timeout is OK (with current setup), we'll just return 0 & try again - // but we need to report if engine encountered some other error - if ( !timedOut ) { - // TODO: set error string - bytesWritten = -1; - } + // TODO: get add'l error info from engine ?? + m_errorString = "TcpSocket::Write - error encountered while waiting for ready-write"; } - - // return actual number of bytes written to socket - return bytesWritten; + return -1; }