11.02 小节完成~
This commit is contained in:
@@ -5,20 +5,20 @@
|
||||
----------
|
||||
问题
|
||||
----------
|
||||
You want to implement a server that communicates with clients using the TCP Internet
|
||||
protocol.
|
||||
你想实现一个服务器,通过TCP协议和客户端通信。
|
||||
|
||||
|
|
||||
|
||||
----------
|
||||
解决方案
|
||||
----------
|
||||
An easy way to create a TCP server is to use the socketserver library. For example,
|
||||
here is a simple echo server:
|
||||
创建一个TCP服务器的一个简单方法是使用 ``socketserver`` 库。例如,下面是一个简单的应答服务器:
|
||||
|
||||
from socketserver import BaseRequestHandler, TCPServer
|
||||
.. code-block:: python
|
||||
|
||||
class EchoHandler(BaseRequestHandler):
|
||||
from socketserver import BaseRequestHandler, TCPServer
|
||||
|
||||
class EchoHandler(BaseRequestHandler):
|
||||
def handle(self):
|
||||
print('Got connection from', self.client_address)
|
||||
while True:
|
||||
@@ -28,31 +28,33 @@ class EchoHandler(BaseRequestHandler):
|
||||
break
|
||||
self.request.send(msg)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == '__main__':
|
||||
serv = TCPServer(('', 20000), EchoHandler)
|
||||
serv.serve_forever()
|
||||
|
||||
In this code, you define a special handler class that implements a handle() method for
|
||||
servicing client connections. The request attribute is the underlying client socket and
|
||||
client_address has client address.
|
||||
To test the server, run it and then open a separate Python process that connects to it:
|
||||
在这段代码中,你定义了一个特殊的处理类,实现了一个 ``handle()`` 方法,用来为客户端连接服务。
|
||||
``request`` 属性是客户端socket,``client_address`` 有客户端地址。
|
||||
为了测试这个服务器,运行它并打开另外一个Python进程连接这个服务器:
|
||||
|
||||
>>> from socket import socket, AF_INET, SOCK_STREAM
|
||||
>>> s = socket(AF_INET, SOCK_STREAM)
|
||||
>>> s.connect(('localhost', 20000))
|
||||
>>> s.send(b'Hello')
|
||||
5
|
||||
>>> s.recv(8192)
|
||||
b'Hello'
|
||||
>>>
|
||||
.. code-block:: python
|
||||
|
||||
In many cases, it may be easier to define a slightly different kind of handler. Here is an
|
||||
example that uses the StreamRequestHandler base class to put a file-like interface on
|
||||
the underlying socket:
|
||||
>>> from socket import socket, AF_INET, SOCK_STREAM
|
||||
>>> s = socket(AF_INET, SOCK_STREAM)
|
||||
>>> s.connect(('localhost', 20000))
|
||||
>>> s.send(b'Hello')
|
||||
5
|
||||
>>> s.recv(8192)
|
||||
b'Hello'
|
||||
>>>
|
||||
|
||||
from socketserver import StreamRequestHandler, TCPServer
|
||||
很多时候,可以很容易的定义一个不同的处理器。下面是一个使用 ``StreamRequestHandler``
|
||||
基类将一个类文件接口放置在底层socket上的例子:
|
||||
|
||||
class EchoHandler(StreamRequestHandler):
|
||||
.. code-block:: python
|
||||
|
||||
from socketserver import StreamRequestHandler, TCPServer
|
||||
|
||||
class EchoHandler(StreamRequestHandler):
|
||||
def handle(self):
|
||||
print('Got connection from', self.client_address)
|
||||
# self.rfile is a file-like object for reading
|
||||
@@ -60,7 +62,7 @@ class EchoHandler(StreamRequestHandler):
|
||||
# self.wfile is a file-like object for writing
|
||||
self.wfile.write(line)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == '__main__':
|
||||
serv = TCPServer(('', 20000), EchoHandler)
|
||||
serv.serve_forever()
|
||||
|
||||
@@ -69,28 +71,28 @@ if __name__ == '__main__':
|
||||
----------
|
||||
讨论
|
||||
----------
|
||||
socketserver makes it relatively easy to create simple TCP servers. However, you
|
||||
should be aware that, by default, the servers are single threaded and can only serve one
|
||||
client at a time. If you want to handle multiple clients, either instantiate a ForkingTCP
|
||||
Server or ThreadingTCPServer object instead. For example:
|
||||
``socketserver`` 可以让我们很容易的创建简单的TCP服务器。
|
||||
但是,你需要注意的是,默认情况下这种服务器是单线程的,一次只能为一个客户端连接服务。
|
||||
如果你想处理多个客户端,可以初始化一个 ``ForkingTCPServer`` 或者是 ``ThreadingTCPServer`` 对象。例如:
|
||||
|
||||
from socketserver import ThreadingTCPServer
|
||||
...
|
||||
.. code-block:: python
|
||||
|
||||
if __name__ == '__main__':
|
||||
from socketserver import ThreadingTCPServer
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
serv = ThreadingTCPServer(('', 20000), EchoHandler)
|
||||
serv.serve_forever()
|
||||
|
||||
One issue with forking and threaded servers is that they spawn a new process or thread
|
||||
on each client connection. There is no upper bound on the number of allowed clients,
|
||||
so a malicious hacker could potentially launch a large number of simultaneous con‐
|
||||
nections in an effort to make your server explode.
|
||||
If this is a concern, you can create a pre-allocated pool of worker threads or processes.
|
||||
To do this, you create an instance of a normal nonthreaded server, but then launch the
|
||||
serve_forever() method in a pool of multiple threads. For example:
|
||||
使用fork或线程服务器有个潜在问题就是它们会为每个客户端连接创建一个新的进程或线程。
|
||||
由于客户端连接数是没有限制的,因此一个恶意的黑客可以同时发送大量的连接让你的服务器奔溃。
|
||||
|
||||
...
|
||||
if __name__ == '__main__':
|
||||
如果你担心这个问题,你可以创建一个预先分配大小的工作线程池或进程池。
|
||||
你先创建一个普通的非线程服务器,然后在一个线程池中使用 ``serve_forever()`` 方法来启动它们。
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if __name__ == '__main__':
|
||||
from threading import Thread
|
||||
NWORKERS = 16
|
||||
serv = TCPServer(('', 20000), EchoHandler)
|
||||
@@ -100,11 +102,12 @@ if __name__ == '__main__':
|
||||
t.start()
|
||||
serv.serve_forever()
|
||||
|
||||
Normally, a TCPServer binds and activates the underlying socket upon instantiation.
|
||||
However, sometimes you might want to adjust the underlying socket by setting options.
|
||||
To do this, supply the bind_and_activate=False argument, like this:
|
||||
一般来讲,一个 ``TCPServer`` 在实例化的时候会绑定并激活相应的 ``socket`` 。
|
||||
不过,有时候你想通过设置某些选项去调整底下的 `socket`` ,可以设置参数 ``bind_and_activate=False`` 。如下:
|
||||
|
||||
if __name__ == '__main__':
|
||||
.. code-block:: python
|
||||
|
||||
if __name__ == '__main__':
|
||||
serv = TCPServer(('', 20000), EchoHandler, bind_and_activate=False)
|
||||
# Set up various socket options
|
||||
serv.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
|
||||
@@ -113,26 +116,25 @@ if __name__ == '__main__':
|
||||
serv.server_activate()
|
||||
serv.serve_forever()
|
||||
|
||||
The socket option shown is actually a very common setting that allows the server to
|
||||
rebind to a previously used port number. It’s actually so common that it’s a class variable
|
||||
that can be set on TCPServer. Set it before instantiating the server, as shown in this
|
||||
example:
|
||||
上面的 ``socket`` 选项是一个非常普遍的配置项,它允许服务器重新绑定一个之前使用过的端口号。
|
||||
由于要被经常使用到,它被放置到类变量中,可以直接在 ``TCPServer`` 上面设置。
|
||||
在实例化服务器的时候去设置它的值,如下所示:
|
||||
|
||||
...
|
||||
if __name__ == '__main__':
|
||||
.. code-block:: python
|
||||
|
||||
if __name__ == '__main__':
|
||||
TCPServer.allow_reuse_address = True
|
||||
serv = TCPServer(('', 20000), EchoHandler)
|
||||
serv.serve_forever()
|
||||
|
||||
In the solution, two different handler base classes were shown (BaseRequestHandler
|
||||
and StreamRequestHandler). The StreamRequestHandler class is actually a bit more
|
||||
在上面示例中,我们演示了两种不同的处理器基类( ``BaseRequestHandler`` 和 ``StreamRequestHandler`` )。
|
||||
``StreamRequestHandler`` 更加灵活点,能通过设置其他的类变量来支持一些新的特性。比如:
|
||||
|
||||
flexible, and supports some features that can be enabled through the specification of
|
||||
additional class variables. For example:
|
||||
.. code-block:: python
|
||||
|
||||
import socket
|
||||
import socket
|
||||
|
||||
class EchoHandler(StreamRequestHandler):
|
||||
class EchoHandler(StreamRequestHandler):
|
||||
# Optional settings (defaults shown)
|
||||
timeout = 5 # Timeout on all socket operations
|
||||
rbufsize = -1 # Read buffer size
|
||||
@@ -147,14 +149,15 @@ class EchoHandler(StreamRequestHandler):
|
||||
except socket.timeout:
|
||||
print('Timed out!')
|
||||
|
||||
Finally, it should be noted that most of Python’s higher-level networking modules (e.g.,
|
||||
HTTP, XML-RPC, etc.) are built on top of the socketserver functionality. That said,
|
||||
it is also not difficult to implement servers directly using the socket library as well. Here
|
||||
is a simple example of directly programming a server with Sockets:
|
||||
最后,还需要注意的是巨大部分Python的高层网络模块(比如HTTP、XML-RPC等)都是建立在 ``socketserver`` 功能之上。
|
||||
也就是说,直接使用 ``socket`` 库来实现服务器也并不是很难。
|
||||
下面是一个使用 ``socket`` 直接编程实现的一个服务器简单例子:
|
||||
|
||||
from socket import socket, AF_INET, SOCK_STREAM
|
||||
.. code-block:: python
|
||||
|
||||
def echo_handler(address, client_sock):
|
||||
from socket import socket, AF_INET, SOCK_STREAM
|
||||
|
||||
def echo_handler(address, client_sock):
|
||||
print('Got connection from {}'.format(address))
|
||||
while True:
|
||||
msg = client_sock.recv(8192)
|
||||
@@ -163,7 +166,7 @@ def echo_handler(address, client_sock):
|
||||
client_sock.sendall(msg)
|
||||
client_sock.close()
|
||||
|
||||
def echo_server(address, backlog=5):
|
||||
def echo_server(address, backlog=5):
|
||||
sock = socket(AF_INET, SOCK_STREAM)
|
||||
sock.bind(address)
|
||||
sock.listen(backlog)
|
||||
@@ -171,6 +174,6 @@ def echo_server(address, backlog=5):
|
||||
client_sock, client_addr = sock.accept()
|
||||
echo_handler(client_addr, client_sock)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == '__main__':
|
||||
echo_server(('', 20000))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user