前言
websocket 是一种网络传输协定。可在单个 TCP 连贯上进行全双工通信。基于此,websocket 使得客户端与服务端的通信变得更加简便和高效。
什么是 websocket
websocket 是独立的、创立在 TCP 上的协定。该协定在 2008 年诞生,并在 2011 年成为国际标准。它的一个次要特点是——全双工,即一旦建设连贯,服务端或客户端能够被动向对方推送音讯。
在 websocket 呈现之前,网站如果须要实现推送技术,都是采纳轮询的形式,即浏览器每隔一段时间就向服务器发出请求。这种模式的毛病在于,浏览器须要一直向服务器发送申请,耗费很多的带宽资源。比拟新的 Comet 技术尽管也能够实现双向通信,但仍然须要重复发送申请,并且 Comet 中广泛采纳的 HTTP 长连贯也会耗费服务器资源。
基于以上的状况,HTML5 定义了 websocket 协定,可能更好的节俭服务器和带宽资源。并且实现高效的实时通信。目前,所有的浏览器都反对它。
websocket 通信原理和机制
websocket 尽管是一种新的协定,但它不可能脱离 http 独自存在,当客户端构建一个 websocket 实例,并且向服务端连贯时,会首先发动一个 http 报文申请。通知服务端须要将通信协议切换至 websocket。
如果服务端反对 websocket 协定,那么它会将通信协议切换至 websocket 并且返回响应报文。此时的返回状态码是 101,表示同意协定转换申请,接下来便能够进行数据传输了。
websocket 之所以借助 HTTP 实现握手协定,是因为有良好的兼容性,默认端口是 80 和 443。握手阶段不容易被防火墙屏蔽。
websocket 的特点
- 开销小,服务器和客户端替换数据时,协定包头部蕴含较少的信息
- 实时性高,协定采纳全双工,绝对于 http 申请客户端发动申请,服务端能力响应的模式,提早显著更低
- 与 HTTP 有良好的兼容性,默认端口是 80 和 443。握手采纳 HTTP 协定,不容易被防火墙屏蔽
- 反对文本和二进制数据传输
- 反对自定义拓展,用户能够本人实现自定义的子协定
- 通过心跳机制放弃服务端与客户端的长连贯
构建实时日志跟踪的小例子
服务端开启一个监听日志脚本的服务,服务会限度容许拜访的门路范畴(避免黑客利用程序破绽,扫描整个服务器);服务器通过解析客户端的申请,返回日志的音讯内容给客户端;服务器定时发送心跳检测给客户端,如果没有收到客户端的响应,则断开连接
服务端外围程序代码逻辑如下
with open(file_path) as f:
# 首次读取指定行数(NUM_LINES)的日志文件,发送给客户端
content = ''.join(deque(f, NUM_LINES))
content = conv.convert(content, full=False)
await websocket.send(content)
# 如果发现客户端有 tail 申请,则进行 tail 日志追踪
if tail:
# 首先创立发动这次申请的心跳工夫
last_heartbeat = time.time()
while True:
# 每次 tail 服务端最新的日志记录,返回给客户端
content = f.read()
if content:
content = conv.convert(content, full=False)
await websocket.send(content)
else:
await asyncio.sleep(1)
# 检测这次申请距上一次发动申请,是不是曾经超过了最长心跳检测时长,如果是,发动心跳检测
if time.time() - last_heartbeat > HEARTBEAT_INTERVAL:
try:
await websocket.send('ping')
pong = await asyncio.wait_for(websocket.recv(), 5)
logger.info(f"pong:{pong}")
if pong != 'pong':
raise Exception()
except Exception:
raise Exception('Ping error')
else:
last_heartbeat = time.time()
else:
await websocket.close()
客户端就非常简单了,监听服务端日志的文件,发现有新的日志产生则输入日志或者间接将日志实时展现在前端页面上。相应地,如果须要长期监听,那么当服务端发送心跳检测的信号过去,也须要回应响应的心跳反馈
客户端外围代码逻辑如下
async def consumer_handler(websocket: WebSocketClientProtocol) -> None:
async for message in websocket:
log_message(message)
if message == "ping":
await websocket.send("pong")
async def cousume(hostname: str, port: int, log_file: str, tail:bool=True) -> None:
websocket_resource_url = f"ws://{hostname}:{port}{log_file}"
if tail:
websocket_resource_url = f"{websocket_resource_url}?tail=1"
async with websockets.connect(websocket_resource_url) as websocket:
await consumer_handler(websocket)
def log_message(message: str) -> None:
logger.info(f"Message: {message}")
这里模仿一个日志生产文件
代码逻辑如下
import os
from loguru import logger
class LoggerExtend(object):
# 寄存目录名称
folder = '../logs'
def __init__(self, filename, folder=None):
self.folder = folder or self.folder
if not os.path.exists(self.folder):
os.mkdir(self.folder)
self.file = self.folder + '/' + filename
logger.add(self.file, rotation="100 MB")
@property
def get_logger(self):
return logger
if __name__ == '__main__':
logger = LoggerExtend(os.path.basename(__file__).replace(".py", ".log")).get_logger
import time
while True:
logger.info("你好 aaa")
最初顺次启动日志生产程序→服务端程序→客户端程序
日志生产文件启动后,运行成果如下
服务端启动程序运行,无运行日志产生
这时候启动客户端程序,运行成果如下
此时,服务端会产生相应的运行日志,如下所示
残缺代码请移步至 GitHub 查看
https://github.com/hacksman/l…
日志生产程序门路:
common/logger_extend.py
服务端程序门路:
websoctet_lab/log_server.py
客户端程序门路:
websoctet_lab/cousumer_log_view.py
参考资料
[1] How To Create a WebSocket in Python
[2] How To Create a WebSocket in Python
[3] WebSocket 教程
[4] WebSocket – 基于 Python 的支流实现形式总结_LIN 的博客 -CSDN 博客
[5] GoEasy | 更简略的 Websocket | Web 音讯推送专家
[6] Python WebSocket Client 實作
[7] 实践联系实际:从零了解 WebSocket 的通信原理、协定格局、安全性 - 网页端 IM 开发 / 专项技术区 – 即时通讯开发者社区!
[8] WebSocket 详解(一):初步意识 WebSocket 技术 - 网页端 IM 开发 / 专项技术区 – 即时通讯开发者社区!
[9] Log Tailer with WebSocket and Python