From 878b504f8e044bc0e59903caa8641492421b76d8 Mon Sep 17 00:00:00 2001 From: James Turner Date: Sun, 21 Aug 2011 22:30:04 +0100 Subject: [PATCH] Support non-blocking address lookups, and switch to getaddrinfo over gethostbyname. (Only affects netChannel - raw socket will use blocking behaviour by default, as previously) --- simgear/io/CMakeLists.txt | 8 +- simgear/io/HTTPClient.cxx | 2 +- simgear/io/HostLookup.cxx | 139 +++++++++++++++++++++++++++++++++++ simgear/io/HostLookup.hxx | 45 ++++++++++++ simgear/io/raw_socket.cxx | 61 ++++++++++----- simgear/io/raw_socket.hxx | 27 +++---- simgear/io/sg_netChannel.cxx | 48 ++++++++---- simgear/io/sg_netChannel.hxx | 7 ++ 8 files changed, 287 insertions(+), 50 deletions(-) create mode 100644 simgear/io/HostLookup.cxx create mode 100644 simgear/io/HostLookup.hxx diff --git a/simgear/io/CMakeLists.txt b/simgear/io/CMakeLists.txt index a2638e1b..1f2ee11d 100644 --- a/simgear/io/CMakeLists.txt +++ b/simgear/io/CMakeLists.txt @@ -32,22 +32,24 @@ set(SOURCES sg_socket_udp.cxx HTTPClient.cxx HTTPRequest.cxx + HostLookup.cxx + HostLookup.hxx ) simgear_component(io io "${SOURCES}" "${HEADERS}") add_executable(test_sock socktest.cxx) -target_link_libraries(test_sock sgio sgstructure sgdebug) +target_link_libraries(test_sock sgio sgstructure sgdebug sgthreads) add_executable(test_http test_HTTP.cxx) target_link_libraries(test_http - sgio sgstructure sgtiming sgmisc sgdebug + sgio sgstructure sgtiming sgmisc sgdebug sgthreads ${RT_LIBRARY}) add_test(http ${EXECUTABLE_OUTPUT_PATH}/test_http) add_executable(httpget httpget.cxx) target_link_libraries(httpget - sgio sgstructure sgtiming sgmisc sgdebug + sgio sgstructure sgtiming sgmisc sgdebug sgthreads ${RT_LIBRARY}) \ No newline at end of file diff --git a/simgear/io/HTTPClient.cxx b/simgear/io/HTTPClient.cxx index 96ef25bd..ad06756f 100644 --- a/simgear/io/HTTPClient.cxx +++ b/simgear/io/HTTPClient.cxx @@ -17,7 +17,7 @@ #include "version.h" #else # if !defined(SIMGEAR_VERSION) -# define SIMGEAR_VERSION "simgear-development" +# define SIMGEAR_VERSION development # endif #endif diff --git a/simgear/io/HostLookup.cxx b/simgear/io/HostLookup.cxx new file mode 100644 index 00000000..a9028e9f --- /dev/null +++ b/simgear/io/HostLookup.cxx @@ -0,0 +1,139 @@ +#include "HostLookup.hxx" + +#include +#include +#include + +#include + +#include + +using std::string; + +namespace simgear +{ + +class Resolver : public SGThread +{ +public: + Resolver() + { + // take lock when resolver starts, + // won't release until it's actually running, and waiting on the + // condition. Otherwise we get a race when starting. + _lock.lock(); + } + + virtual void run() + { + while (true) { + _cond.wait(_lock); // wait until there's something to lookup + + // find the first un-resolved entry in the cache + HostCache::iterator it; + for (it = _cache.begin(); it != _cache.end(); ++it) { + if (!it->second->resolved()) { + break; + } + } + + if (it == _cache.end()) { + continue; + } + + string host = it->first; + // don't hold any locks while looking up + _lock.unlock(); + + // start the actual lookup - may take a long period of time! + // (eg, one, five or sixty seconds) + struct addrinfo hints; + bzero(&hints, sizeof(struct addrinfo)); + hints.ai_family = PF_UNSPEC; + + struct addrinfo *results; + + int result = getaddrinfo(host.c_str(), NULL, &hints, &results); + _lock.lock(); // take the lock back before going any further + + if (result == 0) { + _cache[host]->resolve(IPAddress(results->ai_addr, results->ai_addrlen)); + freeaddrinfo(results); + } else if (result == EAGAIN) { + + } else { + const char* errMsg = gai_strerror(result); + // bad lookup, remove from cache? or set as failed? + _cache[host]->fail(); + } + } // of thread loop + + _lock.unlock(); + } + + void addLookup(HostLookup* l) + { + _lock.lock(); + _cache[l->host()] = l; + _cond.signal(); + _lock.unlock(); + } + + HostLookup* lookup(const string& host) + { + _lock.lock(); + HostCache::iterator it = _cache.find(host); + HostLookup* result = it->second; + _lock.unlock(); + return result; + } +private: + SGMutex _lock; + SGPthreadCond _cond; + + typedef std::map HostCache; + HostCache _cache; +}; + +Resolver* static_resolve = NULL; + +HostLookup* HostLookup::lookup(const string& host) +{ + if (!static_resolve) { + static_resolve = new Resolver; + static_resolve->start(); + } + + HostLookup* l = static_resolve->lookup(host); + if (l) { + return l; + } + + l = new HostLookup(host); + SGReferenced::get(l); + static_resolve->addLookup(l); + return l; +} + +HostLookup::HostLookup(const std::string& h) : + _host(h), + _resolved(false) +{ +} + +void HostLookup::resolve(const IPAddress& addr) +{ + _failed = false; + _address = addr; + _age.stamp(); + _resolved = true; +} + +void HostLookup::fail() +{ + _failed = true; + _resolved = false; + _age.stamp(); +} + +} // of namespace diff --git a/simgear/io/HostLookup.hxx b/simgear/io/HostLookup.hxx new file mode 100644 index 00000000..ee8b0297 --- /dev/null +++ b/simgear/io/HostLookup.hxx @@ -0,0 +1,45 @@ +#ifndef SG_HOST_LOOKUP_HXX +#define SG_HOST_LOOKUP_HXX + +#include +#include + +#include + +namespace simgear +{ + +class HostLookup : public SGReferenced +{ +public: + static HostLookup* lookup(const std::string& h); + + bool resolved() const + { return _resolved; } + + bool failed() const + { return _failed; } + + const IPAddress& address() const + { return _address; } + + const std::string& host() const + { return _host; } +private: + HostLookup(const std::string& h); + + friend class Resolver; + + void resolve(const IPAddress& addr); + void fail(); + + std::string _host; + bool _resolved; + bool _failed; + IPAddress _address; + SGTimeStamp _age; +}; + +} // of namespace simgear + +#endif // of SG_HOST_LOOKUP_HXX diff --git a/simgear/io/raw_socket.cxx b/simgear/io/raw_socket.cxx index 63c9b55e..bc9acc54 100644 --- a/simgear/io/raw_socket.cxx +++ b/simgear/io/raw_socket.cxx @@ -37,6 +37,7 @@ #include #include #include // for snprintf +#include #if defined(WINSOCK) # include @@ -67,13 +68,30 @@ IPAddress::IPAddress ( const char* host, int port ) set ( host, port ) ; } +IPAddress::IPAddress ( struct sockaddr* addr, size_t len ) +{ + memset(&_raw, 0, sizeof(struct sockaddr_in)); + if ((addr->sa_family != AF_INET) || + (len != sizeof(struct sockaddr_in))) + { + std::cout << "address size mismatch" << std::endl; + return; + } + + memcpy(&_raw, addr, len); +} + +void IPAddress::setPort(unsigned int port) +{ + _raw.sin_port = htons(port); +} void IPAddress::set ( const char* host, int port ) { - memset(this, 0, sizeof(IPAddress)); + memset(&_raw, 0, sizeof(struct sockaddr_in)); - sin_family = AF_INET ; - sin_port = htons (port); + _raw.sin_family = AF_INET ; + _raw.sin_port = htons (port); /* Convert a string specifying a host name or one of a few symbolic ** names to a numeric IP address. This usually calls gethostbyname() @@ -81,28 +99,28 @@ void IPAddress::set ( const char* host, int port ) */ if (!host || host[0] == '\0') { - sin_addr = INADDR_ANY; + _raw.sin_addr.s_addr = INADDR_ANY; return; } if (strcmp(host, "") == 0) { - sin_addr = INADDR_BROADCAST; + _raw.sin_addr.s_addr = INADDR_BROADCAST; return; } - sin_addr = inet_addr ( host ) ; - if (sin_addr != INADDR_NONE) { + _raw.sin_addr.s_addr = inet_addr ( host ) ; + if (_raw.sin_addr.s_addr != INADDR_NONE) { return; } // finally, try gethostbyname struct hostent *hp = gethostbyname ( host ) ; if (!hp) { SG_LOG(SG_IO, SG_WARN, "gethostbyname failed for " << host); - sin_addr = INADDR_ANY ; + _raw.sin_addr.s_addr = INADDR_ANY ; return; } - memcpy ( (char *) &sin_addr, hp->h_addr, hp->h_length ) ; + memcpy ( (char *) &_raw.sin_addr, hp->h_addr, hp->h_length ) ; } @@ -116,7 +134,7 @@ const char* IPAddress::getHost () const const char* buf = inet_ntoa ( sin_addr ) ; #else static char buf [32]; - long x = ntohl(sin_addr); + long x = ntohl(_raw.sin_addr.s_addr); sprintf(buf, "%d.%d.%d.%d", (int) (x>>24) & 0xff, (int) (x>>16) & 0xff, (int) (x>> 8) & 0xff, (int) (x>> 0) & 0xff ); @@ -126,17 +144,17 @@ const char* IPAddress::getHost () const unsigned int IPAddress::getIP () const { - return sin_addr; + return _raw.sin_addr.s_addr; } unsigned int IPAddress::getPort() const { - return ntohs(sin_port); + return ntohs(_raw.sin_port); } unsigned int IPAddress::getFamily () const { - return sin_family; + return _raw.sin_family; } const char* IPAddress::getLocalHost () @@ -163,7 +181,7 @@ const char* IPAddress::getLocalHost () bool IPAddress::getBroadcast () const { - return sin_addr == INADDR_BROADCAST; + return (_raw.sin_addr.s_addr == INADDR_BROADCAST); } @@ -321,8 +339,8 @@ int Socket::accept ( IPAddress* addr ) } else { - socklen_t addr_len = (socklen_t) sizeof(IPAddress) ; - return ::accept(handle,(sockaddr*)addr,&addr_len); + socklen_t addr_len = addr->getAddressSize(); + return ::accept(handle,(sockaddr*)addr->getAddress(),&addr_len); } } @@ -334,9 +352,18 @@ int Socket::connect ( const char* host, int port ) if ( addr.getBroadcast() ) { setBroadcast( true ); } - return ::connect(handle,(const sockaddr*)&addr,sizeof(IPAddress)); + return ::connect(handle,(const sockaddr*) addr.getAddress(), addr.getAddressSize()); } +int Socket::connect ( const IPAddress& addr ) +{ + assert ( handle != -1 ) ; + if ( addr.getBroadcast() ) { + setBroadcast( true ); + } + + return ::connect(handle,(const sockaddr*) addr.getAddress(), addr.getAddressSize()); +} int Socket::send (const void * buffer, int size, int flags) { diff --git a/simgear/io/raw_socket.hxx b/simgear/io/raw_socket.hxx index 4eb30448..1af74ce0 100644 --- a/simgear/io/raw_socket.hxx +++ b/simgear/io/raw_socket.hxx @@ -37,24 +37,11 @@ namespace simgear */ class IPAddress { - /* DANGER!!! This MUST match 'struct sockaddr_in' exactly! */ -#if defined(__APPLE__) || defined(__FreeBSD__) - __uint8_t sin_len; - __uint8_t sin_family; - in_port_t sin_port; - in_addr_t sin_addr; - char sin_zero[8]; -#else - short sin_family ; - unsigned short sin_port ; - unsigned int sin_addr ; - char sin_zero [ 8 ] ; -#endif - public: IPAddress () {} IPAddress ( const char* host, int port ) ; - + IPAddress ( struct sockaddr* addr, size_t len ); + void set ( const char* host, int port ) ; const char* getHost () const ; unsigned int getPort() const ; @@ -63,6 +50,15 @@ public: static const char* getLocalHost () ; bool getBroadcast () const ; + + void setPort(unsigned int port); + const struct sockaddr_in* getAddress() const + { return &_raw; } + + const size_t getAddressSize() const + { return sizeof(struct sockaddr_in); } +private: + struct sockaddr_in _raw; }; @@ -89,6 +85,7 @@ public: int listen ( int backlog ) ; int accept ( IPAddress* addr ) ; int connect ( const char* host, int port ) ; + int connect ( const IPAddress& addr ) ; int send ( const void * buffer, int size, int flags = 0 ) ; int sendto ( const void * buffer, int size, int flags, const IPAddress* to ) ; int recv ( void * buffer, int size, int flags = 0 ) ; diff --git a/simgear/io/sg_netChannel.cxx b/simgear/io/sg_netChannel.cxx index c6502565..d34a521b 100644 --- a/simgear/io/sg_netChannel.cxx +++ b/simgear/io/sg_netChannel.cxx @@ -36,6 +36,7 @@ #include #include +#include namespace simgear { @@ -106,21 +107,11 @@ NetChannel::listen ( int backlog ) } int -NetChannel::connect ( const char* host, int port ) +NetChannel::connect ( const char* host, int p ) { - int result = Socket::connect ( host, port ) ; - if (result == 0) { - connected = true ; - //this->handleConnect(); - return 0; - } else if (isNonBlockingError ()) { - return 0; - } else { - // some other error condition - this->handleError (result); - close(); - return -1; - } + host_lookup = HostLookup::lookup(host); + port = p; + return 0; } int @@ -211,6 +202,25 @@ NetChannel::handleWriteEvent (void) this->handleWrite(); } +void +NetChannel::doConnect() +{ + IPAddress addr( host_lookup->address() ); + addr.setPort(port); + int result = Socket::connect ( addr ) ; + host_lookup = NULL; + + if (result == 0) { + connected = true ; + } else if (isNonBlockingError ()) { + + } else { + // some other error condition + handleError (result); + close(); + } +} + bool NetChannel::poll (unsigned int timeout) { @@ -236,6 +246,16 @@ NetChannel::poll (unsigned int timeout) else if ( ! ch -> closed ) { nopen++ ; + if (ch->host_lookup) { + if (ch->host_lookup->resolved()) { + ch->doConnect(); + } else if (ch->host_lookup->failed()) { + ch->handleError (-1); + ch->close(); + } + continue; + } + if (ch -> readable()) { assert(nreads +#include + namespace simgear { +class HostLookup; + class NetChannel : public Socket { bool closed, connected, accepting, write_blocked, should_delete ; NetChannel* next_channel ; + SGSharedPtr host_lookup ; + int port; friend bool netPoll (unsigned int timeout); + void doConnect(); public: NetChannel () ; -- 2.39.5