1 // ***************************************************************************
2 // TcpSocket_p.cpp (c) 2011 Derek Barnett
3 // Marth Lab, Department of Biology, Boston College
4 // ---------------------------------------------------------------------------
5 // Last modified: 10 November 2011 (DB)
6 // ---------------------------------------------------------------------------
7 // Provides basic TCP I/O interface
8 // ***************************************************************************
10 #include "api/internal/io/ByteArray_p.h"
11 #include "api/internal/io/TcpSocket_p.h"
12 #include "api/internal/io/TcpSocketEngine_p.h"
13 using namespace BamTools;
14 using namespace BamTools::Internal;
21 // ------------------------------------
22 // static utility methods & constants
23 // ------------------------------------
29 static const size_t DEFAULT_BUFFER_SIZE = 0x4000;
31 } // namespace Internal
32 } // namespace BamTools
34 // --------------------------
35 // TcpSocket implementation
36 // --------------------------
38 TcpSocket::TcpSocket(void)
39 : m_mode(IBamIODevice::NotOpen)
43 , m_cachedSocketDescriptor(-1)
44 , m_readBuffer(DEFAULT_BUFFER_SIZE)
45 , m_error(TcpSocket::UnknownSocketError)
46 , m_state(TcpSocket::UnconnectedState)
49 TcpSocket::~TcpSocket(void) {
50 if ( m_state == TcpSocket::ConnectedState )
54 size_t TcpSocket::BufferBytesAvailable(void) const {
55 return m_readBuffer.Size();
58 bool TcpSocket::CanReadLine(void) const {
59 return m_readBuffer.CanReadLine();
62 void TcpSocket::ClearBuffer(void) {
66 bool TcpSocket::ConnectImpl(const HostInfo& hostInfo,
67 const std::string& port,
68 IBamIODevice::OpenMode mode)
70 // skip if we're already connected
71 if ( m_state == TcpSocket::ConnectedState ) {
72 m_error = TcpSocket::SocketResourceError;
73 m_errorString = "socket already connected";
78 m_hostName = hostInfo.HostName();
80 m_state = TcpSocket::UnconnectedState;
81 m_error = TcpSocket::UnknownSocketError;
84 // m_localAddress.Clear();
85 m_remoteAddress.Clear();
88 // fetch candidate addresses for requested host
89 vector<HostAddress> addresses = hostInfo.Addresses();
90 if ( addresses.empty() ) {
91 m_error = TcpSocket::HostNotFoundError;
92 m_errorString = "no IP addresses found for host";
96 // convert port string to integer
97 stringstream ss(port);
98 uint16_t portNumber(0);
101 // iterate through adddresses
102 vector<HostAddress>::const_iterator addrIter = addresses.begin();
103 vector<HostAddress>::const_iterator addrEnd = addresses.end();
104 for ( ; addrIter != addrEnd; ++addrIter) {
105 const HostAddress& addr = (*addrIter);
107 // try to initialize socket engine with this address
108 if ( !InitializeSocketEngine(addr.GetProtocol()) ) {
109 // failure to initialize is OK here
110 // we'll just try the next available address
114 // attempt actual connection
115 if ( m_engine->Connect(addr, portNumber) ) {
117 // if connection successful, update our state & return true
119 // m_localAddress = m_engine->GetLocalAddress();
120 // m_localPort = m_engine->GetLocalPort();
121 m_remoteAddress = m_engine->GetRemoteAddress();
122 m_remotePort = m_engine->GetRemotePort();
123 m_cachedSocketDescriptor = m_engine->GetSocketDescriptor();
124 m_state = TcpSocket::ConnectedState;
129 // if we get here, no connection could be made
130 m_error = TcpSocket::HostNotFoundError;
131 m_errorString = "could not connect to any host addresses";
135 bool TcpSocket::ConnectToHost(const string& hostName,
137 IBamIODevice::OpenMode mode)
141 return ConnectToHost(hostName, ss.str(), mode);
145 bool TcpSocket::ConnectToHost(const string& hostName,
147 IBamIODevice::OpenMode mode)
149 // create new address object with requested host name
150 HostAddress hostAddress;
151 hostAddress.SetAddress(hostName);
154 // if host name was IP address ("x.x.x.x" or IPv6 format)
155 // otherwise host name was 'plain-text' ("www.foo.bar")
156 // we need to look up IP address(es)
157 if ( hostAddress.HasIPAddress() )
158 info.SetAddresses( vector<HostAddress>(1, hostAddress) );
160 info = HostInfo::Lookup(hostName, port);
162 // attempt connection on requested port
163 return ConnectImpl(info, port, mode);
166 void TcpSocket::DisconnectFromHost(void) {
168 // close socket engine & delete
169 if ( m_state == TcpSocket::ConnectedState )
172 // reset connection state
175 // m_localAddress.Clear();
176 m_remoteAddress.Clear();
178 m_cachedSocketDescriptor = -1;
180 // for future, make sure there's outgoing data that needs to be flushed
181 m_readBuffer.Clear();
184 TcpSocket::SocketError TcpSocket::GetError(void) const {
188 std::string TcpSocket::GetErrorString(void) const {
189 return m_errorString;
192 std::string TcpSocket::GetHostName(void) const {
196 //HostAddress TcpSocket::GetLocalAddress(void) const {
197 // return m_localAddress;
200 //uint16_t TcpSocket::GetLocalPort(void) const {
201 // return m_localPort;
204 HostAddress TcpSocket::GetRemoteAddress(void) const {
205 return m_remoteAddress;
208 uint16_t TcpSocket::GetRemotePort(void) const {
212 TcpSocket::SocketState TcpSocket::GetState(void) const {
216 bool TcpSocket::InitializeSocketEngine(HostAddress::NetworkProtocol protocol) {
218 m_engine = new TcpSocketEngine;
219 return m_engine->Initialize(protocol);
222 bool TcpSocket::IsConnected(void) const {
225 return ( m_engine->IsValid() && (m_state == TcpSocket::ConnectedState) );
228 // may be read in a look until desired data amount has been read
229 // returns: number of bytes read, or -1 if error
230 int64_t TcpSocket::Read(char* data, const unsigned int numBytes) {
232 // if we have data in buffer, just return it
233 if ( !m_readBuffer.IsEmpty() ) {
234 const size_t bytesRead = m_readBuffer.Read(data, numBytes);
235 return static_cast<int64_t>(bytesRead);
238 // otherwise, we'll need to fetch data from socket
239 // first make sure we have a valid socket engine
240 if ( m_engine == 0 ) {
241 // TODO: set error string/state?
245 // fetch data from socket, return 0 for success, -1 for failure
246 // since this should be called in a loop, we'll pull the actual bytes on next iteration
247 return ( ReadFromSocket() ? 0 : -1 );
250 bool TcpSocket::ReadFromSocket(void) {
252 // check for any socket engine errors
253 if ( !m_engine->IsValid() ) {
254 m_errorString = "TcpSocket::ReadFromSocket - socket disconnected";
259 // wait for ready read
261 bool isReadyRead = m_engine->WaitForRead(5000, &timedOut);
264 if ( !isReadyRead ) {
266 // if we simply timed out
268 m_errorString = "TcpSocket::ReadFromSocket - timed out waiting for ready read";
269 // get error from engine ?
273 // otherwise, there was an error
275 m_errorString = "TcpSocket::ReadFromSocket - encountered error while waiting for ready read";
276 // get error from engine ?
281 // #########################################################################
282 // clean this up - smells funky, but it's a key step so it has to be right
283 // #########################################################################
285 // get number of bytes available from socket
286 // (if 0, still try to read some data so we don't trigger any OS event behavior
287 // that respond to repeated access to a remote closed socket)
288 int64_t bytesToRead = m_engine->NumBytesAvailable();
289 if ( bytesToRead < 0 ) {
290 m_errorString = "TcpSocket::ReadFromSocket - encountered error while determining numBytesAvailable";
291 // get error from engine ?
294 else if ( bytesToRead == 0 )
297 // make space in buffer & read from socket
298 char* buffer = m_readBuffer.Reserve(bytesToRead);
299 int64_t numBytesRead = m_engine->Read(buffer, bytesToRead);
301 // if error while reading
302 if ( numBytesRead == -1 ) {
303 m_errorString = "TcpSocket::ReadFromSocket - encountered error while reading bytes";
304 // get error from engine ?
308 // handle special case (no data, but not error)
309 if ( numBytesRead == -2 )
310 m_readBuffer.Chop(bytesToRead);
316 string TcpSocket::ReadLine(int64_t max) {
318 // prep result byte buffer
321 size_t bufferMax = ((max > static_cast<int64_t>(string::npos)) ? string::npos : static_cast<size_t>(max));
322 result.Resize(bufferMax);
325 int64_t readBytes(0);
326 if ( result.Size() == 0 ) {
328 if ( bufferMax == 0 )
329 bufferMax = string::npos;
335 result.Resize( static_cast<size_t>(std::min(bufferMax, result.Size() + DEFAULT_BUFFER_SIZE)) );
336 readResult = ReadLine(result.Data()+readBytes, result.Size()-readBytes);
337 if ( readResult > 0 || readBytes == 0 )
338 readBytes += readResult;
339 } while ( readResult == DEFAULT_BUFFER_SIZE && result[static_cast<size_t>(readBytes-1)] != '\n' );
342 readBytes = ReadLine(result.Data(), result.Size());
344 // clean up byte buffer
345 if ( readBytes <= 0 )
348 result.Resize(static_cast<size_t>(readBytes));
350 // return byte buffer as string
351 return string( result.ConstData(), result.Size() );
354 int64_t TcpSocket::ReadLine(char* dest, size_t max) {
356 // wait for buffer to contain line contents
357 if ( !WaitForReadLine() ) {
358 m_errorString = "TcpSocket::ReadLine - error waiting for read line";
362 // leave room for null term
367 // read from buffer, handle newlines
368 int64_t readSoFar = m_readBuffer.ReadLine(dest, max);
369 if ( readSoFar && dest[readSoFar-1] == '\n' ) {
371 // adjust for windows-style '\r\n'
372 if ( readSoFar > 1 && dest[readSoFar-2] == '\r') {
374 dest[readSoFar-1] = '\n';
378 // null terminate & return number of bytes read
379 dest[readSoFar] = '\0';
383 void TcpSocket::ResetSocketEngine(void) {
385 // shut down socket engine
392 // reset our state & cached socket handle
393 m_state = TcpSocket::UnconnectedState;
394 m_cachedSocketDescriptor = -1;
397 bool TcpSocket::WaitForReadLine(void) {
399 // wait until we can read a line (will return immediately if already capable)
400 while ( !CanReadLine() ) {
401 if ( !ReadFromSocket() )
405 // if we get here, success
409 int64_t TcpSocket::Write(const char* data, const unsigned int numBytes) {
411 // single-shot attempt at write (not buffered, just try to shove the data through socket)
412 // this method purely exists to send 'small' HTTP requests/FTP commands from client to server
414 int64_t bytesWritten(0);
416 // wait for our socket to be write-able
418 bool isReadyWrite = m_engine->WaitForWrite(3000, &timedOut);
420 bytesWritten = m_engine->Write(data, numBytes);
422 // timeout is OK (with current setup), we'll just return 0 & try again
423 // but we need to report if engine encountered some other error
425 // TODO: set error string
430 // return actual number of bytes written to socket