当前位置:   article > 正文

由浅入深介绍 Python Websocket 编程

python websocket


1. 为什么使用 Websocket ?

1.1 websocket 协议简介

Websocket协议是对http的改进,可以实现client 与 server之间的双向通信; websocket连接一旦建立就始终保持,直到client或server 中断连接,弥补了http无法保持长连接的不足,方便了客户端应用与服务器之间实时通信。

适用场景
  • html页面实时更新, 客户端的html页面内,用` javascript` 与 server 建立websocket连接,实现页面内容的实时更新。Websocket 非常适合网页游戏、聊天、证券交易等实时应用。
  • 要求保持长连接的实时通信的应用场景。 如基于位置的服务应用,物联网,多方协作软件,在线教育,带社交属性的手机APP等。

实时更新数据场景,为什么不使用AJAX?
AJAX 采用http, 如果要实时更新页面,则需要不断地发送http 请求,无论是否有数据更新,产生大量冗余通信流量。而websocket是长连接双向通信,有数据更新时,服务器向客户机发送通知。

1.2 基本原理

基于TCP,一次握手就能建立连接,支持双向通信,可保持长连接。

在这里插入图片描述

WebSocket 握手请求消息示例::

GET /chat HTTP/1.1
Host: normal-website.com
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: wDqumtseNBJdhkihL6PW7w==
Connection: keep-alive, Upgrade
Cookie: session=KOsEJNuflw4Rd9BDNrVmvwBF9rEijeE2
Upgrade: websocket
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如果 Server 接收连接,返回响应

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: 0FFP+2nmNIf/h+4BP36k9uzrYGk=
  • 1
  • 2
  • 3
  • 4

响应码为101,表示切换为websocket 协议。

websocket 已得到主流浏览器,各编程语言的广泛支持,基本都提供了WebSocket高阶编程API,在一般场合下,可以替代socket低阶函数编程。python 提供了更简洁的编程实现方式。下面展示了实例代码方式,说明如何开发 Python websocket 服务器代码,python websocket 客户端, 以及javascript websocket 代码。

2. 如何用 Python 搭建 Websocket 服务

python 第3方库 websockets 提供了websocket 实现框架,支持asyncio, 性能强大,稳定性好,可以用于生产环境。

2.1 安装websockets包

pip install websockets
  • 1

2.2 编写 server 端代码

Websocket服务端代码是面向多用户的长连接,因此本文采用了python3.7 版本的 asyncio 异步方式编写 websocket server 代码。

服务端也可使用 ThreadPoolExecutor 线程池方式同时处理多连接的场景,用户较多时,性能明显不如asyncio异步方式。

websockets 模块 server端的主要方法:

  • recv() 收消息
  • send() 发送消息
  • serve() 创建 server 对象

实现步骤:

  1. 编写websocket 异步任务处理函数handler
  2. 创建1个websocket server 对象
  3. 异步运行 server对象

websocket 地址格式:

  • ws://主机地址:端口号
  • wss://主机地址:端口号, wss表示此连接为https 连接。

下面是具体的代码 server.py

#!/usr/bin/python3
# 主要功能:创建1个基本的websocket server, 符合asyncio 开发要求
import asyncio
import websockets
from datetime import datetime


async def handler(websocket):
    data = await websocket.recv()
    reply = f"Data received as \"{data}\".  time: {datetime.now()}"
    print(reply)
    await websocket.send(reply)
    print("Send reply")


async def main():
    async with websockets.serve(handler, "localhost", 9999):
        await asyncio.Future()  # run forever

if __name__ == "__main__":
    asyncio.run(main())


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

服务端handler函数代码还有1种写法,适用性更好。

async def handler(websocket):
    async for message in websocket:
	    reply = f"Data received as \"{message}\".  time: {datetime.now()}"
	    print(reply)
        await websocket.send(reply)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Websocket协议本身有心跳机制、连接检测机制,服务端无须关心客户端状态,一旦有异常,会自动断开连接。

Websockets提供了交互式测试命令,现在可以快速测试一下服务端是否能正常工作:
(1) 启动服务器: python server.py
(2) 通过命令行连接服务端,并向发送hello, world 消息,可以看到,收到了服务器的响应。

D:\workplace\python\projects\websock>python -m websockets ws://localhost:9999
Connected to ws://localhost:9999.
> hello, world
< Data received as "hello, world".  time: 2023-04-01 09:24:14.787357
Connection closed: 1000 (OK).
  • 1
  • 2
  • 3
  • 4
  • 5

当然实际应用时,应按下面步骤来编写客户端代码。

3. Python websocket 客户端实现代码

websockets 客户端提供的主要方法:

  • connect() 建立与服务器的连接
  • recv(), send() 收发消息
  • close() 显式地关闭连接

下面看一下示例 client.py

import asyncio
import websockets
import time


async def ws_client(url):
    for i in range(1, 40):
        async with websockets.connect(url) as websocket:
            await websocket.send("Hello, I am PyPy.")
            response = await websocket.recv()
        print(response)
        time.sleep(1)

asyncio.run(ws_client('ws://localhost:9999'))


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

4. Javascript websocket 客户端实现代码

目前主流的浏览器都支持websocket协议。

Javascript websocket 对象的主要属性与方法:
请参考菜鸟教程的这篇文章:https://www.runoob.com/html/html5-websocket.html

示例代码: client.html

<!DOCTYPE HTML>
<html>

<head>
    <meta charset="utf-8">
    <title>websocket demo</title>
  	<meta name="viewport" content="width=device-width, initial-scale=1">
 	 <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.3.1/css/bootstrap.min.css">
	<script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js">		</script>
	<script src="https://cdn.staticfile.org/popper.js/1.15.0/umd/popper.min.js"></script>
  	<script src="https://cdn.staticfile.org/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>

    <script type="text/javascript">
        function WebSocketTest() {
            text = document.getElementById("div_text");
            if ("WebSocket" in window) {
                // 打开一个 web socket
                var ws = new WebSocket("ws://localhost:9999/handler");

                ws.onopen = function () {
                    // Web Socket 已连接上,使用 send() 方法发送数据
                    ws.send("Javscript发送的数据");
                    text.innerHTML = "数据发送中...";
                    alert("数据发送中...");
                };

                ws.onmessage = function (evt) {
                    var received_msg = evt.data;
                    text.innerHTML = "收到的数据:" + received_msg;
                    alert("数据已接收...");
                };

                ws.onclose = function () {
                    // 关闭 websocket
                    text.innerHTML = "连接已关闭...";
                    alert("连接已关闭...");
                };
            }

            else {
                // 浏览器不支持 WebSocket
                alert("您的浏览器不支持 WebSocket!");
            }
        }
    </script>

</head>

<body>

    <div class="col-md-6 m-5 p-2" id="div_ws">
        <a class="btn btn-primary" href="javascript:WebSocketTest()">连接WebSocket</a>
    </div>
    <div class="col-md-6 border border-primary mx-5 p-2 " id="div_text" style="margin:20px;height:100px;">
        display communicate text
    </div>

</body>

</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

5. 测试websocket

上述3个文件都放在同1个目录下,打开两个终端窗口,先运行server.py, 再运行 client,py。
Output结果
在这里插入图片描述
在chrome 或edge 中运行client.html, 可以看到websocket 连接建立,发送,接收,关闭各阶段的状态。
在这里插入图片描述
能够看到,服务器与客户端之间的通信是双向的,而且是长连接,客户端断开后,服务器仍然保持侦听状态,而且不需要accept操作。websocket发送、接收文件也不需要 socket 对发送窗口 buffer 进行控制,因此是 socket 开发非常好的替代。

注:Python异步websocket服务器最终性能与代码质量、服务器硬件、网络等紧密相关,可以使用 Websocket-benchmarker 测试工具来测试服务器。

6. 服务器向客户端广播消息

websockets 模块支持向所有连接的客户广播消息,
用1个简单的例子来演示,实现步骤:

  • 保存每个 websocket 客户连接
  • 向每个客户发送消息

将前面的server,.py 代码修改后如下:

#!/usr/bin/python3
# 主要功能:创建1个基本的websocket server, 符合asyncio 开发要求
import asyncio
import websockets
from datetime import datetime

# Set of connected clients
connected_clients = set()

async def handler(websocket, path):
    # Add the client to the connected clients set
    connected_clients.add(websocket)    
    try:
        # Keep listening for incoming messages from the client
        async for message in websocket:
            # Broadcast the message to all connected clients
            await broadcast(message)
    finally:
        connected_clients.remove(websocket) 
        
async def broadcast(message):
    # Broadcast the message to all connected clients
    for client in connected_clients:
        await client.send(message)

async def main():
    async with websockets.serve(handler, "localhost", 9998):
        await asyncio.Future()  # run forever
        loop = asyncio.get_running_loop() #获取当前event_loop对象
        loop.create_task(broadcast())     # 添加新的异步广播任务

if __name__ == "__main__":
    asyncio.run(main())

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

本例中,当服务器收到1条消息时,会广播给所有用户。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/85366
推荐阅读
相关标签
  

闽ICP备14008679号