实时 Python 通过 TCP(第一部分)





0/5 (0投票)
如何使用 Tornado 框架实现异步回显 TCP 服务器和客户端
简而言之
在本篇文章中,您将学习如何使用 Tornado 框架实现异步回显 TCP 服务器和客户端。所有代码均在 GitHub 上。
路线图
- 这是系列文章的第一篇,将重点介绍如何在 Tornado 中构建简单的回显 TCP 服务器,以展示实现细节和协程的使用。
- 在下一篇文章中,我们将更详细地介绍如何将 Tornado 连接到 Redis PUB/SUB 机制,以便实时传递数据更新。
- 最后一篇将介绍如何实现一个简单的协议来控制客户端-服务器交互。
背景
如今,随着实时 Web 全面普及,越来越多的应用程序需要改进服务器和客户端之间复杂的交互机制。通知、实时聊天、流畅的数据流是所谓的“实时”交互的常见示例,在这种交互中,服务器不仅负责接收来自客户端的请求并响应它们,还能主动向客户端生成活动。
所有这些功能都得益于推送技术。如今实现这一目标的一个更流行的方法是使用WebSockets 协议。它与 HTTP 非常相似,并提供服务器和客户端之间的双向连接。
如文档中所述
WebSocket 协议是一个独立的、基于 TCP 的协议。它与 HTTP 的唯一关系是,它的握手请求被 HTTP 服务器解释为升级请求。
所以,归根结底,它只是具有特定服务器和客户端行为约定的一种特殊 TCP 连接。
我将要描述的解决方案略有不同。我建议使用原始 TCP 协议,这样我们可以玩转底层的东西,也可以了解如何在 WebSockets 不可用的环境中使用推送连接。它也可能比使用 WebSocket 稍快一点,尽管我不建议过分依赖这一点,因为实验表明 WebSockets 已经得到了极好的优化。
回显 TCP 服务器
那么,让我们开始实现一个简单的 TCP 回显服务器,它只监听特定端口上的连接,并以接收到的任何数据进行响应。
我们将使用 Tornado 异步 Web 框架来完成这项任务,它被证明非常健壮,并且能够处理数万个并发连接。另一种解决方案是使用标准的 Python socketserver 框架。
这里的代码仅适用于 Python 3。如果您想在 Python 2 上运行它,您需要对协程的返回点进行一些小的调整。有关更多详细信息,请参阅此处。
这是 Tornado 服务器的快速预览: 在 GitHub 上查看此代码片段。
我们继承了 tornado.tcpserver.TCPServer
。为了使其正常工作,唯一需要重写的函数是 handle_stream
。它接收一个 stream
,代表包裹在特殊 Tornado 对象中的客户端套接字连接,以及客户端地址,这是一个简单的元组,形式为 ('127.0.0.1', 55196)
。函数体包含一个无限循环,使用 stream.read_until
从套接字读取数据,并通过调用 stream.write
将相同的数据写回客户端。套接字的意外关闭绝不是一个意料之外的情况,因为连接可能会以各种方式被切断(路由错误、客户端关闭应用程序、超时等)。这就是为什么我们在我们这边清理套接字资源,并在这种情况下从函数返回。
这里棘手的部分是 handle_stream
不是一个典型的函数。它被称为协程,是一种在异步编程中使用的特殊函数。您可以看到第 14 行和第 19 行中的 yield
调用。它们看起来像生成器表达式中的返回点,但实际上,这些是当前函数执行中断,并且其他协程可能会在请求的值返回之前运行的地方。然后,执行会继续,好像什么都没发生一样。
协程的主题非常广泛,绝对超出了本文的范围,因此请查看 Tornado 文档和 Python asyncio 文档以获取详细解释。现在,您可以将它们视为常规函数调用。
服务器的启动也值得一提: 在 GitHub 上查看此代码片段。
当 IOLoop
实例启动并开始处理提交的协程时,服务器就开始工作了。在 IOLoop
退出后,整个程序就结束了。
客户端代码更简单: 在 GitHub 上查看此代码片段。
在这里,我们异步连接到指定的套接字(主机/端口对),并启动一个无限循环,等待从控制台输入数据,将这些数据写入套接字,并读取服务器的响应。
最后,客户端的启动代码与服务器的类似: 在 GitHub 上查看此代码片段。
请注意,在服务器和客户端中,我们都使用 msg_separator = b'\r\n'
来结束消息。原因是 TCP 在网络上传输原始字节数据,无法知道第一个消息的结束位置和下一个消息的开始位置。用分隔符结束消息是实现 TCP 套接字通信协议的一种方式。 此处对此类最常见的方法(包括此方法)进行了很好的概述。
现在我们可以启动所有东西并看看它是如何工作的。
启动服务器
$ python real_time/server.py
Starting the server...
启动客户端
$ python real_time/client.py
Connecting to server socket...
>> echo me
b'echo me'
您可以在单独的终端中启动任意数量的客户端,并看到每个人都收到了自己的消息反馈。
结论
现在,这里的代码本身并不特别有用,但我们将在此基础上进行改进,以获得一些很酷的功能。
如开头所述,此回显服务器/客户端的代码在 GitHub 上,请克隆、分叉、尝试运行并自己尝试。就这些了。不要争吵,保持冷静。
参考文献
- 推送技术
- Python TCP
- docs.python.org: socket.py
- docs.python.org: socketserver.py
- tornadoweb.org: Tornado framework
- pymotw.com: TCP/IP Client and Server
- binarytides.com: Code a simple socket server in Python
- curiousefficiency.org: TCP echo client and server in Python 3.5
- gist.github.com: Simple example of creating a socket server with Tornado
历史
- 2018年6月2日: 初始版本