flask后端+网页前端:基于 socket.io 的双向通信和服务器部署
我想实现的效果是,我的服务器提供两个路由网址,网页A用于拍照、然后录音,把照片和录音传给服务器,服务器发射信号,通知另一个路由的网页B更新,把刚刚传来的照片和录音显示在网页上。
然后网页B用户根据这个照片和录音,回答一些问题,输入答案的文本,传给服务器。服务器拿到答案后,再发射信号,把这个结果显示在网页A上。
这就得用到双向通信(其实有点类似两个网页聊天的功能,而且支持发送语音、图片、文本三种消息)。这里用的是 socket.io 包。在本地写还是很好写的,但是,部署到服务器上之后,就出了很多 bug。很多坑,这里把我遇到的记下来,防止再次犯错。
这里只记录关键代码,也就是容易掉坑的代码。整个项目我之后会上传到 github 上。传好了补连接。
整体的逻辑
- 建立双向通道
- 网页A上传文件,得到服务器传回的提示信号(code: 200),然后
socket.emit("upload_completed"),通知服务器数据已经上传了 - 服务器监听
upload_completed信号,收到该信号后,服务器作为中转站,广播信号emit('data_updated', data, broadcast=True)通知前端该更新数据了 - 网页 B 监听
data_updated信号,修改自己的页面,展示图片和录音。等用户在该网页填好答案之后,点击发送按钮,网页 B 发生信号socket.emit("annotated_answer") - 服务器监听
annotated_answer信号,收到该信号后,作为中转站,广播信号emit('send_answer', answer, broadcast=True) - 网页 A 监听信号
send_answer,收到该信号后,把结果显示在网页上
特别拎出来的坑,特别注意
- 运行 flask 代码的时候调用 socketio 的 run 方法,不是用 app 的 run 方法,不然没法双向连接的;但是在服务器端部署的时候,用 uwsgi 跑上,它就是默认调用 app.run,很崩溃的啊这个;所以服务器端部署的时候,用 gunicorn (这个部署,真的,翻遍全网才找到,落泪了)
- socket 连接的地址,本地调试的时候填的是 localhost,但是传到服务器要改成服务器的地址,不该的话,连不上的!!
- 刚刚更新模型的时候又出毛病,爬上来更新。这次没有报任何错误,但是网页就是访问不到。检查了一个小时,发现是因为梯子忘记关了
- 部署后手机端打不开录音设备和摄像头,那是因为,媒体设备只能在 https 协议下,或者 http://localhost 下访问,所以要用这个功能,就必须去申请 ssl 证书。阿里云有免费的 20 张,好好把握。
flask 代码
我这里只贴最关键的代码,加上注释,直接把这个代码粘上去,是会报错的。
@app.route('/upload', methods=['POST'])
def app_upload_file():# 保存图片img_file = request.files['img']if img_file.filename == '':return jsonify({'error': 'No image'}), 400try:image_path = os.path.join(app.config['UPLOAD_FOLDER'], img_file.filename)img_file.save(image_path)shutil.copy(image_path, os.path.join(os.path.dirname(__file__), 'static/show.jpg')) # 用于展示在网页上log(f"save image: {image_path}")except Exception as e:return jsonify({'error': str(e)}), 500try:# 传过来的就是文本question = request.form['question']except:question = "请描述图片内容"return jsonify({"image": img_file.filename, "question": question})@app.route('/upload/speech', methods=['POST'])
def recognize_speech():speech_file = request.files['speech']try:save_path = os.path.join(app.config['UPLOAD_FOLDER'], speech_file.filename)speech_file_path = os.path.join(app.config['UPLOAD_FOLDER'], save_path)speech_file.save(speech_file_path)# question = speech2txt(speech_file_path)# print('百度识别结果:', question)except Exception as e:return jsonify({'error': str(e)}), 500return jsonify({"speech": speech_file.filename})@socketio.on('upload_completed')
def handle_upload_completed(data):# pip install flask-socketio eventletprint(data)try:emit('data_updated', data, broadcast=True)except Exception as e:print(e)emit('error', {'error': str(e)})@socketio.on('upload_speech_completed')
def handle_upload_speech_completed(data):# pip install flask-socketio eventlettry:emit('data_speech_updated', data, broadcast=True)except Exception as e:print(e)emit('error', {'error': str(e)})@socketio.on('annotated_answer')
def handle_annotated_answer(answer):log(f'get answer from annotator: {answer}')try:emit('send_answer', answer, broadcast=True)except Exception as e:print(e)if __name__ == '__main__':# app.run(host='0.0.0.0', port=8099)# 这个地方!!看清楚!看清楚!要调用 socketio 的 run 方法,不是用 app 的 run 方法,不然没法双向连接的socketio.run(app, host='0.0.0.0', allow_unsafe_werkzeug=True, port=8099)
网页 A 的代码
注意这里只贴了一部分代码,关于文件怎么上传的,也就是引入的 camera.js 和 recorder.js 这俩文件的内容,在我这这篇文章里贴了: flask 后端 + 微信小程序和网页两种前端:调用硬件(相机和录音)和上传至服务器
html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><link rel="stylesheet" href="{{ url_for('static', filename='css/full_button.css') }}" type="text/css"><script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.min.js"></script>
</head>
<body><div style="display: flex"><div><video id="videoElement" autoplay="autoplay" muted="muted" style="width: 40px"></video><img id="photo" alt="你的照片" src="" style="display: none"></div><div id="answer" class="answer-text">答案等待中...</div></div><div class="button-grid"><button id="snapButton">拍摄照片</button><button id="recorderButton">录音</button><button id="captionButton">描述图片</button><button id="vqaButton">回答问题</button></div>{# <input type="text" id="textQuestion" placeholder="请输入问题...">#}<script>// 这里最最最关键的就是这个网址,如果你在本地跑,要填 localhost,不能填 127.0.0.1;如果是部署在服务器,要填成服务器的地址,不然肯定是连不上的。const socket = io.connect('http://localhost:8099'); // 连接到Flask服务器socket.on('send_answer', function (data) {// 接收到服务器返回的答案,震动提示,把答案显示在页面上console.log('接收到答案:', data);document.getElementById('answer').textContent = data;navigator.vibrate([200]); // 震动提示收到答案})var imageBlob = null; // 拍摄的图片var speechBlob = null; // 提出的问题// 生成随机文件名function randomFilename() {let now = new Date().getTime();let str = `xxxxxxxx-xxxx-${now}-yxxx`;return str.replace(/[xy]/g, function(c) {const r = Math.random() * 16 | 0;const v = c === 'x' ? r : (r & 0x3 | 0x8);return v.toString(16)})}</script><script type="text/javascript" src="../static/js/user_camera.js"></script><script type="text/javascript" src="../static/js/user_recorder.js"></script><script>// 绑定 caption 按钮document.getElementById('captionButton').onclick = function () {if (imageBlob == null) {alert('请先拍摄照片,再点击“描述图片”按钮')} else {const captionFormData = new FormData();let imgFilename = randomFilename()+'.jpg';captionFormData.append('img', imageBlob, imgFilename);captionFormData.append('question', '请描述图片内容');fetch('http://localhost:8099/upload', {method: 'POST',body: captionFormData}).then(response => {console.log('response:', response);if (response.status === 200) {console.log('发射信号 upload_completed');// 注意!!这里发射的信号,带的数据,得是URL.createObjectURL(imageBlob)不能是别的不能是别的不能是别的,重要的事情说3遍!!不然无法正确地显示在网页 B 上socket.emit('upload_completed', {'image': URL.createObjectURL(imageBlob),'question': '请描述图片内容'});}}).then(data => console.log('data:', data)).catch(error => console.error(error));}};// 绑定 vqa 按钮document.getElementById('vqaButton').onclick = function () {if (imageBlob == null) {alert('请先拍摄照片,再点击“描述图片”按钮')} else {if (speechBlob == null) {alert('您还没有提问,请先点击录音按钮录音提问')} else {let filename = randomFilename();// 先发语音再发图片,因为发了图片之后会提示听录音const speechFormData = new FormData();speechFormData.append('speech', speechBlob, filename+'.wav');fetch('http://localhost:8099/upload/speech', {method: 'POST',body: speechFormData}).then(response => {console.log('response:', response);if (response.status === 200) {console.log('成功上传音频', response);socket.emit('upload_speech_completed',{'speech': window.URL.createObjectURL(speechBlob)})}}).then(data => console.log('data:', data)).catch(error => console.error(error));const imgFormData = new FormData();imgFormData.append('img', imageBlob, filename+'.jpg');fetch('http://localhost:8099/upload', {method: 'POST',body: imgFormData}).then(response => {console.log('response:', response);if (response.status === 200) {console.log('发射信号 upload_completed');socket.emit('upload_completed', {'image': URL.createObjectURL(imageBlob),'question': '请听录音'});}}).then(data => console.log('data:', data)).catch(error => console.error(error));}}};</script>
</body>
</html>
网页 B 的代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>human-annotation</title><script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.min.js"></script></head>
<body><img id="image" src="" alt="Your Image"><audio id="audioPlayer" controls class="audio-player"></audio><div style="display: flex">提问:<div id="question"></div></div><input type="text" id="textInput" placeholder="请输入答案..."><button id="submitButton">发送</button><script>// 这里也是,大坑,大坑啊!!这个地址要填对,本地用 localhost,云端用云端服务器地址啊!var socket = io.connect('http://localhost:8099'); // 连接到Flask服务器socket.on('data_updated', function(data) {// 当接收到来自服务器的数据时,更新页面内容var img = document.getElementById('image');img.src = data.image;console.log('img.src');// document.getElementById('image').innerHTML = '<img src="' + data.image + '" alt="Uploaded Image">';document.getElementById('question').textContent = data.question;});socket.on('data_speech_updated', function (data) {var audioPlayer = document.getElementById("audioPlayer");audioPlayer.src = data.speech;});// 监听按钮点击事件document.getElementById('submitButton').addEventListener('click', function() {// 获取输入框中的文本var message = document.getElementById('textInput').value;// 验证消息是否为空if (message.trim() !== '') {// 通过Socket.IO发送消息给服务器socket.emit('annotated_answer', message);// 清空输入框document.getElementById('textInput').value = '';} else {alert('Please enter a message.');}});</script>
</body>
</html>
部署
用 gunicorn 部署
配置文件:

运行命令:
相关文章:
flask后端+网页前端:基于 socket.io 的双向通信和服务器部署
我想实现的效果是,我的服务器提供两个路由网址,网页A用于拍照、然后录音,把照片和录音传给服务器,服务器发射信号,通知另一个路由的网页B更新,把刚刚传来的照片和录音显示在网页上。 然后网页B用户根据这个…...
【Docker】解决 docker build 提示 `Wrong architecture ‘amd64‘`
解决 docker build 提示 Wrong architecture amd64 使用 securify2 的 docker 版本进行 sc 安全扫描 执行语句 RUN wget https://github.com/souffle-lang/souffle/releases/download/1.6.2/souffle_1.6.2-1_amd64.deb -O /tmp/souffle.deb &&\ gdebi --n /tmp/souff…...
机器学习_XGBoost模型_用C++推理示例Demo
1. 需求 将 python 训练好的 xgboost 模型, 使用C 进行加载并进行推理(预测) 2. 代码实现 #include <iostream> #include <fstream> #include <sstream> #include <vector> #include <string> #include <xgboost/c_api.h>const char *m…...
C语言 | Leetcode C语言题解之第21题合并两个有序链表
题目: 题解: /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/typedef struct ListNode ListNode; struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {/…...
2024考研调剂须知
----------------------------------------------------------------------------------------------------- 考研复试科研背景提升班 教你快速深入了解掌握考研复试面试中的常见问题以及注意事项,系统的教你如何在短期内快速提升自己的专业知识水平和编程以及英语…...
PCIE协议版--M.2接口规范V1.0中文版1——电气规格篇
3.电气规范 3.1 Connectivity Socket 1 系统接口信号 表15适用于Socket 1-SD和Socket 1-DP输出版本。 3.1.1.补充NFC信号 当一个SIM设备被用作安全元素时,NFC解决方案可以与表16中列出的附加信号相结合。 3.1.2.电源和地 PCI Express M.2 Socket 1使用一个3.3 V…...
【JVM】JVM堆占用情况分析(频繁创建的对象、内存泄露等问题)、jmap+jhat、jvisualvm工具使用
文章目录 一. 相关命令1. 查看进程堆内存整体使用情况:OOM的可能2. 统计类的对象数量以及内存占用:定位内存泄漏 二. 分析内存占用1. 使用 jhat 排查对象堆占用情况1.1. 排查步骤1.2. 具体分析例子a. 分析频繁创建对象导致的OOM 1.3. OQL查看某一个对象的…...
【蓝桥杯每日一题】4.11 更小的数(不用区间DP)
题目来源: 蓝桥杯 2023 省 A]更小的数 - 洛谷 这题只需要用到双指针就OK~ 思路1: 翻转数组的子数组,然后进行比较大小将翻转后的数组存储在字符串 k k k中,然后将字符串 k k k与字符串 a a a进行逐一元素比较(因为…...
【线段树】2276. 统计区间中的整数数目
算法可以发掘本质,如: 一,若干师傅和徒弟互有好感,有好感的师徒可以结对学习。师傅和徒弟都只能参加一个对子。如何让对子最多。 二,有无限多1X2和2X1的骨牌,某个棋盘若干格子坏了,如何在没有坏…...
ChatGPT 写作利器:探索ChatGPT在论文写作中的应用
ChatGPT无限次数:点击直达 ChatGPT 写作利器:探索ChatGPT在论文写作中的应用 引言 ChatGPT是一种强大的自然语言处理工具,能够为我们提供高效、准确的文本生成功能。在论文写作领域,ChatGPT的应用也逐渐受到关注。本文将探讨ChatGPT在论文写…...
从 SQLite 3.4.2 迁移到 3.5.0(二十)
返回:SQLite—系列文章目录 上一篇:SQLite---调试提示(十九) 下一篇:SQLite—系列文章目录 SQLite 版本 3.5.0 (2007-09-04) 引入了一个新的操作系统接口层, 与所有先前版本的 SQLi…...
集群开发学习(一)(安装GO和MySQL,K8S基础概念)
完成gin小任务 参考文档: https://www.kancloud.cn/jiajunxi/ginweb100/1801414 https://github.com/hanjialeOK/going 最终代码地址:https://github.com/qinliangql/gin_mini_test.git 学习 1.安装go wget https://dl.google.com/go/go1.20.2.linu…...
[Kubernetes[K8S]集群:Slaver从节点初始化和Join]:添加到主节点集群内
文章目录 操作流程:上篇主节初始化地址:前置:Docker和K8S安装版本匹配查看0.1:安装指定docker版本 **[1 — 8] ** [ 这些步骤主从节点前置操作一样的 ]一:主节点操作 查看主机域名->编辑域名->域名配置二&#x…...
redis复习笔记08(小滴课堂)
案例实战需求之大数据下的用户画像标签去重 我们就简单的做到了去重了。 案例实战社交应用里面之关注、粉丝、共同好友案例 这就是我们set的一个应用。 案例实战之SortedSet用户积分实时榜单最佳实践 准备积分类对象: 我们加上构造方法和判断相等的equals和hascod…...
在线课程平台LearnDash评测 – 最佳 WordPress LMS插件
在我的LearnDash评测中,我探索了流行的 WordPress LMS 插件,该插件以其用户友好的拖放课程构建器而闻名。我深入研究了各种功能,包括课程创建、测验、作业、滴灌内容、焦点模式、报告、分析和管理工具。 我的评测还讨论了套餐和定价选项&…...
OpenDDS-3.27构建与用法
一、OpenDDS-3.27构建 ./configure To enable Java bindings, use ./configure --java make 二、运行Messenger Example: source setenv.sh For the C example:cd DevGuideExamples/DCPS/Messenger For the Java example:cd java/tests/mes…...
计算机网络——MAC地址和IP地址
目录 前言 引入 MAC地址与IP地址 IP地址和MAC地址是什么?如何起作用的? MAC地址如何表示与确定网卡在网络中的确定位置? DHCP协议自动帮我们配置 操作系统是如何知道对方的MAC地址的? 前言 本博客是博主用于复习计算机网络…...
Unity构建详解(7)——AssetBundle格式解析
【文件格式】 文件可以分为文本文件、图片文件、音频文件、视频文件等等,我们常见的这些文件都有行业内的标准格式,其意味着按照一定的规则和规范去保存读取文件,可以获取我们想要的数据。 有些软件会有自己的文件格式,会按照其…...
前端对接fastGPT流式数据+打字机效果
首先在对接api时 参数要设置stream: true, const data {chatId: abc,stream: true,//这里true返回流式数据detail: false,variables: {uid: sfdsdf,name: zhaoyunyao,},messages: [{ content: text, role: user }]}; 不要用axios发请求 不然处理不了流式数据 我这里使用fetch …...
避免使用第三方工具完成电脑环境检测
0. 简介 在之前配置各种深度学习环境的时候经常需要先检测一下电脑的软硬件环境,其实整个过程比较重复和固定,所以我们是否有可能一键检测Python版本、PIP版本、Conda版本、CUDA版本、电脑系统、CPU核数、CPU频率、内存、硬盘等内容这是很多Deepper苦恼…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
