# 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