1 // ***************************************************************************
2 // TcpSocket_p.cpp (c) 2011 Derek Barnett
3 // Marth Lab, Department of Biology, Boston College
4 // ---------------------------------------------------------------------------
5 // Last modified: 25 October 2011 (DB)
6 // ---------------------------------------------------------------------------
7 // Provides generic TCP socket (buffered) I/O
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;
16 #include <iostream> // debug
23 // ------------------------------------
24 // static utility methods & constants
25 // ------------------------------------
31 static const size_t DEFAULT_BUFFER_SIZE = 0x4000;
33 } // namespace Internal
34 } // namespace BamTools
36 // --------------------------
37 // TcpSocket implementation
38 // --------------------------
40 TcpSocket::TcpSocket(void)
41 : m_mode(IBamIODevice::NotOpen)
45 , m_cachedSocketDescriptor(-1)
46 , m_readBuffer(DEFAULT_BUFFER_SIZE)
47 , m_error(TcpSocket::UnknownSocketError)
48 , m_state(TcpSocket::UnconnectedState)
51 TcpSocket::~TcpSocket(void) {
52 if ( m_state == TcpSocket::ConnectedState )
56 size_t TcpSocket::BufferBytesAvailable(void) const {
57 return m_readBuffer.Size();
60 bool TcpSocket::CanReadLine(void) const {
61 return m_readBuffer.CanReadLine();
64 void TcpSocket::ClearBuffer(void) {
68 bool TcpSocket::ConnectImpl(const HostInfo& hostInfo,
69 const std::string& port,
70 IBamIODevice::OpenMode mode)
72 // skip if we're already connected
73 if ( m_state == TcpSocket::ConnectedState ) {
74 m_error = TcpSocket::SocketResourceError;
75 m_errorString = "socket already connected";
80 m_hostName = hostInfo.HostName();
82 m_state = TcpSocket::UnconnectedState;
83 m_error = TcpSocket::UnknownSocketError;
86 // m_localAddress.Clear();
87 m_remoteAddress.Clear();
90 // fetch candidate addresses for requested host
91 vector<HostAddress> addresses = hostInfo.Addresses();
92 if ( addresses.empty() ) {
93 m_error = TcpSocket::HostNotFoundError;
94 m_errorString = "no IP addresses found for host";
98 // convert port string to integer
99 stringstream ss(port);
100 uint16_t portNumber(0);
103 // iterate through adddresses
104 vector<HostAddress>::const_iterator addrIter = addresses.begin();
105 vector<HostAddress>::const_iterator addrEnd = addresses.end();
106 for ( ; addrIter != addrEnd; ++addrIter) {
107 const HostAddress& addr = (*addrIter);
109 // try to initialize socket engine with this address
110 if ( !InitializeSocketEngine(addr.GetProtocol()) ) {
111 // failure to initialize is OK here
112 // we'll just try the next available address
116 // attempt actual connection
117 if ( m_engine->Connect(addr, portNumber) ) {
119 // if connection successful, update our state & return true
121 // m_localAddress = m_engine->GetLocalAddress();
122 // m_localPort = m_engine->GetLocalPort();
123 m_remoteAddress = m_engine->GetRemoteAddress();
124 m_remotePort = m_engine->GetRemotePort();
125 m_cachedSocketDescriptor = m_engine->GetSocketDescriptor();
126 m_state = TcpSocket::ConnectedState;
131 // if we get here, no connection could be made
132 m_error = TcpSocket::HostNotFoundError;
133 m_errorString = "could not connect to any host addresses";
137 bool TcpSocket::ConnectToHost(const string& hostName,
139 IBamIODevice::OpenMode mode)
143 return ConnectToHost(hostName, ss.str(), mode);
147 bool TcpSocket::ConnectToHost(const string& hostName,
149 IBamIODevice::OpenMode mode)
151 // create new address object with requested host name
152 HostAddress hostAddress;
153 hostAddress.SetAddress(hostName);
156 // if host name was IP address ("x.x.x.x" or IPv6 format)
157 // otherwise host name was 'plain-text' ("www.foo.bar")
158 // we need to look up IP address(es)
159 if ( hostAddress.HasIPAddress() )
160 info.SetAddresses( vector<HostAddress>(1, hostAddress) );
162 info = HostInfo::Lookup(hostName, port);
164 // attempt connection on requested port
165 return ConnectImpl(info, port, mode);
168 void TcpSocket::DisconnectFromHost(void) {
170 // close socket engine & delete
171 if ( m_state == TcpSocket::ConnectedState )
174 // reset connection state
177 // m_localAddress.Clear();
178 m_remoteAddress.Clear();
180 m_cachedSocketDescriptor = -1;
182 // for future, make sure there's outgoing data that needs to be flushed
183 m_readBuffer.Clear();
186 TcpSocket::SocketError TcpSocket::GetError(void) const {
190 std::string TcpSocket::GetErrorString(void) const {
191 return m_errorString;
194 std::string TcpSocket::GetHostName(void) const {
198 //HostAddress TcpSocket::GetLocalAddress(void) const {
199 // return m_localAddress;
202 //uint16_t TcpSocket::GetLocalPort(void) const {
203 // return m_localPort;
206 HostAddress TcpSocket::GetRemoteAddress(void) const {
207 return m_remoteAddress;
210 uint16_t TcpSocket::GetRemotePort(void) const {
214 TcpSocket::SocketState TcpSocket::GetState(void) const {
218 bool TcpSocket::InitializeSocketEngine(HostAddress::NetworkProtocol protocol) {
220 m_engine = new TcpSocketEngine;
221 return m_engine->Initialize(protocol);
224 bool TcpSocket::IsConnected(void) const {
227 return ( m_engine->IsValid() && (m_state == TcpSocket::ConnectedState) );
230 // may be read in a look until desired data amount has been read
231 // returns: number of bytes read, or -1 if error
232 int64_t TcpSocket::Read(char* data, const unsigned int numBytes) {
234 // if we have data in buffer, just return it
235 if ( !m_readBuffer.IsEmpty() ) {
236 const size_t bytesRead = m_readBuffer.Read(data, numBytes);
237 return static_cast<int64_t>(bytesRead);
240 // otherwise, we'll need to fetch data from socket
241 // first make sure we have a valid socket engine
242 if ( m_engine == 0 ) {
243 // TODO: set error string/state?
247 // fetch data from socket, return 0 for success, -1 for failure
248 // since this should be called in a loop, we'll pull the actual bytes on next iteration
249 return ( ReadFromSocket() ? 0 : -1 );
252 bool TcpSocket::ReadFromSocket(void) {
254 // check for any socket engine errors
255 if ( !m_engine->IsValid() ) {
256 m_errorString = "TcpSocket::ReadFromSocket - socket disconnected";
261 // wait for ready read
263 bool isReadyRead = m_engine->WaitForRead(5000, &timedOut);
266 if ( !isReadyRead ) {
268 // if we simply timed out
270 m_errorString = "TcpSocket::ReadFromSocket - timed out waiting for ready read";
271 // get error from engine ?
275 // otherwise, there was an error
277 m_errorString = "TcpSocket::ReadFromSocket - encountered error while waiting for ready read";
278 // get error from engine ?
283 // #########################################################################
284 // clean this up - smells funky, but it's a key step so it has to be right
285 // #########################################################################
287 // get number of bytes available from socket
288 // (if 0, still try to read some data so we don't trigger any OS event behavior
289 // that respond to repeated access to a remote closed socket)
290 int64_t bytesToRead = m_engine->NumBytesAvailable();
291 if ( bytesToRead < 0 ) {
292 m_errorString = "TcpSocket::ReadFromSocket - encountered error while determining numBytesAvailable";
293 // get error from engine ?
296 else if ( bytesToRead == 0 )
299 // make space in buffer & read from socket
300 char* buffer = m_readBuffer.Reserve(bytesToRead);
301 int64_t numBytesRead = m_engine->Read(buffer, bytesToRead);
303 // if error while reading
304 if ( numBytesRead == -1 ) {
305 m_errorString = "TcpSocket::ReadFromSocket - encountered error while reading bytes";
306 // get error from engine ?
310 // handle special case (no data, but not error)
311 if ( numBytesRead == -2 )
312 m_readBuffer.Chop(bytesToRead);
318 string TcpSocket::ReadLine(int64_t max) {
320 // prep result byte buffer
323 size_t bufferMax = ((max > static_cast<int64_t>(string::npos)) ? string::npos : static_cast<size_t>(max));
324 result.Resize(bufferMax);
327 int64_t readBytes(0);
328 if ( result.Size() == 0 ) {
330 if ( bufferMax == 0 )
331 bufferMax = string::npos;
337 result.Resize( static_cast<size_t>(std::min(bufferMax, result.Size() + DEFAULT_BUFFER_SIZE)) );
338 readResult = ReadLine(result.Data()+readBytes, result.Size()-readBytes);
339 if ( readResult > 0 || readBytes == 0 )
340 readBytes += readResult;
341 } while ( readResult == DEFAULT_BUFFER_SIZE && result[static_cast<size_t>(readBytes-1)] != '\n' );
344 readBytes = ReadLine(result.Data(), result.Size());
346 // clean up byte buffer
347 if ( readBytes <= 0 )
350 result.Resize(static_cast<size_t>(readBytes));
352 // return byte buffer as string
353 return string( result.ConstData(), result.Size() );
356 int64_t TcpSocket::ReadLine(char* dest, size_t max) {
358 // wait for buffer to contain line contents
359 if ( !WaitForReadLine() ) {
360 m_errorString = "TcpSocket::ReadLine - error waiting for read line";
364 // leave room for null term
369 // read from buffer, handle newlines
370 int64_t readSoFar = m_readBuffer.ReadLine(dest, max);
371 if ( readSoFar && dest[readSoFar-1] == '\n' ) {
373 // adjust for windows-style '\r\n'
374 if ( readSoFar > 1 && dest[readSoFar-2] == '\r') {
376 dest[readSoFar-1] = '\n';
380 // null terminate & return number of bytes read
381 dest[readSoFar] = '\0';
385 void TcpSocket::ResetSocketEngine(void) {
387 // shut down socket engine
394 // reset our state & cached socket handle
395 m_state = TcpSocket::UnconnectedState;
396 m_cachedSocketDescriptor = -1;
399 bool TcpSocket::WaitForReadLine(void) {
401 // wait until we can read a line (will return immediately if already capable)
402 while ( !CanReadLine() ) {
403 if ( !ReadFromSocket() )
407 // if we get here, success
411 int64_t TcpSocket::Write(const char* data, const unsigned int numBytes) {
413 // single-shot attempt at write (not buffered, just try to shove the data through socket)
414 // this method purely exists to send 'small' HTTP requests/FTP commands from client to server
416 int64_t bytesWritten(0);
418 // wait for our socket to be write-able
420 bool isReadyWrite = m_engine->WaitForWrite(3000, &timedOut);
422 bytesWritten = m_engine->Write(data, numBytes);
424 // timeout is OK (with current setup), we'll just return 0 & try again
425 // but we need to report if engine encountered some other error
427 // TODO: set error string
432 // return actual number of bytes written to socket