pion-net  4.0.9
TCPServer.cpp
1 // ------------------------------------------------------------------
2 // pion-net: a C++ framework for building lightweight HTTP interfaces
3 // ------------------------------------------------------------------
4 // Copyright (C) 2007-2008 Atomic Labs, Inc. (http://www.atomiclabs.com)
5 //
6 // Distributed under the Boost Software License, Version 1.0.
7 // See http://www.boost.org/LICENSE_1_0.txt
8 //
9 
10 #include <boost/asio.hpp>
11 #include <boost/bind.hpp>
12 #include <boost/thread/mutex.hpp>
13 #include <pion/PionAdminRights.hpp>
14 #include <pion/net/TCPServer.hpp>
15 
16 using boost::asio::ip::tcp;
17 
18 
19 namespace pion { // begin namespace pion
20 namespace net { // begin namespace net (Pion Network Library)
21 
22 
23 // TCPServer member functions
24 
25 TCPServer::TCPServer(PionScheduler& scheduler, const unsigned int tcp_port)
26  : m_logger(PION_GET_LOGGER("pion.net.TCPServer")),
27  m_active_scheduler(scheduler),
28  m_tcp_acceptor(m_active_scheduler.getIOService()),
29 #ifdef PION_HAVE_SSL
30  m_ssl_context(m_active_scheduler.getIOService(), boost::asio::ssl::context::sslv23),
31 #else
32  m_ssl_context(0),
33 #endif
34  m_endpoint(tcp::v4(), tcp_port), m_ssl_flag(false), m_is_listening(false)
35 {}
36 
37 TCPServer::TCPServer(PionScheduler& scheduler, const tcp::endpoint& endpoint)
38  : m_logger(PION_GET_LOGGER("pion.net.TCPServer")),
39  m_active_scheduler(scheduler),
40  m_tcp_acceptor(m_active_scheduler.getIOService()),
41 #ifdef PION_HAVE_SSL
42  m_ssl_context(m_active_scheduler.getIOService(), boost::asio::ssl::context::sslv23),
43 #else
44  m_ssl_context(0),
45 #endif
46  m_endpoint(endpoint), m_ssl_flag(false), m_is_listening(false)
47 {}
48 
49 TCPServer::TCPServer(const unsigned int tcp_port)
50  : m_logger(PION_GET_LOGGER("pion.net.TCPServer")),
51  m_default_scheduler(), m_active_scheduler(m_default_scheduler),
52  m_tcp_acceptor(m_active_scheduler.getIOService()),
53 #ifdef PION_HAVE_SSL
54  m_ssl_context(m_active_scheduler.getIOService(), boost::asio::ssl::context::sslv23),
55 #else
56  m_ssl_context(0),
57 #endif
58  m_endpoint(tcp::v4(), tcp_port), m_ssl_flag(false), m_is_listening(false)
59 {}
60 
61 TCPServer::TCPServer(const tcp::endpoint& endpoint)
62  : m_logger(PION_GET_LOGGER("pion.net.TCPServer")),
63  m_default_scheduler(), m_active_scheduler(m_default_scheduler),
64  m_tcp_acceptor(m_active_scheduler.getIOService()),
65 #ifdef PION_HAVE_SSL
66  m_ssl_context(m_active_scheduler.getIOService(), boost::asio::ssl::context::sslv23),
67 #else
68  m_ssl_context(0),
69 #endif
70  m_endpoint(endpoint), m_ssl_flag(false), m_is_listening(false)
71 {}
72 
73 void TCPServer::start(void)
74 {
75  // lock mutex for thread safety
76  boost::mutex::scoped_lock server_lock(m_mutex);
77 
78  if (! m_is_listening) {
79  PION_LOG_INFO(m_logger, "Starting server on port " << getPort());
80 
82 
83  // configure the acceptor service
84  try {
85  // get admin permissions in case we're binding to a privileged port
86  pion::PionAdminRights use_admin_rights(getPort() < 1024);
87  m_tcp_acceptor.open(m_endpoint.protocol());
88  // allow the acceptor to reuse the address (i.e. SO_REUSEADDR)
89  // ...except when running not on Windows - see http://msdn.microsoft.com/en-us/library/ms740621%28VS.85%29.aspx
90 #ifndef _MSC_VER
91  m_tcp_acceptor.set_option(tcp::acceptor::reuse_address(true));
92 #endif
93  m_tcp_acceptor.bind(m_endpoint);
94  if (m_endpoint.port() == 0) {
95  // update the endpoint to reflect the port chosen by bind
96  m_endpoint = m_tcp_acceptor.local_endpoint();
97  }
98  m_tcp_acceptor.listen();
99  } catch (std::exception& e) {
100  PION_LOG_ERROR(m_logger, "Unable to bind to port " << getPort() << ": " << e.what());
101  throw;
102  }
103 
104  m_is_listening = true;
105 
106  // unlock the mutex since listen() requires its own lock
107  server_lock.unlock();
108  listen();
109 
110  // notify the thread scheduler that we need it now
111  m_active_scheduler.addActiveUser();
112  }
113 }
114 
115 void TCPServer::stop(bool wait_until_finished)
116 {
117  // lock mutex for thread safety
118  boost::mutex::scoped_lock server_lock(m_mutex);
119 
120  if (m_is_listening) {
121  PION_LOG_INFO(m_logger, "Shutting down server on port " << getPort());
122 
123  m_is_listening = false;
124 
125  // this terminates any connections waiting to be accepted
126  m_tcp_acceptor.close();
127 
128  if (! wait_until_finished) {
129  // this terminates any other open connections
130  std::for_each(m_conn_pool.begin(), m_conn_pool.end(),
131  boost::bind(&TCPConnection::close, _1));
132  }
133 
134  // wait for all pending connections to complete
135  while (! m_conn_pool.empty()) {
136  // try to prun connections that didn't finish cleanly
137  if (pruneConnections() == 0)
138  break; // if no more left, then we can stop waiting
139  // sleep for up to a quarter second to give open connections a chance to finish
140  PION_LOG_INFO(m_logger, "Waiting for open connections to finish");
141  PionScheduler::sleep(m_no_more_connections, server_lock, 0, 250000000);
142  }
143 
144  // notify the thread scheduler that we no longer need it
145  m_active_scheduler.removeActiveUser();
146 
147  // all done!
148  afterStopping();
149  m_server_has_stopped.notify_all();
150  }
151 }
152 
153 void TCPServer::join(void)
154 {
155  boost::mutex::scoped_lock server_lock(m_mutex);
156  while (m_is_listening) {
157  // sleep until server_has_stopped condition is signaled
158  m_server_has_stopped.wait(server_lock);
159  }
160 }
161 
162 void TCPServer::setSSLKeyFile(const std::string& pem_key_file)
163 {
164  // configure server for SSL
165  setSSLFlag(true);
166 #ifdef PION_HAVE_SSL
167  m_ssl_context.set_options(boost::asio::ssl::context::default_workarounds
168  | boost::asio::ssl::context::no_sslv2
169  | boost::asio::ssl::context::single_dh_use);
170  m_ssl_context.use_certificate_file(pem_key_file, boost::asio::ssl::context::pem);
171  m_ssl_context.use_private_key_file(pem_key_file, boost::asio::ssl::context::pem);
172 #endif
173 }
174 
175 void TCPServer::listen(void)
176 {
177  // lock mutex for thread safety
178  boost::mutex::scoped_lock server_lock(m_mutex);
179 
180  if (m_is_listening) {
181  // create a new TCP connection object
182  TCPConnectionPtr new_connection(TCPConnection::create(getIOService(),
183  m_ssl_context, m_ssl_flag,
184  boost::bind(&TCPServer::finishConnection,
185  this, _1)));
186 
187  // prune connections that finished uncleanly
188  pruneConnections();
189 
190  // keep track of the object in the server's connection pool
191  m_conn_pool.insert(new_connection);
192 
193  // use the object to accept a new connection
194  new_connection->async_accept(m_tcp_acceptor,
195  boost::bind(&TCPServer::handleAccept,
196  this, new_connection,
197  boost::asio::placeholders::error));
198  }
199 }
200 
201 void TCPServer::handleAccept(TCPConnectionPtr& tcp_conn,
202  const boost::system::error_code& accept_error)
203 {
204  if (accept_error) {
205  // an error occured while trying to a accept a new connection
206  // this happens when the server is being shut down
207  if (m_is_listening) {
208  listen(); // schedule acceptance of another connection
209  PION_LOG_WARN(m_logger, "Accept error on port " << getPort() << ": " << accept_error.message());
210  }
211  finishConnection(tcp_conn);
212  } else {
213  // got a new TCP connection
214  PION_LOG_DEBUG(m_logger, "New" << (tcp_conn->getSSLFlag() ? " SSL " : " ")
215  << "connection on port " << getPort());
216 
217  // schedule the acceptance of another new connection
218  // (this returns immediately since it schedules it as an event)
219  if (m_is_listening) listen();
220 
221  // handle the new connection
222 #ifdef PION_HAVE_SSL
223  if (tcp_conn->getSSLFlag()) {
224  tcp_conn->async_handshake_server(boost::bind(&TCPServer::handleSSLHandshake,
225  this, tcp_conn,
226  boost::asio::placeholders::error));
227  } else
228 #endif
229  // not SSL -> call the handler immediately
230  handleConnection(tcp_conn);
231  }
232 }
233 
234 void TCPServer::handleSSLHandshake(TCPConnectionPtr& tcp_conn,
235  const boost::system::error_code& handshake_error)
236 {
237  if (handshake_error) {
238  // an error occured while trying to establish the SSL connection
239  PION_LOG_WARN(m_logger, "SSL handshake failed on port " << getPort()
240  << " (" << handshake_error.message() << ')');
241  finishConnection(tcp_conn);
242  } else {
243  // handle the new connection
244  PION_LOG_DEBUG(m_logger, "SSL handshake succeeded on port " << getPort());
245  handleConnection(tcp_conn);
246  }
247 }
248 
249 void TCPServer::finishConnection(TCPConnectionPtr& tcp_conn)
250 {
251  boost::mutex::scoped_lock server_lock(m_mutex);
252  if (m_is_listening && tcp_conn->getKeepAlive()) {
253 
254  // keep the connection alive
255  handleConnection(tcp_conn);
256 
257  } else {
258  PION_LOG_DEBUG(m_logger, "Closing connection on port " << getPort());
259 
260  // remove the connection from the server's management pool
261  ConnectionPool::iterator conn_itr = m_conn_pool.find(tcp_conn);
262  if (conn_itr != m_conn_pool.end())
263  m_conn_pool.erase(conn_itr);
264 
265  // trigger the no more connections condition if we're waiting to stop
266  if (!m_is_listening && m_conn_pool.empty())
267  m_no_more_connections.notify_all();
268  }
269 }
270 
271 std::size_t TCPServer::pruneConnections(void)
272 {
273  // assumes that a server lock has already been acquired
274  ConnectionPool::iterator conn_itr = m_conn_pool.begin();
275  while (conn_itr != m_conn_pool.end()) {
276  if (conn_itr->unique()) {
277  PION_LOG_WARN(m_logger, "Closing orphaned connection on port " << getPort());
278  ConnectionPool::iterator erase_itr = conn_itr;
279  ++conn_itr;
280  (*erase_itr)->close();
281  m_conn_pool.erase(erase_itr);
282  } else {
283  ++conn_itr;
284  }
285  }
286 
287  // return the number of connections remaining
288  return m_conn_pool.size();
289 }
290 
291 std::size_t TCPServer::getConnections(void) const
292 {
293  boost::mutex::scoped_lock server_lock(m_mutex);
294  return (m_is_listening ? (m_conn_pool.size() - 1) : m_conn_pool.size());
295 }
296 
297 } // end namespace net
298 } // end namespace pion
void addActiveUser(void)
virtual void beforeStarting(void)
called before the TCP server starts listening for new connections
Definition: TCPServer.hpp:143
virtual void afterStopping(void)
called after the TCP server has stopped listing for new connections
Definition: TCPServer.hpp:146
void setSSLKeyFile(const std::string &pem_key_file)
Definition: TCPServer.cpp:162
static boost::shared_ptr< TCPConnection > create(boost::asio::io_service &io_service, SSLContext &ssl_context, const bool ssl_flag, ConnectionHandler finished_handler)
static void sleep(boost::uint32_t sleep_sec, boost::uint32_t sleep_nsec)
std::size_t getConnections(void) const
returns the number of active tcp connections
Definition: TCPServer.cpp:291
void join(void)
the calling thread will sleep until the server has stopped listening for connections ...
Definition: TCPServer.cpp:153
virtual void handleConnection(TCPConnectionPtr &tcp_conn)
Definition: TCPServer.hpp:137
void start(void)
starts listening for new connections
Definition: TCPServer.cpp:73
PionLogger m_logger
primary logging interface used by this class
Definition: TCPServer.hpp:153
TCPServer(const unsigned int tcp_port)
Definition: TCPServer.cpp:49
void removeActiveUser(void)
unregisters an active user with the thread scheduler
void close(void)
closes the tcp socket and cancels any pending asynchronous operations
unsigned int getPort(void) const
returns tcp port number that the server listens for connections on
Definition: TCPServer.hpp:63
void stop(bool wait_until_finished=false)
Definition: TCPServer.cpp:115
boost::asio::io_service & getIOService(void)
returns an async I/O service used to schedule work
Definition: TCPServer.hpp:149
void setSSLFlag(bool b=true)
sets value of SSL flag (true if the server uses SSL to encrypt connections)
Definition: TCPServer.hpp:84