import asyncio
import datetime
import time
from loguru import logger as log
from concurrent.futures import ThreadPoolExecutor
from tornado.web import Application
from tornado.ioloop import PeriodicCallback
from tornado.websocket import WebSocketHandler, WebSocketClosedError
class MyThreadPool:
executor = ThreadPoolExecutor(max_workers=5)
class MyWsHandler(WebSocketHandler, MyThreadPool):
def data_received(self, chunk: bytes):
pass
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.event_loop = None
self.request_message = None
def check_origin(self, origin):
"""允许跨域请求"""
return True
def on_ping(self, data):
print("ping", data)
# self.ping(str(round(time.time() * 1000)))
def on_pong(self, data):
print("pong", data)
if not data:
self.ping(str(round(datetime.datetime.now().timestamp() * 1000)))
def initialize(self):
"""为每个请求初始化"""
self.current_user = None
def prepare(self):
"""请求前置操作"""
pass
def open(self):
"""建立连接"""
log.info(f"建立连接: [{self.request.remote_ip}]-[{datetime.datetime.now()}]")
self.set_nodelay(True) # 设置无延迟
# 单位毫秒,上下浮动50%
self.event_loop = PeriodicCallback(callback=self.send_message, callback_time=1000, jitter=0.5)
self.event_loop.start()
def on_message(self, message):
"""接收前端信息"""
self.request_message = message
def on_close(self):
"""连接断开"""
log.info(f"连接断开: [{self.request.remote_ip}]-[{datetime.datetime.now()}]")
self.event_loop.stop()
async def send_message(self):
"""发送信息"""
log.info("接受的信息==>" + str(self.request_message))
if not self.request_message:
return
await asyncio.sleep(1)
if self.ws_connection is None or self.ws_connection.is_closing():
self.close()
return
for i in range(100):
time.sleep(1)
try:
await self.write_message(f"{i}")
except WebSocketClosedError as e:
print("连接断开")
break
class Main:
def __init__(self, debug=False):
self.debug = debug
self.urls = [
(r"/ws", MyWsHandler),
]
def mkApp(self):
return Application(
self.urls,
debug=self.debug,
# websocket_ping_interval=5, # 间隔5秒发送一次ping帧,第一次发送为触发的5s后
# websocket_ping_timeout=20 # 每次 ping 操作重置时间超时时间,若超时则断开连接,默认3次 ping 或 30s 断开
)
async def admin(self, host, port):
app = self.mkApp()
app.listen(address=host, port=port)
await asyncio.Event().wait()
def run(self, host, port):
log.info(f"启动服务: {host}:{port}")
asyncio.run(self.admin(host=host, port=port))
if __name__ == '__main__':
Main(debug=True).run(host="localhost", port=8765)
客户端使用 vue
<template>
<div>
<h2>websocket</h2>
<button class="button" @click="send1()" >发送</button>
<button class="button" @click="close()" >断开</button>
<button class="button" @click="connect()" >连接</button>
<div>
<h2>来自服务端的消息: {{ msg }}</h2>
</div>
</div>
</template>
<script>
export default {
name: 'WebSocket',
mounted () {
this.initWebSocket();
},
data() {
return {
msg:""
}
},
methods: {
connect(){
this.initWebSocket()
},
initWebSocket(){
this.websocket = new WebSocket("ws://localhost:8765/ws?a=1");
this.websocket.onmessage = this.websocketOnMessage;
this.websocket.onopen = this.websocketOnOpen;
this.websocket.onerror = this.websocketOnError;
this.websocket.onclose = this.websocketOnClose;
},
close(){
this.websocket.close()
},
websocketOnMessage(e){
this.msg = e.data
console.log("收到的消息",e.data);
},
websocketOnOpen(e){
console.log("链接成功",e);
},
websocketOnError(e){
console.log("连接失败",e);
alert("连接失败")
},
websocketOnClose(e){
console.log("断开连接");
this.websocket.close()
},
send1(){
this.websocket.send("来自 1号 的消息")
}
}
};
</script>
<style>
.button {
/* width: 30px; */
cursor: pointer;
border-radius: 20px;
/* border: 1px solid #ff4b2b;
background: #ff4b2b; */
/* border: 1px solid #fa8817;
background: #fa8817; */
border: 1px solid #1BBFB4;
background: #1BBFB4;
color: #fff;
font-size: 12px;
font-weight: bold;
padding: 35px 45px;
margin-top: 10%;
letter-spacing: 1px;
text-transform: uppercase;
transition: transform 80ms ease-in;
}
</style>