如何实现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 是一个异步迭代的语法


.
.
.

posted @   tengyifan  阅读(16)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· 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
点击右上角即可分享
微信分享提示