pion-net
4.0.9
|
00001 // ------------------------------------------------------------------ 00002 // pion-net: a C++ framework for building lightweight HTTP interfaces 00003 // ------------------------------------------------------------------ 00004 // Copyright (C) 2007-2008 Atomic Labs, Inc. (http://www.atomiclabs.com) 00005 // 00006 // Distributed under the Boost Software License, Version 1.0. 00007 // See http://www.boost.org/LICENSE_1_0.txt 00008 // 00009 00010 #include <boost/asio.hpp> 00011 #include <boost/bind.hpp> 00012 #include <boost/thread/mutex.hpp> 00013 #include <pion/PionAdminRights.hpp> 00014 #include <pion/net/TCPServer.hpp> 00015 00016 using boost::asio::ip::tcp; 00017 00018 00019 namespace pion { // begin namespace pion 00020 namespace net { // begin namespace net (Pion Network Library) 00021 00022 00023 // TCPServer member functions 00024 00025 TCPServer::TCPServer(PionScheduler& scheduler, const unsigned int tcp_port) 00026 : m_logger(PION_GET_LOGGER("pion.net.TCPServer")), 00027 m_active_scheduler(scheduler), 00028 m_tcp_acceptor(m_active_scheduler.getIOService()), 00029 #ifdef PION_HAVE_SSL 00030 m_ssl_context(m_active_scheduler.getIOService(), boost::asio::ssl::context::sslv23), 00031 #else 00032 m_ssl_context(0), 00033 #endif 00034 m_endpoint(tcp::v4(), tcp_port), m_ssl_flag(false), m_is_listening(false) 00035 {} 00036 00037 TCPServer::TCPServer(PionScheduler& scheduler, const tcp::endpoint& endpoint) 00038 : m_logger(PION_GET_LOGGER("pion.net.TCPServer")), 00039 m_active_scheduler(scheduler), 00040 m_tcp_acceptor(m_active_scheduler.getIOService()), 00041 #ifdef PION_HAVE_SSL 00042 m_ssl_context(m_active_scheduler.getIOService(), boost::asio::ssl::context::sslv23), 00043 #else 00044 m_ssl_context(0), 00045 #endif 00046 m_endpoint(endpoint), m_ssl_flag(false), m_is_listening(false) 00047 {} 00048 00049 TCPServer::TCPServer(const unsigned int tcp_port) 00050 : m_logger(PION_GET_LOGGER("pion.net.TCPServer")), 00051 m_default_scheduler(), m_active_scheduler(m_default_scheduler), 00052 m_tcp_acceptor(m_active_scheduler.getIOService()), 00053 #ifdef PION_HAVE_SSL 00054 m_ssl_context(m_active_scheduler.getIOService(), boost::asio::ssl::context::sslv23), 00055 #else 00056 m_ssl_context(0), 00057 #endif 00058 m_endpoint(tcp::v4(), tcp_port), m_ssl_flag(false), m_is_listening(false) 00059 {} 00060 00061 TCPServer::TCPServer(const tcp::endpoint& endpoint) 00062 : m_logger(PION_GET_LOGGER("pion.net.TCPServer")), 00063 m_default_scheduler(), m_active_scheduler(m_default_scheduler), 00064 m_tcp_acceptor(m_active_scheduler.getIOService()), 00065 #ifdef PION_HAVE_SSL 00066 m_ssl_context(m_active_scheduler.getIOService(), boost::asio::ssl::context::sslv23), 00067 #else 00068 m_ssl_context(0), 00069 #endif 00070 m_endpoint(endpoint), m_ssl_flag(false), m_is_listening(false) 00071 {} 00072 00073 void TCPServer::start(void) 00074 { 00075 // lock mutex for thread safety 00076 boost::mutex::scoped_lock server_lock(m_mutex); 00077 00078 if (! m_is_listening) { 00079 PION_LOG_INFO(m_logger, "Starting server on port " << getPort()); 00080 00081 beforeStarting(); 00082 00083 // configure the acceptor service 00084 try { 00085 // get admin permissions in case we're binding to a privileged port 00086 pion::PionAdminRights use_admin_rights(getPort() < 1024); 00087 m_tcp_acceptor.open(m_endpoint.protocol()); 00088 // allow the acceptor to reuse the address (i.e. SO_REUSEADDR) 00089 // ...except when running not on Windows - see http://msdn.microsoft.com/en-us/library/ms740621%28VS.85%29.aspx 00090 #ifndef _MSC_VER 00091 m_tcp_acceptor.set_option(tcp::acceptor::reuse_address(true)); 00092 #endif 00093 m_tcp_acceptor.bind(m_endpoint); 00094 if (m_endpoint.port() == 0) { 00095 // update the endpoint to reflect the port chosen by bind 00096 m_endpoint = m_tcp_acceptor.local_endpoint(); 00097 } 00098 m_tcp_acceptor.listen(); 00099 } catch (std::exception& e) { 00100 PION_LOG_ERROR(m_logger, "Unable to bind to port " << getPort() << ": " << e.what()); 00101 throw; 00102 } 00103 00104 m_is_listening = true; 00105 00106 // unlock the mutex since listen() requires its own lock 00107 server_lock.unlock(); 00108 listen(); 00109 00110 // notify the thread scheduler that we need it now 00111 m_active_scheduler.addActiveUser(); 00112 } 00113 } 00114 00115 void TCPServer::stop(bool wait_until_finished) 00116 { 00117 // lock mutex for thread safety 00118 boost::mutex::scoped_lock server_lock(m_mutex); 00119 00120 if (m_is_listening) { 00121 PION_LOG_INFO(m_logger, "Shutting down server on port " << getPort()); 00122 00123 m_is_listening = false; 00124 00125 // this terminates any connections waiting to be accepted 00126 m_tcp_acceptor.close(); 00127 00128 if (! wait_until_finished) { 00129 // this terminates any other open connections 00130 std::for_each(m_conn_pool.begin(), m_conn_pool.end(), 00131 boost::bind(&TCPConnection::close, _1)); 00132 } 00133 00134 // wait for all pending connections to complete 00135 while (! m_conn_pool.empty()) { 00136 // try to prun connections that didn't finish cleanly 00137 if (pruneConnections() == 0) 00138 break; // if no more left, then we can stop waiting 00139 // sleep for up to a quarter second to give open connections a chance to finish 00140 PION_LOG_INFO(m_logger, "Waiting for open connections to finish"); 00141 PionScheduler::sleep(m_no_more_connections, server_lock, 0, 250000000); 00142 } 00143 00144 // notify the thread scheduler that we no longer need it 00145 m_active_scheduler.removeActiveUser(); 00146 00147 // all done! 00148 afterStopping(); 00149 m_server_has_stopped.notify_all(); 00150 } 00151 } 00152 00153 void TCPServer::join(void) 00154 { 00155 boost::mutex::scoped_lock server_lock(m_mutex); 00156 while (m_is_listening) { 00157 // sleep until server_has_stopped condition is signaled 00158 m_server_has_stopped.wait(server_lock); 00159 } 00160 } 00161 00162 void TCPServer::setSSLKeyFile(const std::string& pem_key_file) 00163 { 00164 // configure server for SSL 00165 setSSLFlag(true); 00166 #ifdef PION_HAVE_SSL 00167 m_ssl_context.set_options(boost::asio::ssl::context::default_workarounds 00168 | boost::asio::ssl::context::no_sslv2 00169 | boost::asio::ssl::context::single_dh_use); 00170 m_ssl_context.use_certificate_file(pem_key_file, boost::asio::ssl::context::pem); 00171 m_ssl_context.use_private_key_file(pem_key_file, boost::asio::ssl::context::pem); 00172 #endif 00173 } 00174 00175 void TCPServer::listen(void) 00176 { 00177 // lock mutex for thread safety 00178 boost::mutex::scoped_lock server_lock(m_mutex); 00179 00180 if (m_is_listening) { 00181 // create a new TCP connection object 00182 TCPConnectionPtr new_connection(TCPConnection::create(getIOService(), 00183 m_ssl_context, m_ssl_flag, 00184 boost::bind(&TCPServer::finishConnection, 00185 this, _1))); 00186 00187 // prune connections that finished uncleanly 00188 pruneConnections(); 00189 00190 // keep track of the object in the server's connection pool 00191 m_conn_pool.insert(new_connection); 00192 00193 // use the object to accept a new connection 00194 new_connection->async_accept(m_tcp_acceptor, 00195 boost::bind(&TCPServer::handleAccept, 00196 this, new_connection, 00197 boost::asio::placeholders::error)); 00198 } 00199 } 00200 00201 void TCPServer::handleAccept(TCPConnectionPtr& tcp_conn, 00202 const boost::system::error_code& accept_error) 00203 { 00204 if (accept_error) { 00205 // an error occured while trying to a accept a new connection 00206 // this happens when the server is being shut down 00207 if (m_is_listening) { 00208 listen(); // schedule acceptance of another connection 00209 PION_LOG_WARN(m_logger, "Accept error on port " << getPort() << ": " << accept_error.message()); 00210 } 00211 finishConnection(tcp_conn); 00212 } else { 00213 // got a new TCP connection 00214 PION_LOG_DEBUG(m_logger, "New" << (tcp_conn->getSSLFlag() ? " SSL " : " ") 00215 << "connection on port " << getPort()); 00216 00217 // schedule the acceptance of another new connection 00218 // (this returns immediately since it schedules it as an event) 00219 if (m_is_listening) listen(); 00220 00221 // handle the new connection 00222 #ifdef PION_HAVE_SSL 00223 if (tcp_conn->getSSLFlag()) { 00224 tcp_conn->async_handshake_server(boost::bind(&TCPServer::handleSSLHandshake, 00225 this, tcp_conn, 00226 boost::asio::placeholders::error)); 00227 } else 00228 #endif 00229 // not SSL -> call the handler immediately 00230 handleConnection(tcp_conn); 00231 } 00232 } 00233 00234 void TCPServer::handleSSLHandshake(TCPConnectionPtr& tcp_conn, 00235 const boost::system::error_code& handshake_error) 00236 { 00237 if (handshake_error) { 00238 // an error occured while trying to establish the SSL connection 00239 PION_LOG_WARN(m_logger, "SSL handshake failed on port " << getPort() 00240 << " (" << handshake_error.message() << ')'); 00241 finishConnection(tcp_conn); 00242 } else { 00243 // handle the new connection 00244 PION_LOG_DEBUG(m_logger, "SSL handshake succeeded on port " << getPort()); 00245 handleConnection(tcp_conn); 00246 } 00247 } 00248 00249 void TCPServer::finishConnection(TCPConnectionPtr& tcp_conn) 00250 { 00251 boost::mutex::scoped_lock server_lock(m_mutex); 00252 if (m_is_listening && tcp_conn->getKeepAlive()) { 00253 00254 // keep the connection alive 00255 handleConnection(tcp_conn); 00256 00257 } else { 00258 PION_LOG_DEBUG(m_logger, "Closing connection on port " << getPort()); 00259 00260 // remove the connection from the server's management pool 00261 ConnectionPool::iterator conn_itr = m_conn_pool.find(tcp_conn); 00262 if (conn_itr != m_conn_pool.end()) 00263 m_conn_pool.erase(conn_itr); 00264 00265 // trigger the no more connections condition if we're waiting to stop 00266 if (!m_is_listening && m_conn_pool.empty()) 00267 m_no_more_connections.notify_all(); 00268 } 00269 } 00270 00271 std::size_t TCPServer::pruneConnections(void) 00272 { 00273 // assumes that a server lock has already been acquired 00274 ConnectionPool::iterator conn_itr = m_conn_pool.begin(); 00275 while (conn_itr != m_conn_pool.end()) { 00276 if (conn_itr->unique()) { 00277 PION_LOG_WARN(m_logger, "Closing orphaned connection on port " << getPort()); 00278 ConnectionPool::iterator erase_itr = conn_itr; 00279 ++conn_itr; 00280 (*erase_itr)->close(); 00281 m_conn_pool.erase(erase_itr); 00282 } else { 00283 ++conn_itr; 00284 } 00285 } 00286 00287 // return the number of connections remaining 00288 return m_conn_pool.size(); 00289 } 00290 00291 std::size_t TCPServer::getConnections(void) const 00292 { 00293 boost::mutex::scoped_lock server_lock(m_mutex); 00294 return (m_is_listening ? (m_conn_pool.size() - 1) : m_conn_pool.size()); 00295 } 00296 00297 } // end namespace net 00298 } // end namespace pion