大家好,我是一个初级的Python开发工程师。本文是结合官方教程和代码案例,简单说下我对flask-socketio的使用理解。

一、websocket简介

websocket 说白一点就是,建立客户端和服务端双向通讯通道, 服务器可以主动向客户端发消息。

二、flask-socketio理解与使用

1. 环境准备:Python3.7

pip install eventlet==0.33.3
pip install flask-socketio==5.8.0
pip install flask==1.1.4

2. 代码来自官方教程

下面的代码亲测可用,请放心食用。

(1)项目结构

(2)app.py代码

from threading import Lock
from flask import Flask, render_template, session, request, copy_current_request_context
from flask_socketio import SocketIO, emit, join_room, leave_room, close_room, rooms, disconnect # Set this variable to "threading", "eventlet" or "gevent" to test the
# different async modes, or leave it set to None for the application to choose
# the best option based on installed packages.
async_mode = None app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, async_mode=async_mode)
thread = None
thread_lock = Lock() def background_thread():
"""Example of how to send server generated events to clients."""
count = 0
while True:
socketio.sleep(10)
count += 1
socketio.emit('my_response',
{'data': 'Server generated event', 'count': count}) @app.route('/')
def index():
return render_template('index.html', async_mode=socketio.async_mode) @socketio.event
def my_event(message):
session['receive_count'] = session.get('receive_count', 0) + 1
emit('my_response',
{'data': message['data'], 'count': session['receive_count']}) @socketio.event
def my_broadcast_event(message):
session['receive_count'] = session.get('receive_count', 0) + 1
emit('my_response',
{'data': message['data'], 'count': session['receive_count']},
broadcast=True) @socketio.event
def join(message):
join_room(message['room'])
session['receive_count'] = session.get('receive_count', 0) + 1
emit('my_response',
{'data': 'In rooms: ' + ', '.join(rooms()),
'count': session['receive_count']}) @socketio.event
def leave(message):
leave_room(message['room'])
session['receive_count'] = session.get('receive_count', 0) + 1
emit('my_response',
{'data': 'In rooms: ' + ', '.join(rooms()),
'count': session['receive_count']}) @socketio.on('close_room')
def on_close_room(message):
session['receive_count'] = session.get('receive_count', 0) + 1
emit('my_response', {'data': 'Room ' + message['room'] + ' is closing.',
'count': session['receive_count']},
to=message['room'])
close_room(message['room']) @socketio.event
def my_room_event(message):
session['receive_count'] = session.get('receive_count', 0) + 1
emit('my_response',
{'data': message['data'], 'count': session['receive_count']},
to=message['room']) @socketio.event
def disconnect_request():
@copy_current_request_context
def can_disconnect():
disconnect()
session['receive_count'] = session.get('receive_count', 0) + 1
# for this emit we use a callback function
# when the callback function is invoked we know that the message has been
# received and it is safe to disconnect
emit('my_response',
{'data': 'Disconnected!', 'count': session['receive_count']},
callback=can_disconnect) @socketio.event
def my_ping():
emit('my_pong') @socketio.event
def connect():
global thread
with thread_lock:
if thread is None:
thread = socketio.start_background_task(background_thread)
emit('my_response', {'data': 'Connected', 'count': 0}) @socketio.on('disconnect')
def test_disconnect():
print('Client disconnected', request.sid) if __name__ == '__main__':
socketio.run(app, host='0.0.0.0', debug=True)

(3)index.html代码

<!DOCTYPE HTML>
<html>
<head>
<title>Flask-SocketIO Test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.4/socket.io.js" integrity="sha512-aMGMvNYu8Ue4G+fHa359jcPb1u+ytAF+P2SCb+PxrjCdO3n3ZTxJ30zuH39rimUggmTwmh2u7wvQsDTHESnmfQ==" crossorigin="anonymous"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// Connect to the Socket.IO server.
// The connection URL has the following format, relative to the current page:
// http[s]://<domain>:<port>[/<namespace>]
var socket = io.connect('http://' + document.domain + ':' + location.port); // Event handler for new connections.
// The callback function is invoked when a connection with the
// server is established.
socket.on('connect', function() {
socket.emit('my_event', {data: 'I\'m connected!'});
}); // Event handler for server sent data.
// The callback function is invoked whenever the server emits data
// to the client. The data is then displayed in the "Received"
// section of the page.
socket.on('my_response', function(msg, cb) {
$('#log').append('<br>' + $('<div/>').text('Received #' + msg.count + ': ' + msg.data).html());
if (cb)
cb();
}); // Interval function that tests message latency by sending a "ping"
// message. The server then responds with a "pong" message and the
// round trip time is measured.
var ping_pong_times = [];
var start_time;
window.setInterval(function() {
start_time = (new Date).getTime();
$('#transport').text(socket.io.engine.transport.name);
socket.emit('my_ping');
}, 1000); // Handler for the "pong" message. When the pong is received, the
// time from the ping is stored, and the average of the last 30
// samples is average and displayed.
socket.on('my_pong', function() {
var latency = (new Date).getTime() - start_time;
ping_pong_times.push(latency);
ping_pong_times = ping_pong_times.slice(-30); // keep last 30 samples
var sum = 0;
for (var i = 0; i < ping_pong_times.length; i++)
sum += ping_pong_times[i];
$('#ping-pong').text(Math.round(10 * sum / ping_pong_times.length) / 10);
}); // Handlers for the different forms in the page.
// These accept data from the user and send it to the server in a
// variety of ways
$('form#emit').submit(function(event) {
socket.emit('my_event', {data: $('#emit_data').val()});
return false;
});
$('form#broadcast').submit(function(event) {
socket.emit('my_broadcast_event', {data: $('#broadcast_data').val()});
return false;
});
$('form#join').submit(function(event) {
socket.emit('join', {room: $('#join_room').val()});
return false;
});
$('form#leave').submit(function(event) {
socket.emit('leave', {room: $('#leave_room').val()});
return false;
});
$('form#send_room').submit(function(event) {
socket.emit('my_room_event', {room: $('#room_name').val(), data: $('#room_data').val()});
return false;
});
$('form#close').submit(function(event) {
socket.emit('close_room', {room: $('#close_room').val()});
return false;
});
$('form#disconnect').submit(function(event) {
socket.emit('disconnect_request');
return false;
});
});
</script>
</head>
<body>
<h1>Flask-SocketIO Test</h1>
<p>
Async mode is: <b>{{ async_mode }}</b><br>
Current transport is: <b><span id="transport"></span></b><br>
Average ping/pong latency: <b><span id="ping-pong"></span>ms</b>
</p>
<h2>Send:</h2>
<form id="emit" method="POST" action='#'>
<input type="text" name="emit_data" id="emit_data" placeholder="Message">
<input type="submit" value="Echo">
</form>
<form id="broadcast" method="POST" action='#'>
<input type="text" name="broadcast_data" id="broadcast_data" placeholder="Message">
<input type="submit" value="Broadcast">
</form>
<form id="join" method="POST" action='#'>
<input type="text" name="join_room" id="join_room" placeholder="Room Name">
<input type="submit" value="Join Room">
</form>
<form id="leave" method="POST" action='#'>
<input type="text" name="leave_room" id="leave_room" placeholder="Room Name">
<input type="submit" value="Leave Room">
</form>
<form id="send_room" method="POST" action='#'>
<input type="text" name="room_name" id="room_name" placeholder="Room Name">
<input type="text" name="room_data" id="room_data" placeholder="Message">
<input type="submit" value="Send to Room">
</form>
<form id="close" method="POST" action="#">
<input type="text" name="close_room" id="close_room" placeholder="Room Name">
<input type="submit" value="Close Room">
</form>
<form id="disconnect" method="POST" action="#">
<input type="submit" value="Disconnect">
</form>
<h2>Receive:</h2>
<div id="log"></div>
</body>
</html>

(4)运行app.py代码,浏览器访问5000端口,如下:

(5)代码理解(最重要的部分!!!)

  flask-socketio包的常用方法理解:

  1. socketio.on和socketio.event是等价的,都是用来定义事件处理器(event handlers)的。区别是.on的第一个参数是事件名称(event name),.event没有这个参数,而是使用被装饰的函数名作为事件名称。其他参数是一样的。事件名称 connect / disconnect / message / json 都是SocketIO生成的特殊事件名,任何其他的事件名都被视为自定义事件。其他参数还有namespace(命名空间)。

  2. send和emit都被服务器用来向客户端发送消息。send直接发送消息,emit需要指定事件和消息。一般情况下,都是使用emit指定事件名发送消息。emit的其他参数有:

    A. namespace(命名空间),和事件名配合使用。默认为"/"。

    B. broadcast(广播模式True/False),是否向所有客户端Client发送消息。

    C. to,通常为room_id,发送给指定房间的所有用户。

    D. callback(回调函数),指定回调函数,发送到另一端执行。

  

启动后的运行流程理解:

  1. 启动时的初始运行流程。客户端访问http://host:5000后,触发index.html里面的js代码,客户端执行了后,

  var socket = io.connect('http://' + document.domain + ':' + location.port);

  客户端和后台服务器建立了连接,注意,此时先触发服务器端的代码:  

@socketio.event
def connect():
global thread
with thread_lock:
if thread is None:
thread = socketio.start_background_task(background_thread)
emit('my_response', {'data': 'Connected', 'count': 0})

  然后紧接着触发了客户端的代码:

socket.on('connect', function() {
socket.emit('my_event', {data: 'I\'m connected!'});
});

  所以,浏览器请求的web页面 Receive部分:先是 Received #0: Connected,再是 Received #1: I'm connected! 

  2. 接下来,看看 浏览器请求的web页面 Send部分:

  (1)echo:输入123,浏览器会向服务器端的my_event事件处理器发送数据{"data": 123}

socket.emit('my_event', {data: $('#emit_data').val()});

  服务器端的my_event事件处理器为:

@socketio.event
def my_event(message):
session['receive_count'] = session.get('receive_count', 0) + 1
emit('my_response',
{'data': message['data'], 'count': session['receive_count']})

  可以看到,服务器端在接收到数据后,又向客户端的my_response事件处理器发送数据。在看看index.html里的my_response事件处理器是如何定义的:

socket.on('my_response', function(msg, cb) {
$('#log').append('<br>' + $('<div/>').text('Received #' + msg.count + ': ' + msg.data).html());
if (cb)
cb();
});

  最终,浏览器的web页面显示为 Received #2: 123。通过这个例子,也充分展示了websocket的功能,服务端和客户端都主动可以向另一端发送数据。这是有别于http的。http协议只能客户端发起请求,服务端响应请求。服务端无法主动向客户端发送数据。

  (2)broadcast暂时不说了。

  (3)Join Room:这个和Leave Room是成对使用的。就像一个聊天室一样,加入指定聊天室后,当执行Send to Room,就可以接收这个房间内的所有消息。

  (4)Close Room:关闭房间

  (5)Disconnect:客户端主动断开连接,客户端触发服务端的disconnect_request事件处理器,

# 客户端
socket.emit('disconnect_request'); # 服务端
@socketio.event
def disconnect_request():
@copy_current_request_context
def can_disconnect():
disconnect()
session['receive_count'] = session.get('receive_count', 0) + 1
# for this emit we use a callback function
# when the callback function is invoked we know that the message has been
# received and it is safe to disconnect
emit('my_response',
{'data': 'Disconnected!', 'count': session['receive_count']},
callback=can_disconnect)

  服务端收到请求后,会向客户端的my_response事件处理器发送数据,同时发送一个callback回调函数can_disconnect,让客户端执行该函数。

  最终浏览器的页面显示:Received #2: Disconnected!

三、写在最后

至此,你应该已经对使用flask-socketio库有了基本的认识了。如果还有不了解的,可以留言交流。

生产环境中,还需要添加异常处理,比如socketio.on_error()和socketio.on_error_default()。

本文只是入门使用教程,感兴趣的话请大家自行查文档深入理解。

附上官方教程链接

1. https://blog.miguelgrinberg.com/post/easy-websockets-with-flask-and-gevent

2. https://flask-socketio.readthedocs.io/en/latest/index.html

【websocket】小白快速上手flask-socketio的更多相关文章

  1. 快速上手python的简单web框架flask

    目录 简介 web框架的重要组成部分 快速上手flask flask的第一个应用 flask中的路由 不同的http方法 静态文件 使用模板 总结 简介 python可以做很多事情,虽然它的强项在于进 ...

  2. Flask入门和快速上手

    目录 Flask入门和快速上手 python三大主流框架对比 Flask安装 依赖 可选依赖 创建flask项目 flask最小应用--hello word 非法导入名称 调试模式 路由 唯一的 UR ...

  3. 【Python五篇慢慢弹】快速上手学python

    快速上手学python 作者:白宁超 2016年10月4日19:59:39 摘要:python语言俨然不算新技术,七八年前甚至更早已有很多人研习,只是没有现在流行罢了.之所以当下如此盛行,我想肯定是多 ...

  4. WebAPI调用笔记 ASP.NET CORE 学习之自定义异常处理 MySQL数据库查询优化建议 .NET操作XML文件之泛型集合的序列化与反序列化 Asp.Net Core 轻松学-多线程之Task快速上手 Asp.Net Core 轻松学-多线程之Task(补充)

    WebAPI调用笔记   前言 即时通信项目中初次调用OA接口遇到了一些问题,因为本人从业后几乎一直做CS端项目,一个简单的WebAPI调用居然浪费了不少时间,特此记录. 接口描述 首先说明一下,基于 ...

  5. 快速上手Ubuntu之安装篇——安装win7,Ubuntu16.04双系统【转】

    本文转载自:http://blog.csdn.net/qq_28205153/article/details/52203512 Linux可以说是开发者的系统,对于开发者来说,Linux发行版不仅为我 ...

  6. 必会技能!Docker助你快速上手玩转HBase!

    前言:本文主要讲述了如何使用Docker快速上手HBase,省去繁杂的安装部署环境,直接上手,小白必备.适合HBase入门学习及简单代码测试. 1. Docker 安装 参考地址: https://y ...

  7. Tapdata 在线研讨会:如何快速上手 Tapdata Cloud?

    偶然接触到 Tapdata Cloud,据说不仅可以实现异构数据实时同步,还永久 100% 免费,但就是不知道怎么获取.怎么用? 打开相关文档逐渐陷入迷茫,术语."黑话"随处可见, ...

  8. 快速上手Unity原生Json库

    现在新版的Unity(印象中是从5.3开始)已经提供了原生的Json库,以前一直使用LitJson,研究了一下Unity用的JsonUtility工具类的使用,发现使用还挺方便的,所以打算把项目中的J ...

  9. [译]:Xamarin.Android开发入门——Hello,Android Multiscreen快速上手

    原文链接:Hello, Android Multiscreen Quickstart. 译文链接:Hello,Android Multiscreen快速上手 本部分介绍利用Xamarin.Androi ...

  10. [译]:Xamarin.Android开发入门——Hello,Android快速上手

    返回索引目录 原文链接:Hello, Android_Quickstart. 译文链接:Xamarin.Android开发入门--Hello,Android快速上手 本部分介绍利用Xamarin开发A ...

随机推荐

  1. 点亮LED灯_STM32第一课

    基本原理 初始化Hal库 HAL_Init(); 系统时钟 SystemClock_Config();   GPIOB初始化:GPIOB模式为推挽输出,GPIO引脚为Pin_5.0.1代表红绿蓝LED ...

  2. phpcm v9 任意调用分页/phpcm v9首页调用分页不起作用或者乱码

    默认如下: {pc:content action="lists" catid="1" num="10" order="id DES ...

  3. [python] Python枚举模块enum总结

    枚举是一种数据类型,在编程中用于表示一组相关的常量.枚举中的每个常量都有一个名称和一个对应的值,可以用于增强代码的可读性和可维护性.在Python中,枚举是由enum模块提供的,而不是Python提供 ...

  4. 任务拆解,悠然自得,自动版本的ChatGPT,AutoGPT自动人工智能AI任务实践(Python3.10)

    当我们使用ChatGPT完成某些工作的时候,往往需要多轮对话,比如让ChatGPT分析.翻译.总结一篇网上的文章或者文档,再将总结的结果以文本的形式存储在本地.过程中免不了要和ChatGPT" ...

  5. UniApp小程序开发如何获取用户手机号

    我们在小程序开发的时候经常遇到这种需求,需要在账号登陆的时候进行手机号获取,并使用手机号登陆. 本文讲述如何在前后端分离的状态下获取手机号 查阅官网文档不难发现我们需要使用uni.login()这个方 ...

  6. Html 设置标题栏顶部固定

    如何设置标题栏一直置顶固定显示? 只需要给标题栏所在的容器,以下设置:   position: fixed;   top: 0px;   left: 0px;   width: 100%; 位置固定在 ...

  7. TED--10 ways to have a better conversation

    10 ways to have a better conversation All right, I want to see a show of hands: how many of you have ...

  8. CS144 计算机网络 Lab3:TCP Sender

    前言 在 Lab2 中我们实现了 TCP Receiver,负责在收到报文段之后将数据写入重组器中,并回复给发送方确认应答号.在 Lab3 中,我们将实现 TCP 连接的另一个端点--发送方,负责读取 ...

  9. 2021-09-02:IP 到 CIDR。给定起始IP和整数n,返回长度最小的CIDR块。力扣751。比如:ip=255.0.0.7,n=10,输出:[“255.0.0.7/32“,“255.0.0.

    2021-09-02:IP 到 CIDR.给定起始IP和整数n,返回长度最小的CIDR块.力扣751.比如:ip=255.0.0.7,n=10,输出:["255.0.0.7/32" ...

  10. ICANN 2001-Learning to Learn Using Gradient Descent

    Key Gradient Descent+LSTM元学习器 解决的主要问题 在之前的机器学习的学习方法中,不会利用到之前的经验,利用到之前经验的"knowledge transfer&quo ...