1 // ***************************************************************************
2 // TcpSocket_p.cpp (c) 2011 Derek Barnett
3 // Marth Lab, Department of Biology, Boston College
4 // ---------------------------------------------------------------------------
5 // Last modified: 8 December 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,
247 // we'll pull the actual bytes from the buffer on next iteration
248 const int64_t socketBytesRead = ReadFromSocket();
249 if ( socketBytesRead < 0 ) {
250 // TODO: set error string/state ?
254 // we should have data now in buffer, try to fetch requested amount
255 // if nothing in buffer, we will return 0 bytes read (signals EOF reached)
256 const size_t numBytesRead = m_readBuffer.Read(data, numBytes);
257 return static_cast<int64_t>(numBytesRead);
260 int64_t TcpSocket::ReadFromSocket(void) {
262 // check for any socket engine errors
263 if ( !m_engine->IsValid() ) {
264 m_errorString = "TcpSocket::ReadFromSocket - socket disconnected";
269 // wait for ready read
271 const bool isReadyRead = m_engine->WaitForRead(5000, &timedOut);
274 if ( !isReadyRead ) {
276 // if we simply timed out
278 // TODO: get add'l error info from engine ?
279 m_errorString = "TcpSocket::ReadFromSocket - timed out waiting for ready read";
282 // otherwise, there was some other error
284 // TODO: get add'l error info from engine ?
285 m_errorString = "TcpSocket::ReadFromSocket - encountered error while waiting for ready read";
292 // get number of bytes available from socket
293 const int64_t bytesToRead = m_engine->NumBytesAvailable();
294 if ( bytesToRead < 0 ) {
295 // TODO: get add'l error info from engine ?
296 m_errorString = "TcpSocket::ReadFromSocket - encountered error while determining numBytesAvailable";
300 // make space in buffer & read from socket
301 char* buffer = m_readBuffer.Reserve(bytesToRead);
302 const int64_t numBytesRead = m_engine->Read(buffer, bytesToRead);
303 if ( numBytesRead == -1 ) {
304 // TODO: get add'l error info from engine ?
305 m_errorString = "TcpSocket::ReadFromSocket - encountered error while reading bytes";
308 // return number of bytes actually read
312 string TcpSocket::ReadLine(int64_t max) {
314 // prep result byte buffer
317 size_t bufferMax = ((max > static_cast<int64_t>(string::npos)) ? string::npos : static_cast<size_t>(max));
318 result.Resize(bufferMax);
321 int64_t readBytes(0);
322 if ( result.Size() == 0 ) {
324 if ( bufferMax == 0 )
325 bufferMax = string::npos;
331 result.Resize( static_cast<size_t>(min(bufferMax, result.Size() + DEFAULT_BUFFER_SIZE)) );
332 readResult = ReadLine(result.Data()+readBytes, result.Size()-readBytes);
333 if ( readResult > 0 || readBytes == 0 )
334 readBytes += readResult;
335 } while ( readResult == DEFAULT_BUFFER_SIZE && result[static_cast<size_t>(readBytes-1)] != '\n' );
338 readBytes = ReadLine(result.Data(), result.Size());
340 // clean up byte buffer
341 if ( readBytes <= 0 )
344 result.Resize(static_cast<size_t>(readBytes));
346 // return byte buffer as string
347 return string( result.ConstData(), result.Size() );
350 int64_t TcpSocket::ReadLine(char* dest, size_t max) {
352 // wait for buffer to contain line contents
353 if ( !WaitForReadLine() ) {
354 m_errorString = "TcpSocket::ReadLine - error waiting for read line";
358 // leave room for null term
363 // read from buffer, handle newlines
364 int64_t readSoFar = m_readBuffer.ReadLine(dest, max);
365 if ( readSoFar && dest[readSoFar-1] == '\n' ) {
367 // adjust for windows-style '\r\n'
368 if ( readSoFar > 1 && dest[readSoFar-2] == '\r') {
370 dest[readSoFar-1] = '\n';
374 // null terminate & return number of bytes read
375 dest[readSoFar] = '\0';
379 void TcpSocket::ResetSocketEngine(void) {
381 // shut down socket engine
388 // reset our state & cached socket handle
389 m_state = TcpSocket::UnconnectedState;
390 m_cachedSocketDescriptor = -1;
393 bool TcpSocket::WaitForReadLine(void) {
395 // wait until we can read a line (will return immediately if already capable)
396 while ( !CanReadLine() ) {
397 if ( !ReadFromSocket() )
401 // if we get here, success
405 int64_t TcpSocket::Write(const char* data, const unsigned int numBytes) {
407 // single-shot attempt at write (not buffered, just try to shove the data through socket)
408 // this method purely exists to send 'small' HTTP requests/FTP commands from client to server
410 // wait for our socket to be write-able
412 const bool isReadyWrite = m_engine->WaitForWrite(3000, &timedOut);
414 // if ready, return number of bytes written
416 return m_engine->Write(data, numBytes);
418 // otherwise, socket not ready for writing
419 // set error string depending on reason & return failure
421 // TODO: get add'l error info from engine ??
422 m_errorString = "TcpSocket::Write - timed out waiting for ready-write";
425 // TODO: get add'l error info from engine ??
426 m_errorString = "TcpSocket::Write - error encountered while waiting for ready-write";