// 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 <algorithm>
+#include <climits>
#include <sstream>
#include <vector>
using namespace std;
namespace Internal {
// constants
-static const size_t DEFAULT_BUFFER_SIZE = 0x8000;
+static const size_t DEFAULT_BUFFER_SIZE = 0x10000;
} // namespace Internal
} // namespace BamTools
, m_engine(0)
, m_cachedSocketDescriptor(-1)
, m_readBuffer(DEFAULT_BUFFER_SIZE)
- , m_error(TcpSocket::UnknownSocketError)
+ , m_error(TcpSocket::NoError)
, m_state(TcpSocket::UnconnectedState)
{ }
// 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();
vector<HostAddress> addresses = hostInfo.Addresses();
if ( addresses.empty() ) {
m_error = TcpSocket::HostNotFoundError;
+ m_errorString = "no IP addresses found for host";
return false;
}
// if we get here, no connection could be made
m_error = TcpSocket::HostNotFoundError;
+ m_errorString = "could not connect to any host addresses";
return false;
}
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 {
}
// 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<int64_t>(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<int64_t>(UINT_MAX))
+ ? UINT_MAX : static_cast<size_t>(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<size_t>(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<size_t>(readBytes-1)] != '\n' );
+
+ } else
+ readBytes = ReadLine(result.Data(), result.Size());
+
+ // clean up byte buffer
+ if ( readBytes <= 0 )
+ result.Clear();
+ else
+ result.Resize(static_cast<size_t>(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) {
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;
}