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/TcpSocket_p.h"
11 #include "api/internal/io/TcpSocketEngine_p.h"
12 using namespace BamTools;
13 using namespace BamTools::Internal;
19 // ------------------------------------
20 // static utility methods & constants
21 // ------------------------------------
27 static const size_t DEFAULT_BUFFER_SIZE = 0x8000;
29 } // namespace Internal
30 } // namespace BamTools
32 // --------------------------
33 // TcpSocket implementation
34 // --------------------------
36 TcpSocket::TcpSocket(void)
37 : m_mode(IBamIODevice::NotOpen)
41 , m_cachedSocketDescriptor(-1)
42 , m_readBuffer(DEFAULT_BUFFER_SIZE)
43 , m_error(TcpSocket::UnknownSocketError)
44 , m_state(TcpSocket::UnconnectedState)
47 TcpSocket::~TcpSocket(void) {
48 if ( m_state == TcpSocket::ConnectedState )
52 size_t TcpSocket::BufferBytesAvailable(void) const {
53 return m_readBuffer.Size();
56 bool TcpSocket::CanReadLine(void) const {
57 return m_readBuffer.CanReadLine();
60 void TcpSocket::ClearBuffer(void) {
64 bool TcpSocket::ConnectImpl(const HostInfo& hostInfo,
65 const std::string& port,
66 IBamIODevice::OpenMode mode)
68 // skip if we're already connected
69 if ( m_state == TcpSocket::ConnectedState ) {
70 m_error = TcpSocket::SocketResourceError;
76 m_hostName = hostInfo.HostName();
77 m_state = TcpSocket::UnconnectedState;
78 m_error = TcpSocket::UnknownSocketError;
81 // m_localAddress.Clear();
82 m_remoteAddress.Clear();
85 // fetch candidate addresses for requested host
86 vector<HostAddress> addresses = hostInfo.Addresses();
87 if ( addresses.empty() ) {
88 m_error = TcpSocket::HostNotFoundError;
92 // convert port string to integer
93 stringstream ss(port);
94 uint16_t portNumber(0);
97 // iterate through adddresses
98 vector<HostAddress>::const_iterator addrIter = addresses.begin();
99 vector<HostAddress>::const_iterator addrEnd = addresses.end();
100 for ( ; addrIter != addrEnd; ++addrIter) {
101 const HostAddress& addr = (*addrIter);
103 // try to initialize socket engine with this address
104 if ( !InitializeSocketEngine(addr.GetProtocol()) ) {
105 // failure to initialize is OK here
106 // we'll just try the next available address
110 // attempt actual connection
111 if ( m_engine->Connect(addr, portNumber) ) {
113 // if connection successful, update our state & return true
115 // m_localAddress = m_engine->GetLocalAddress();
116 // m_localPort = m_engine->GetLocalPort();
117 m_remoteAddress = m_engine->GetRemoteAddress();
118 m_remotePort = m_engine->GetRemotePort();
119 m_cachedSocketDescriptor = m_engine->GetSocketDescriptor();
120 m_state = TcpSocket::ConnectedState;
125 // if we get here, no connection could be made
126 m_error = TcpSocket::HostNotFoundError;
130 bool TcpSocket::ConnectToHost(const string& hostName,
132 IBamIODevice::OpenMode mode)
136 return ConnectToHost(hostName, ss.str(), mode);
140 bool TcpSocket::ConnectToHost(const string& hostName,
142 IBamIODevice::OpenMode mode)
144 // create new address object with requested host name
145 HostAddress hostAddress;
146 hostAddress.SetAddress(hostName);
149 // if host name was IP address ("x.x.x.x" or IPv6 format)
150 // otherwise host name was 'plain-text' ("www.foo.bar")
151 // we need to look up IP address(es)
152 if ( hostAddress.HasIPAddress() )
153 info.SetAddresses( vector<HostAddress>(1, hostAddress) );
155 info = HostInfo::Lookup(hostName, port);
157 // attempt connection on requested port
158 return ConnectImpl(info, port, mode);
161 void TcpSocket::DisconnectFromHost(void) {
163 // close socket engine & delete
164 if ( m_state == TcpSocket::ConnectedState )
167 // reset connection state
170 // m_localAddress.Clear();
171 m_remoteAddress.Clear();
173 m_cachedSocketDescriptor = -1;
176 TcpSocket::SocketError TcpSocket::GetError(void) const {
180 std::string TcpSocket::GetErrorString(void) const {
181 return m_errorString;
184 std::string TcpSocket::GetHostName(void) const {
188 //HostAddress TcpSocket::GetLocalAddress(void) const {
189 // return m_localAddress;
192 //uint16_t TcpSocket::GetLocalPort(void) const {
193 // return m_localPort;
196 HostAddress TcpSocket::GetRemoteAddress(void) const {
197 return m_remoteAddress;
200 uint16_t TcpSocket::GetRemotePort(void) const {
204 TcpSocket::SocketState TcpSocket::GetState(void) const {
208 bool TcpSocket::InitializeSocketEngine(HostAddress::NetworkProtocol protocol) {
210 m_engine = new TcpSocketEngine;
211 return m_engine->Initialize(protocol);
214 bool TcpSocket::IsConnected(void) const {
217 return ( m_engine->IsValid() && (m_state == TcpSocket::ConnectedState) );
220 // may be read in a look until desired data amount has been read
221 // returns: number of bytes read, or -1 if error
222 int64_t TcpSocket::Read(char* data, const unsigned int numBytes) {
224 // if we have data in buffer, just return it
225 if ( !m_readBuffer.IsEmpty() ) {
226 const size_t bytesRead = m_readBuffer.Read(data, numBytes);
227 return static_cast<int64_t>(bytesRead);
230 // otherwise, we'll need to fetch data from socket
231 // first make sure we have a valid socket engine
232 if ( m_engine == 0 ) {
233 // TODO: set error string/state?
237 // fetch data from socket, return 0 for success, -1 for failure
238 // since this should be called in a loop, we'll pull the actual bytes on next iteration
239 return ( ReadFromSocket() ? 0 : -1 );
242 bool TcpSocket::ReadFromSocket(void) {
244 // wait for ready read
246 bool isReadyRead = m_engine->WaitForRead(5000, &timedOut);
249 if ( !isReadyRead ) {
251 // if we simply timed out
253 // TODO: set error string
257 // otherwise, there was an error
259 // TODO: set error string
264 // #########################################################################
265 // clean this up - smells funky, but it's a key step so it has to be right
266 // #########################################################################
267 // get number of bytes available from socket
268 // (if 0, still try to read some data so we don't trigger any OS event behavior
269 // that respond to repeated access to a remote closed socket)
270 int64_t bytesToRead = m_engine->NumBytesAvailable();
271 if ( bytesToRead < 0 )
273 else if ( bytesToRead == 0 )
276 // make space in buffer & read from socket
277 char* buffer = m_readBuffer.Reserve(bytesToRead);
278 int64_t numBytesRead = m_engine->Read(buffer, bytesToRead);
280 // (Qt uses -2 for no data, not error)
281 // squeeze buffer back down & return success
282 if ( numBytesRead == -2 ) {
283 m_readBuffer.Chop(bytesToRead);
286 // #########################################################################
288 // check for any socket engine errors
289 if ( !m_engine->IsValid() ) {
290 // TODO: set error string
299 string TcpSocket::ReadLine(void) {
300 if ( m_readBuffer.CanReadLine() )
301 return m_readBuffer.ReadLine();
305 void TcpSocket::ResetSocketEngine(void) {
307 // shut down socket engine
314 // reset our state & cached socket handle
315 m_state = TcpSocket::UnconnectedState;
316 m_cachedSocketDescriptor = -1;
319 int64_t TcpSocket::Write(const char* data, const unsigned int numBytes) {
321 // single-shot attempt at write (not buffered, just try to shove the data through socket)
322 // this method purely exists to send 'small' HTTP requests/FTP commands from client to server
324 int64_t bytesWritten(0);
326 // wait for our socket to be write-able
328 bool isReadyWrite = m_engine->WaitForWrite(3000, &timedOut);
330 bytesWritten = m_engine->Write(data, numBytes);
332 // timeout is OK (with current setup), we'll just return 0 & try again
333 // but we need to report if engine encountered some other error
335 // TODO: set error string
340 // return actual number of bytes written to socket