pion-net  4.0.9
net/src/TCPServer.cpp
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