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