|
5 | 5 | ----------
|
6 | 6 | 问题
|
7 | 7 | ----------
|
8 |
| -You want to implement a server that communicates with clients using the UDP Internet |
9 |
| -protocol. |
| 8 | +你想实现一个基于UDP协议的服务器来与客户端通信。 |
10 | 9 |
|
11 | 10 | |
|
12 | 11 |
|
13 | 12 | ----------
|
14 | 13 | 解决方案
|
15 | 14 | ----------
|
16 |
| -As with TCP, UDP servers are also easy to create using the socketserver library. For |
17 |
| -example, here is a simple time server: |
18 |
| - |
19 |
| -from socketserver import BaseRequestHandler, UDPServer |
20 |
| -import time |
21 |
| - |
22 |
| -class TimeHandler(BaseRequestHandler): |
23 |
| - def handle(self): |
24 |
| - print('Got connection from', self.client_address) |
25 |
| - # Get message and client socket |
26 |
| - msg, sock = self.request |
27 |
| - resp = time.ctime() |
28 |
| - sock.sendto(resp.encode('ascii'), self.client_address) |
29 |
| - |
30 |
| -if __name__ == '__main__': |
31 |
| - serv = UDPServer(('', 20000), TimeHandler) |
32 |
| - serv.serve_forever() |
33 |
| - |
34 |
| -As before, you define a special handler class that implements a handle() method for |
35 |
| -servicing client connections. The request attribute is a tuple that contains the incoming |
36 |
| -datagram and underlying socket object for the server. The client_address contains |
37 |
| -the client address. |
38 |
| -To test the server, run it and then open a separate Python process that sends messages |
39 |
| -to it: |
40 |
| - |
41 |
| ->>> from socket import socket, AF_INET, SOCK_DGRAM |
42 |
| ->>> s = socket(AF_INET, SOCK_DGRAM) |
43 |
| ->>> s.sendto(b'', ('localhost', 20000)) |
44 |
| -0 |
45 |
| ->>> s.recvfrom(8192) |
46 |
| -(b'Wed Aug 15 20:35:08 2012', ('127.0.0.1', 20000)) |
47 |
| ->>> |
| 15 | +跟TCP一样,UDP服务器也可以通过使用 ``socketserver`` 库很容易的被创建。 |
| 16 | +例如,下面是一个简单的时间服务器: |
| 17 | + |
| 18 | +.. code-block:: python |
| 19 | +
|
| 20 | + from socketserver import BaseRequestHandler, UDPServer |
| 21 | + import time |
| 22 | +
|
| 23 | + class TimeHandler(BaseRequestHandler): |
| 24 | + def handle(self): |
| 25 | + print('Got connection from', self.client_address) |
| 26 | + # Get message and client socket |
| 27 | + msg, sock = self.request |
| 28 | + resp = time.ctime() |
| 29 | + sock.sendto(resp.encode('ascii'), self.client_address) |
| 30 | +
|
| 31 | + if __name__ == '__main__': |
| 32 | + serv = UDPServer(('', 20000), TimeHandler) |
| 33 | + serv.serve_forever() |
| 34 | +
|
| 35 | +跟之前一样,你先定义一个实现 ``handle()`` 特殊方法的类,为客户端连接服务。 |
| 36 | +这个类的 ``request`` 属性是一个包含了数据报和底层socket对象的元组。``client_address`` 包含了客户端地址。 |
| 37 | + |
| 38 | +我们来测试下这个服务器,首先运行它,然后打开另外一个Python进程向服务器发送消息: |
| 39 | + |
| 40 | +.. code-block:: python |
| 41 | +
|
| 42 | + >>> from socket import socket, AF_INET, SOCK_DGRAM |
| 43 | + >>> s = socket(AF_INET, SOCK_DGRAM) |
| 44 | + >>> s.sendto(b'', ('localhost', 20000)) |
| 45 | + 0 |
| 46 | + >>> s.recvfrom(8192) |
| 47 | + (b'Wed Aug 15 20:35:08 2012', ('127.0.0.1', 20000)) |
| 48 | + >>> |
48 | 49 |
|
49 | 50 | |
|
50 | 51 |
|
51 | 52 | ----------
|
52 | 53 | 讨论
|
53 | 54 | ----------
|
54 |
| -A typical UDP server receives an incoming datagram (message) along with a client |
55 |
| -address. If the server is to respond, it sends a datagram back to the client. For trans‐ |
56 |
| -mission of datagrams, you should use the sendto() and recvfrom() methods of a |
57 |
| - |
58 |
| -socket. Although the traditional send() and recv() methods also might work, the for‐ |
59 |
| -mer two methods are more commonly used with UDP communication. |
60 |
| -Given that there is no underlying connection, UDP servers are often much easier to |
61 |
| -write than a TCP server. However, UDP is also inherently unreliable (e.g., no “connec‐ |
62 |
| -tion” is established and messages might be lost). Thus, it would be up to you to figure |
63 |
| -out how to deal with lost messages. That’s a topic beyond the scope of this book, but |
64 |
| -typically you might need to introduce sequence numbers, retries, timeouts, and other |
65 |
| -mechanisms to ensure reliability if it matters for your application. UDP is often used in |
66 |
| -cases where the requirement of reliable delivery can be relaxed. For instance, in real- |
67 |
| -time applications such as multimedia streaming and games where there is simply no |
68 |
| -option to go back in time and recover a lost packet (the program simply skips it and |
69 |
| -keeps moving forward). |
70 |
| -The UDPServer class is single threaded, which means that only one request can be serv‐ |
71 |
| -iced at a time. In practice, this is less of an issue with UDP than with TCP connections. |
72 |
| -However, should you want concurrent operation, instantiate a ForkingUDPServer or |
73 |
| -ThreadingUDPServer object instead: |
74 |
| - |
75 |
| -from socketserver import ThreadingUDPServer |
76 |
| -... |
77 |
| -if __name__ == '__main__': |
78 |
| - serv = ThreadingUDPServer(('',20000), TimeHandler) |
79 |
| - serv.serve_forever() |
80 |
| - |
81 |
| -Implementing a UDP server directly using sockets is also not difficult. Here is an |
82 |
| -example: |
83 |
| - |
84 |
| -from socket import socket, AF_INET, SOCK_DGRAM |
85 |
| -import time |
86 |
| - |
87 |
| -def time_server(address): |
88 |
| - sock = socket(AF_INET, SOCK_DGRAM) |
89 |
| - sock.bind(address) |
90 |
| - while True: |
91 |
| - msg, addr = sock.recvfrom(8192) |
92 |
| - print('Got message from', addr) |
93 |
| - resp = time.ctime() |
94 |
| - sock.sendto(resp.encode('ascii'), addr) |
95 |
| - |
96 |
| -if __name__ == '__main__': |
97 |
| - time_server(('', 20000)) |
| 55 | +一个典型的UPD服务器接收到达的数据报(消息)和客户端地址。如果服务器需要做应答, |
| 56 | +它要给客户端回发一个数据报。对于数据报的传送, |
| 57 | +你应该使用socket的 ``sendto()`` 和 ``recvfrom()`` 方法。 |
| 58 | +尽管传统的 ``send()`` 和 ``recv()`` 也可以达到同样的效果, |
| 59 | +但是前面的两个方法对于UDP连接而言更普遍。 |
| 60 | + |
| 61 | +由于没有底层的连接,UPD服务器相对于TCP服务器来讲实现起来更加简单。 |
| 62 | +不过,UDP天生是不可靠的(因为通信没有建立连接,消息可能丢失)。 |
| 63 | +因此需要由你自己来决定该怎样处理丢失消息的情况。这个已经不在本书讨论范围内了, |
| 64 | +不过通常来说,如果可靠性对于你程序很重要,你需要借助于序列号、重试、超时以及一些其他方法来保证。 |
| 65 | +UDP通常被用在那些对于可靠传输要求不是很高的场合。例如,在实时应用如多媒体流以及游戏领域, |
| 66 | +无需返回恢复丢失的数据包(程序只需简单的忽略它并继续向前运行)。 |
| 67 | + |
| 68 | +``UDPServer`` 类是单线程的,也就是说一次只能为一个客户端连接服务。 |
| 69 | +实际使用中,这个无论是对于UDP还是TCP都不是什么大问题。 |
| 70 | +如果你想要并发操作,可以实例化一个 ``ForkingUDPServer`` 或 ``ThreadingUDPServer`` 对象: |
| 71 | + |
| 72 | +.. code-block:: python |
| 73 | +
|
| 74 | + from socketserver import ThreadingUDPServer |
| 75 | +
|
| 76 | + if __name__ == '__main__': |
| 77 | + serv = ThreadingUDPServer(('',20000), TimeHandler) |
| 78 | + serv.serve_forever() |
| 79 | +
|
| 80 | +直接使用 ``socket`` 来是想一个UDP服务器也不难,下面是一个例子: |
| 81 | + |
| 82 | +.. code-block:: python |
| 83 | +
|
| 84 | + from socket import socket, AF_INET, SOCK_DGRAM |
| 85 | + import time |
| 86 | +
|
| 87 | + def time_server(address): |
| 88 | + sock = socket(AF_INET, SOCK_DGRAM) |
| 89 | + sock.bind(address) |
| 90 | + while True: |
| 91 | + msg, addr = sock.recvfrom(8192) |
| 92 | + print('Got message from', addr) |
| 93 | + resp = time.ctime() |
| 94 | + sock.sendto(resp.encode('ascii'), addr) |
| 95 | +
|
| 96 | + if __name__ == '__main__': |
| 97 | + time_server(('', 20000)) |
98 | 98 |
|
0 commit comments