tcpserver.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # Copyright 2011 Facebook
00004 #
00005 # Licensed under the Apache License, Version 2.0 (the "License"); you may
00006 # not use this file except in compliance with the License. You may obtain
00007 # a copy of the License at
00008 #
00009 #     http://www.apache.org/licenses/LICENSE-2.0
00010 #
00011 # Unless required by applicable law or agreed to in writing, software
00012 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
00013 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
00014 # License for the specific language governing permissions and limitations
00015 # under the License.
00016 
00017 """A non-blocking, single-threaded TCP server."""
00018 from __future__ import absolute_import, division, print_function, with_statement
00019 
00020 import errno
00021 import os
00022 import socket
00023 
00024 from tornado.log import app_log
00025 from tornado.ioloop import IOLoop
00026 from tornado.iostream import IOStream, SSLIOStream
00027 from tornado.netutil import bind_sockets, add_accept_handler, ssl_wrap_socket
00028 from tornado import process
00029 from tornado.util import errno_from_exception
00030 
00031 try:
00032     import ssl
00033 except ImportError:
00034     # ssl is not available on Google App Engine.
00035     ssl = None
00036 
00037 
00038 class TCPServer(object):
00039     r"""A non-blocking, single-threaded TCP server.
00040 
00041     To use `TCPServer`, define a subclass which overrides the `handle_stream`
00042     method.
00043 
00044     To make this server serve SSL traffic, send the ssl_options dictionary
00045     argument with the arguments required for the `ssl.wrap_socket` method,
00046     including "certfile" and "keyfile"::
00047 
00048        TCPServer(ssl_options={
00049            "certfile": os.path.join(data_dir, "mydomain.crt"),
00050            "keyfile": os.path.join(data_dir, "mydomain.key"),
00051        })
00052 
00053     `TCPServer` initialization follows one of three patterns:
00054 
00055     1. `listen`: simple single-process::
00056 
00057             server = TCPServer()
00058             server.listen(8888)
00059             IOLoop.instance().start()
00060 
00061     2. `bind`/`start`: simple multi-process::
00062 
00063             server = TCPServer()
00064             server.bind(8888)
00065             server.start(0)  # Forks multiple sub-processes
00066             IOLoop.instance().start()
00067 
00068        When using this interface, an `.IOLoop` must *not* be passed
00069        to the `TCPServer` constructor.  `start` will always start
00070        the server on the default singleton `.IOLoop`.
00071 
00072     3. `add_sockets`: advanced multi-process::
00073 
00074             sockets = bind_sockets(8888)
00075             tornado.process.fork_processes(0)
00076             server = TCPServer()
00077             server.add_sockets(sockets)
00078             IOLoop.instance().start()
00079 
00080        The `add_sockets` interface is more complicated, but it can be
00081        used with `tornado.process.fork_processes` to give you more
00082        flexibility in when the fork happens.  `add_sockets` can
00083        also be used in single-process servers if you want to create
00084        your listening sockets in some way other than
00085        `~tornado.netutil.bind_sockets`.
00086 
00087     .. versionadded:: 3.1
00088        The ``max_buffer_size`` argument.
00089     """
00090     def __init__(self, io_loop=None, ssl_options=None, max_buffer_size=None,
00091                  read_chunk_size=None):
00092         self.io_loop = io_loop
00093         self.ssl_options = ssl_options
00094         self._sockets = {}  # fd -> socket object
00095         self._pending_sockets = []
00096         self._started = False
00097         self.max_buffer_size = max_buffer_size
00098         self.read_chunk_size = None
00099 
00100         # Verify the SSL options. Otherwise we don't get errors until clients
00101         # connect. This doesn't verify that the keys are legitimate, but
00102         # the SSL module doesn't do that until there is a connected socket
00103         # which seems like too much work
00104         if self.ssl_options is not None and isinstance(self.ssl_options, dict):
00105             # Only certfile is required: it can contain both keys
00106             if 'certfile' not in self.ssl_options:
00107                 raise KeyError('missing key "certfile" in ssl_options')
00108 
00109             if not os.path.exists(self.ssl_options['certfile']):
00110                 raise ValueError('certfile "%s" does not exist' %
00111                                  self.ssl_options['certfile'])
00112             if ('keyfile' in self.ssl_options and
00113                     not os.path.exists(self.ssl_options['keyfile'])):
00114                 raise ValueError('keyfile "%s" does not exist' %
00115                                  self.ssl_options['keyfile'])
00116 
00117     def listen(self, port, address=""):
00118         """Starts accepting connections on the given port.
00119 
00120         This method may be called more than once to listen on multiple ports.
00121         `listen` takes effect immediately; it is not necessary to call
00122         `TCPServer.start` afterwards.  It is, however, necessary to start
00123         the `.IOLoop`.
00124         """
00125         sockets = bind_sockets(port, address=address)
00126         self.add_sockets(sockets)
00127 
00128     def add_sockets(self, sockets):
00129         """Makes this server start accepting connections on the given sockets.
00130 
00131         The ``sockets`` parameter is a list of socket objects such as
00132         those returned by `~tornado.netutil.bind_sockets`.
00133         `add_sockets` is typically used in combination with that
00134         method and `tornado.process.fork_processes` to provide greater
00135         control over the initialization of a multi-process server.
00136         """
00137         if self.io_loop is None:
00138             self.io_loop = IOLoop.current()
00139 
00140         for sock in sockets:
00141             self._sockets[sock.fileno()] = sock
00142             add_accept_handler(sock, self._handle_connection,
00143                                io_loop=self.io_loop)
00144 
00145     def add_socket(self, socket):
00146         """Singular version of `add_sockets`.  Takes a single socket object."""
00147         self.add_sockets([socket])
00148 
00149     def bind(self, port, address=None, family=socket.AF_UNSPEC, backlog=128):
00150         """Binds this server to the given port on the given address.
00151 
00152         To start the server, call `start`. If you want to run this server
00153         in a single process, you can call `listen` as a shortcut to the
00154         sequence of `bind` and `start` calls.
00155 
00156         Address may be either an IP address or hostname.  If it's a hostname,
00157         the server will listen on all IP addresses associated with the
00158         name.  Address may be an empty string or None to listen on all
00159         available interfaces.  Family may be set to either `socket.AF_INET`
00160         or `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise
00161         both will be used if available.
00162 
00163         The ``backlog`` argument has the same meaning as for
00164         `socket.listen <socket.socket.listen>`.
00165 
00166         This method may be called multiple times prior to `start` to listen
00167         on multiple ports or interfaces.
00168         """
00169         sockets = bind_sockets(port, address=address, family=family,
00170                                backlog=backlog)
00171         if self._started:
00172             self.add_sockets(sockets)
00173         else:
00174             self._pending_sockets.extend(sockets)
00175 
00176     def start(self, num_processes=1):
00177         """Starts this server in the `.IOLoop`.
00178 
00179         By default, we run the server in this process and do not fork any
00180         additional child process.
00181 
00182         If num_processes is ``None`` or <= 0, we detect the number of cores
00183         available on this machine and fork that number of child
00184         processes. If num_processes is given and > 1, we fork that
00185         specific number of sub-processes.
00186 
00187         Since we use processes and not threads, there is no shared memory
00188         between any server code.
00189 
00190         Note that multiple processes are not compatible with the autoreload
00191         module (or the ``autoreload=True`` option to `tornado.web.Application`
00192         which defaults to True when ``debug=True``).
00193         When using multiple processes, no IOLoops can be created or
00194         referenced until after the call to ``TCPServer.start(n)``.
00195         """
00196         assert not self._started
00197         self._started = True
00198         if num_processes != 1:
00199             process.fork_processes(num_processes)
00200         sockets = self._pending_sockets
00201         self._pending_sockets = []
00202         self.add_sockets(sockets)
00203 
00204     def stop(self):
00205         """Stops listening for new connections.
00206 
00207         Requests currently in progress may still continue after the
00208         server is stopped.
00209         """
00210         for fd, sock in self._sockets.items():
00211             self.io_loop.remove_handler(fd)
00212             sock.close()
00213 
00214     def handle_stream(self, stream, address):
00215         """Override to handle a new `.IOStream` from an incoming connection."""
00216         raise NotImplementedError()
00217 
00218     def _handle_connection(self, connection, address):
00219         if self.ssl_options is not None:
00220             assert ssl, "Python 2.6+ and OpenSSL required for SSL"
00221             try:
00222                 connection = ssl_wrap_socket(connection,
00223                                              self.ssl_options,
00224                                              server_side=True,
00225                                              do_handshake_on_connect=False)
00226             except ssl.SSLError as err:
00227                 if err.args[0] == ssl.SSL_ERROR_EOF:
00228                     return connection.close()
00229                 else:
00230                     raise
00231             except socket.error as err:
00232                 # If the connection is closed immediately after it is created
00233                 # (as in a port scan), we can get one of several errors.
00234                 # wrap_socket makes an internal call to getpeername,
00235                 # which may return either EINVAL (Mac OS X) or ENOTCONN
00236                 # (Linux).  If it returns ENOTCONN, this error is
00237                 # silently swallowed by the ssl module, so we need to
00238                 # catch another error later on (AttributeError in
00239                 # SSLIOStream._do_ssl_handshake).
00240                 # To test this behavior, try nmap with the -sT flag.
00241                 # https://github.com/tornadoweb/tornado/pull/750
00242                 if errno_from_exception(err) in (errno.ECONNABORTED, errno.EINVAL):
00243                     return connection.close()
00244                 else:
00245                     raise
00246         try:
00247             if self.ssl_options is not None:
00248                 stream = SSLIOStream(connection, io_loop=self.io_loop,
00249                                      max_buffer_size=self.max_buffer_size,
00250                                      read_chunk_size=self.read_chunk_size)
00251             else:
00252                 stream = IOStream(connection, io_loop=self.io_loop,
00253                                   max_buffer_size=self.max_buffer_size,
00254                                   read_chunk_size=self.read_chunk_size)
00255             self.handle_stream(stream, address)
00256         except Exception:
00257             app_log.error("Error in connection callback", exc_info=True)


rosbridge_tools
Author(s): Jonathan Mace
autogenerated on Sat Dec 27 2014 11:25:59