如何实现websocket
抄的老刘博客 https://zhuanlan.zhihu.com/p/371500343
.
.
.
WebSocket介绍
WebSocket是一种通信协议,可在单个TCP连接上进行全双工通信。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以建立持久性的连接,
并进行双向数据传输。
说白了就是WebSocket可以主动向客户端推送消息实现双工通信
.
.
.
.
.
1.1 客户端向服务端发送建立连接的 请求
# 主要就是客户端带着WebSocket-Key对应的串给服务端,服务端拿到该串后和一个
GET / HTTP/1.1\r\n # 请求首行,握手阶段还是使用http协议
Host: 127.0.0.1:8080\r\n # 请求头
Connection: Upgrade\r\n # 表示要升级协议
Pragma: no-cache\r\n
Cache-Control: no-cache\r\n
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36\r\n
Upgrade: websocket\r\n # 要升级协议到websocket协议
Origin: http://localhost:63342\r\n
Sec-WebSocket-Version: 13\r\n
# 表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,
# 里面包含服务端支持的版本号
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
Sec-WebSocket-Key: 07EWNDBSpegw1vfsIBJtkg==\r\n
# 对应服务端响应头的Sec-WebSocket-Accept,由于没有同源限制,
# websocket客户端可任意连接支持websocket的服务。
# 这个就相当于一个钥匙一把锁,避免多余的,无意义的连接
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n\r\n
.
.
.
.
.
响应协议(响应成功,便握手成功,就可以双工通信了)
HTTP/1.1 101 Switching Protocols\r\n # 响应首行,还是使用http协议
Upgrade:websocket\r\n # 表示要升级到websocket协议
Connection: Upgrade\r\n # 表示要升级协议
Sec-WebSocket-Accept: 07EWNDBSpegw1vfsIBJtkg==\r\n
# 根据客户端请求首部的Sec-WebSocket-Key计算出来。
WebSocket-Location: ws://127.0.0.1:8000\r\n\r\n
.
.
.
.
通信数据帧格式
.
.
.
.
.
.
.
.
.
.
flask项目使用websocket 代码示范
flask_sock模块的使用
# 简单代码示范
import time
from flask import Flask
from flask_sock import Sock
from threading import Thread
app = Flask(__name__)
sock = Sock(app) # 将这个sock对象绑定Flask对象,也就是绑定app
@sock.route('/connect')
def hahaha(ws):
data = ws.receive()
while True:
time.sleep(1)
ws.send(data)
if __name__ == '__main__':
app.run('0.0.0.0',7777,debug=True)
# 在postman上,输入 ws://localhost:7777/connect
# 点击connect按钮建立连接后,
# message里面输入对应要发送的信息发送后,就会触发对应的视图函数hahaha运行了
# 此时这个装饰器@sock.route('/connect') ,就会把该websocket请求,解析保证成ws对象
# 传给下面对应的视图函数,通过该ws对象就可以给客户端不停的回消息了!!!
# hahaha视图函数就会不停的给客户端发消息了
----------------------------------------------------------
# 下面这个就可以实现,开始客户端发了一个消息,服务端一直给我回同样的消息
# 一旦客户端又发了另一个消息,服务端也会自动给我回另一个消息
import time
from flask import Flask
from flask_sock import Sock
from threading import Thread
app = Flask(__name__)
sock = Sock(app) # 将这个sock对象绑定Flask对象,也就是绑定app
# 定义一个变量stop_flag,用于控制发消息循环,是否结束
stop_flag = False
def send_data(ws, data):
global stop_flag
# 只要这个停止标记是False,就一直发数据
while not stop_flag:
time.sleep(1)
ws.send(data) # 发送数据
# 定义一个ws_client字典,用于存储接收到的ws对象
ws_client = dict()
# 定义一个hahaha视图函数,给ws对象用的
@sock.route('/connect')
def hahaha(ws):
print(ws) # <simple_websocket.ws.Server object at 0x0000027A26AF8160>
print(type(ws)) # <class 'simple_websocket.ws.Server'>
global stop_flag
# 循环接收到数据
while True:
data = ws.receive() # 接收数据
# 如果ws已经在ws_client字典的键中,则将stop设置为True
if str(ws) in ws_client.keys():
if isinstance(ws_client[str(ws)], Thread):
stop_flag = True
ws_client[str(ws)].join() # 等待之前开的子线程结束
msg = ramdon.randint(1,10) # 1-10 之间生成随机整数
# 创建一个子线程,用于发送数据
t = Thread(target=send_data, args=(ws, msg,))
# 将线程添加到ws_client字典中
ws_client[str(ws)] = t
stop_flag = False # 将stop设置为False
t.start() # 启动子线程
# 这个视图函数的作用就是收到一条消息后,就开启子线程,不停的发数据给客户端
# 一旦有收到一条消息后,由于此时的str(ws)已经在字典的键里面了
# 主动把停止的哨兵改为True,这样子线程send_data在循环的时候判断为True了,就结束了
# 再随机生成一个消息,再重开一个子线程,再运行send_data函数,此时就实现
# 用户每发一次消息后,客户端端都会随机生成一个数,然后一直发这个数给客户端
# 用户再发一次消息后,客户端端又会随机生成一个数,然后一直发这个新数给客户端
# 以此类推
# 如果当前文件名为__main__,则启动flask
if __name__ == '__main__':
# 启动flask
app.run('0.0.0.0', 7777, debug=True)
--------------------------------------------------
.
.
.
.
.
.
websocket模块的使用
# 该方法也能实现
# 完整代码
import asyncio
import json
import time
import websockets
from websockets import WebSocketServerProtocol
TOKENS_FOR_TRAFFIC_STREAM_PUBLISH = [
'token_for_traffic_stream_publish',
]
TOKENS_FOR_TRAFFIC_STREAM_RECIEVER = [
'token_for_traffic_stream_recieve',
]
channels_dict = dict()
async def send_traffic_stream_to_client(ws,traffic_stream_msg:str):
await ws.send(traffic_stream_msg)
async def handle_publish(ws:WebSocketServerProtocol):
# 检查请求头的token对不对
if ws.request_headers['token'] not in TOKENS_FOR_TRAFFIC_STREAM_PUBLISH:
await ws.send(json.dumps(
{
'operation':'Auth',
'success': False,
'message': 'Invalid token'
}))
await ws.close()
else:
# token对了发数据
await ws.send(json.dumps(
{
'operation':'Auth',
'success': True,
}))
global channels
try:
# 获取请求头里面,channel的值
channel_id = int(ws.request_headers.get('channel',1))
except:
channel_id = 1
# 如果channel的值,不在channels_dict字典的键里面
if channel_id not in channels_dict.keys():
# 在channels_dict字典里面,就把该channel的值作为键加到字典里
channels[channel_id] = dict()
channels[channel_id]["clients"] = list()
channels[channel_id]["frame"] = None
# 有空再写
# async for xx in xxx 是一个异步迭代的语法
async for message in ws:
channels[channel_id]["frame"] = message
print(f'Recieved traffic stream frame. timestamp: {int(time.time())}')
for client in channels[channel_id]["clients"]:
try:
await send_traffic_stream_to_client(client,message)
except Exception as ex:
print(type(ex))
print(f"Connection {str(client)} closed, Remove from channel...")
channels[channel_id]["clients"].remove(client)
async def handle_recieve(ws:WebSocketServerProtocol):
if ws.request_headers['token'] not in TOKENS_FOR_TRAFFIC_STREAM_RECIEVER:
await ws.send(json.dumps(
{
'operation':'Auth',
'success': False,
'message': 'Invalid token'
}))
await ws.close()
else:
await ws.send(json.dumps(
{
'operation':'Auth',
'success': True,
}))
try:
channel_id = int(ws.request_headers.get('channel',1))
except:
channel_id = 1
if channel_id not in channels.keys():
channels[channel_id] = dict()
channels[channel_id]["clients"] = list()
channels[channel_id]["frame"] = None
channels[channel_id]["clients"].append(ws)
print(f"Client {str(ws)} joined")
async for message in ws:
# 处理通知消息
await ws.send("You said in notifications: " + message)
handle_path = {
'/publish':handle_publish,
'/recieve':handle_recieve
}
async def handle(ws:WebSocketServerProtocol, path):
try:
await handle_path[path](ws)
except Exception as ex:
print(ex)
await ws.close()
if __name__ == "__main__":
start_server = websockets.serve(handle, "0.0.0.0", 5002)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
------------------------------------------------------------
------------------------------------------------------------
# 简化代码
import asyncio
import json
import time
import websockets
from websockets import WebSocketServerProtocol
async def handle_publish(ws:WebSocketServerProtocol):
pass
async def handle_recieve(ws:WebSocketServerProtocol):
pass
handle_path = {
'/publish':handle_publish,
'/recieve':handle_recieve
}
async def hahaha(ws, path):
try:
await handle_path[path](ws) # 我们就可以根据路径的不同,去运行对应的函数了
except Exception as ex:
print(ex)
await ws.close()
if __name__ == "__main__":
start_server = websockets.serve(hahaha, "0.0.0.0", 5002)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
------------------------------------------------------------
# 这就相当于是一个脚本文件了,和flask没关系了,右键运行直接就起了一个websocket的服务端了
# postman输入 ws://localhost:5002 就能直接连上websocket的服务端了
# 触发hahaha函数了,假如端口后面带了路由,比如 ws://localhost:5002/publish
# 一旦这个hahaha函数运行了,就函数就拿到了,建立连接的ws对象,以及端口后面的路径了
# 我们就可以根据路径的不同,去运行对应的函数了
.
.
.
.
.
.
SocketIO模块的使用
# 使用SocketIO 也能实现
import time
from flask import Flask, render_template
from flask_socketio import SocketIO, send, emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret_key'
socketio = SocketIO(app, async_mode='gevent')
@app.route('/')
def index():
return render_template('index.html')
@socketio.on('connect', namespace='/send_rec')
def handle_connect():
print('Client connected')
# emit(event, data=None, room=None, namespace=None, callback=None, broadcast=False, include_self=True)
# event参数是一个字符串,指定要发送的事件名称
# data参数是要发送的消息数据,可以是任意类型。
emit('message', '已建立连接')
@socketio.on('message', namespace='/send_rec')
def handle_message(message):
print('Received:', message)
# 要在postman里面 ws://localhost:5000/send_rec 连接
# 然后要在Events里面 把要监听的事件写下来,比如当前的message,然后把listen按钮打开
# 这样就行了,客户端发一个,服务端就能回了
emit('message', 'haha') # 正常
if __name__ == '__main__':
socketio.run(app,)
--------------------------------------------------
# 如果想要实现服务端不停的给客户端发消息,要打补丁
from gevent import monkey;monkey.patch_all()
import time
from flask import Flask, render_template
from flask_socketio import SocketIO, send, emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret_key'
socketio = SocketIO(app, async_mode='gevent')
@socketio.on('connect', namespace='/send_rec')
def handle_connect():
print('Client connected')
emit('message', '已建立连接')
@socketio.on('message', namespace='/send_rec')
def handle_message(message):
print('Received:', message)
while True:
print('sleep')
time.sleep(1)
emit('message', 'haha')
# emit('message', 'haha') # 正常
if __name__ == '__main__':
socketio.run(app, )
# 这样,客户端发一个消息后,就能不停的收到服务端的消息了
.
.
.
.
async for xx in xxx 是一个异步迭代的语法
.
.
.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY