day112:MoFang:种植园使用websocket代替http&服务端基于flask-socketio提供服务&服务端响应信息&种植园页面显示初始化
目录
1.种植园使用websocket代替http
我们需要完成的种植园,是一个互动频繁,并且要求有一定即时性的模块,所以如果继续基于http协议开发,那么需要通过ajax发送大量http请求,同时因为http本身属于单向通讯,所以服务端无法主动发送信息提供给客户端。所以对于客户端使用来说,非常不友好,所以我们需要基于socket通讯来完成这个模块的开发。当然,如果我们服务端基于socket实现tcp/ip通讯的同时,那么客户端必须也要使用websocket来实现tcp/ip通讯才能正常运作。
1.websocket协议简介
文档:https://tools.ietf.org/html/rfc6455
一直以来,HTTP是无状态、单向通信的网络协议,即客户端请求一次,服务器回复一次,默认情况下,只允许浏览器向服务器发出请求后,服务器才能返回相应的数据。如果想让服务器消息及时下发到客户端,需要采用类似于轮询的机制,大部分情况就是客户端通过定时器使用ajax频繁地向服务器发出请求。这样的做法效率很低,而且HTTP数据包头本身的字节量较大,浪费了大量带宽和服务器资源。
为了提高效率,HTML5推出了WebSocket技术。
WebScoket是一种让客户端和服务器之间能进行全双工通信(full-duplex)的技术。它是HTML最新标准HTML5的一个协议规范,本质上是个基于TCP的协议,它通过HTTP/HTTPS协议发送一条特殊的请求进行握手后创建了一个TCP连接,此后浏览器/客户端和服务器之间便可随时随地以通过此连接来进行双向实时通信,且交换的数据包头信息量很小。
同时为了方便使用,HTML5提供了非常简单的操作就可以让前端开发者直接实现socket通讯,开发者只需要在支持WebSocket的浏览器中,创建Socket之后,通过onopen、onmessage、onclose、onerror四个事件的实现即可处理Socket的响应。
注意:websocket是HTML5技术的一部分,但是websocket并非只能在浏览器或者HTML文档中才能使用,事实上在python或者C++等语言中只要能实现websocket协议报文,均可使用。
导读:https://blog.csdn.net/zhusongziye/article/details/80316127
客户端报文
- GET /mofang/websocket HTTP/1.1
- Host: 127.0.0.1
- Origin: http://127.0.0.1:5000
- Upgrade: websocket
- Connection: Upgrade
- Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ== # Sec-WebSocket-Key 是随机生成的
- Sec-WebSocket-Version: 13
服务端报文
- HTTP/1.1 101 Switching Protocols
- Upgrade: websocket
- Connection: Upgrade
- Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= # 结合客户端提供的Sec-WebSocket-Key基于固定算法计算出来的
- Sec-WebSocket-Protocol: chat
2.WebSocket与Socket的关系
他们两的关系就像Java和JavaScript,并非完全没有关系,只能说有点渊源。
Socket严格来说,其实并不是一个协议,而是为了方便开发者使用TCP或UDP协议而对TCP/IP协议进行封装出来的一组接口,是位于应用层和传输控制层之间的接口。通过Socket接口,我们可以更简单,更方便的使用TCP/IP协议。
WebSocket是实现了浏览器与服务器的全双工通信协议,一个模拟Socket的应用层协议。
2.服务端基于socket提供服务
1.安装并配置flask-socketio
在python中实现socket服务端的方式有非常多,一种最常用的有python-socketio
,而我们现在使用的flask框架也有一个基于python-socket
模块进行了封装的flask-socketio
模块.
官方文档:https://flask-socketio.readthedocs.io/en/latest/
注意:
因为目前还有会存在一小部分的设备或者应用是不支持websocket的.所以为了保证功能的可用性,我们使用socektio,但是由此带来了2个问题,必须要注意的:
python服务端使用基于socketio进行通信服务,则另一端必须也是基于socetio来进行对接通信,否则无法进行通信
socketio还有一个版本对应的问题, 版本不对应则无法通信.回报版本错误.
如果使用了javascript io 1.x或者2.x版本,则python-socketio或者flask-socketio的版本必须是4.x
如果使用了javascriptio 3.x版本,则python-socketio或者flask-socketio的版本必须是5.x.
我们当前使用的flask-socketio版本是5.x,所以javasctipt的socketio版本就必须是3.x.
终端下执行命令,安装:
- pip install flask-socketio
- pip install gevent-websocket
模块初始化,application/__init__.py
,代码:
- from flask_socketio import SocketIO
- # socketio
- socketio = SocketIO()
- def init_app(config_path):
- """全局初始化"""
- # socketio
- socketio.init_app(app, cors_allowed_origins=app.config["CORS_ALLOWED_ORIGINS"],async_mode=app.config["ASYNC_MODE"], debug=app.config["DEBUG"])
- # 改写runserver命令
- if sys.argv[1] == "runserver":
- manager.add_command("run", socketio.run(app,host=app.config["HOST"],port=app.config["PORT"]))
配置文件,application/settings/dev.py
,代码:
- # socketio
- CORS_ALLOWED_ORIGINS="*"
- ASYNC_MODE=None
- HOST="0.0.0.0"
- PORT=5000
application/utils/__init__.py
,在加载蓝图的过程中,自动加载socket服务端的api,代码:
- def init_blueprint(app):
- """自动注册蓝图"""
- ......
- # 加载蓝图内部的socket接口
- try:
- import_module(blueprint_path+".socket")
- except:
- pass
2.客户端引入socket.io.js
因为我们是基于python-socketio
模块提供的服务端,所以客户端必须基于socketIO.js
才能与其进行通信,所以客户端引入socketio.js。
socket.io.js的官方文档: https://socket.io/docs/v3
socket.io.js的github: https://github.com/socketio/socket.io/releases
我们可以新建一个orchard.html作为将来种植园模块的主页面,并在这个页面中使用socketio和服务端的flask-socketIO进行通信。
代码:
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title></title>
- <script type="text/javascript" src="../static/js/socket.io.js"></script>
- </head>
- <body>
- <script>
- // 命名空间
- namespace = '/mofang';
- var socket = io.connect('ws://192.168.20.251:5000' + namespace, {transports: ['websocket']});
- // socket.on('connect', function() {
- // console.log("客户端连接socket服务端");
- // });
- </script>
- </body>
- </html>
3.服务端创建orchard蓝图
服务端创建并注册蓝图目录orchard,终端命令如下:
- cd application/apps/
- python ../../manage.py blue -n=orchard
application/urls.py
,代码:
- from application.utils import include
- urlpatterns = [
- include("","home.urls"),
- include("/users","users.urls"),
- include("/marsh","marsh.urls"),
- include("/orchard","orchard.urls"),
applicaion/settings/dev.py
,代码:
- # 注册蓝图
- INSTALLED_APPS = [
- "application.apps.home",
- "application.apps.users",
- "application.apps.marsh",
- "application.apps.orchard",
- ]
4.创建socket连接
在蓝图下面创建socket.py文件,并提供连接接口, orchard/socket.py
:
- from application import socketio
- from flask import request
- @socketio.on("connect", namespace="/mofang")
- def user_connect():
- # request.sid socketIO基于客户端生成的唯一会话ID
- print("用户%s连接过来了!" % request.sid)
- @socketio.on("disconnect", namespace="/mofang")
- def user_disconnect():
- print("用户%s退出了种植园" % request.sid )
5.客户端vue结合socketio
1.orchard.html
orchard.html
客户端代码:
- <!DOCTYPE html>
- <html>
- <head>
- <title>用户中心</title>
- <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
- <meta charset="utf-8">
- <link rel="stylesheet" href="../static/css/main.css">
- <script src="../static/js/vue.js"></script>
- <script src="../static/js/axios.js"></script>
- <script src="../static/js/main.js"></script>
- <script src="../static/js/uuid.js"></script>
- <script src="../static/js/settings.js"></script>
- <script src="../static/js/socket.io.js"></script>
- </head>
- <body>
- <div class="app orchard" id="app">
- <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
- <div class="orchard-bg">
- <img src="../static/images/bg2.png">
- <img class="board_bg2" src="../static/images/board_bg2.png">
- </div>
- <img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
- </div>
- <script>
- apiready = function(){
- init();
- new Vue({
- el:"#app",
- data(){
- return {
- music_play:true,
- namespace: '/mofang_orchard',
- token:"",
- socket: null,
- timeout: 0,
- prev:{name:"",url:"",params:{}},
- current:{name:"orchard",url:"orchard.html",params:{}},
- }
- },
- created(){
- this.checkout();
- },
- methods:{
- checkout(){
- var token = this.game.get("access_token") || this.game.fget("access_token");
- this.game.checkout(this,token,(new_access_token)=>{
- this.connect();
- });
- },
- connect(){
- // socket连接
- this.socket = io.connect(this.settings.socket_server + this.namespace, {transports: ['websocket']});
- this.socket.on('connect', ()=>{
- this.game.print("开始连接服务端");
- });
- },
- go_index(){
- this.game.outWin("orchard");
- },
- }
- });
- }
- </script>
- </body>
- </html>
2.orchard的CSS样式
css样式,main.css
,代码:


- .app .orchard-bg{
- margin: 0 auto;
- width: 100%;
- max-width: 100rem;
- position: absolute;;
- z-index: -1;
- top: -6rem;
- }
- .app .orchard-bg .board_bg2{
- position: absolute;
- top: 1rem;
- }
- .orchard .back{
- position: absolute;
- width: 3.83rem;
- height: 3.89rem;
- z-index: 1;
- top: 2rem;
- left: 2rem;
- }
- .orchard .music{
- right: 2rem;
- }
- .orchard .header{
- position: absolute;
- top: 0rem;
- left: 0;
- right: 0;
- margin: auto;
- width: 32rem;
- height: 19.28rem;
- }
- .orchard .info{
- position: absolute;
- z-index: 1;
- top: 0rem;
- left: 4.4rem;
- width: 8rem;
- height: 9.17rem;
- }
- .orchard .info .avata{
- width: 8rem;
- height: 8rem;
- position: relative;
- }
- .orchard .info .avatar_bf{
- position: absolute;
- z-index: 1;
- margin: auto;
- width: 6rem;
- height: 6rem;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- }
- .orchard .info .user_avatar{
- position: absolute;
- z-index: 1;
- width: 6rem;
- height: 6rem;
- margin: auto;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- border-radius: 1rem;
- }
- .orchard .info .avatar_border{
- position: absolute;
- z-index: 1;
- margin: auto;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- width: 7.2rem;
- height: 7.2rem;
- }
- .orchard .info .user_name{
- position: absolute;
- left: 8rem;
- top: 1rem;
- width: 11rem;
- height: 3rem;
- line-height: 3rem;
- font-size: 1.5rem;
- text-shadow: 1px 1px 1px #aaa;
- border-radius: 3rem;
- background: #ff9900;
- text-align: center;
- }
- .orchard .wallet{
- position: absolute;
- top: 3.4rem;
- right: 4rem;
- width: 16rem;
- height: 10rem;
- }
- .orchard .wallet .balance{
- margin-top: 1.4rem;
- float: left;
- margin-right: 1rem;
- }
- .orchard .wallet .title{
- color: #fff;
- font-size: 1.2rem;
- width: 6.4rem;
- text-align: center;
- }
- .orchard .wallet .title img{
- width: 1.4rem;
- margin-right: 0.2rem;
- vertical-align: sub;
- height: 1.4rem;
- }
- .orchard .wallet .num{
- background: url("../images/btn3.png") no-repeat 0 0;
- background-size: 100%;
- width: 6.4rem;
- font-size: 0.8rem;
- color: #fff;
- height: 2rem;
- line-height: 1.8rem;
- text-indent: 1rem;
- }
- .orchard .header .menu-list{
- position: absolute;
- top: 9rem;
- left: 2rem;
- }
- .orchard .header .menu-list .menu{
- color: #fff;
- font-size: 1rem;
- float: left;
- width: 4rem;
- height: 4rem;
- text-align: center;
- margin-right: 2rem;
- }
- .orchard .header .menu-list .menu img{
- width: 3.33rem;
- height: 3.61rem;
- display: block;
- margin: auto;
- margin-bottom: 0.4rem;
- }
- .orchard .footer{
- position: absolute;
- width: 100%;
- height: 6rem;
- bottom: -2rem;
- background: url("../images/board_bg3.png") no-repeat -1rem 0;
- background-size: 110%;
- }
- .orchard .footer .menu-list{
- width: 100%;
- height: 4rem;
- display: flex;
- position: absolute;
- top: -1rem;
- }
- .orchard .footer .menu-list .menu,
- .orchard .footer .menu-list .menu-center{
- float: left;
- width: 4.44rem;
- height: 5.2rem;
- font-size: 1.5rem;
- color: #fff;
- line-height: 4.44rem;
- text-align: center;
- background: url("../images/btn5.png") no-repeat 0 0;
- background-size: 100%;
- flex: 1;
- margin-left: 4px;
- margin-right: 4px;
- }
- .orchard .footer .menu-list .menu-center{
- background: url("../images/btn6.png") no-repeat 0 0;
- background-size: 100%;
- flex: 2;
- }
orchard的CSS样式
6.基于事件接受信息
1.基于未定义事件进行通信
orchard.html
客户端代码:


- <!DOCTYPE html>
- <html>
- <head>
- <title>用户中心</title>
- <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
- <meta charset="utf-8">
- <link rel="stylesheet" href="../static/css/main.css">
- <script src="../static/js/vue.js"></script>
- <script src="../static/js/axios.js"></script>
- <script src="../static/js/main.js"></script>
- <script src="../static/js/uuid.js"></script>
- <script src="../static/js/settings.js"></script>
- <script src="../static/js/socket.io.js"></script>
- </head>
- <body>
- <div class="app orchard" id="app">
- <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
- <div class="orchard-bg">
- <img src="../static/images/bg2.png">
- <img class="board_bg2" src="../static/images/board_bg2.png">
- </div>
- <img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
- </div>
- <script>
- apiready = function(){
- init();
- new Vue({
- el:"#app",
- data(){
- return {
- music_play:true,
- token:"",
- socket: null,
- timeout: 0,
- prev:{name:"",url:"",params:{}},
- current:{name:"orchard",url:"orchard.html",params:{}},
- }
- },
- created(){
- this.checkout();
- },
- methods:{
- checkout(){
- var token = this.game.get("access_token") || this.game.fget("access_token");
- this.game.checkout(this,token,(new_access_token)=>{
- this.connect();
- });
- },
- connect(){
- // socket连接
- this.socket = io.connect(this.settings.socket_server + this.settings.socket_namespace, {transports: ['websocket']});
- this.socket.on('connect', ()=>{
- this.game.print("开始连接服务端");
- this.login();
- });
- },
- login(){
- var id = this.game.fget("id");
- // ***通过send方法可以直接发送数据,不需要自定义事件,数据格式是json格式***
- this.socket.send({"uid":id});
- },
- go_index(){
- this.game.outWin("orchard");
- },
- }
- });
- }
- </script>
- </body>
- </html>
基于未定义事件进行通信
服务端orchard/socket.py
代码:
- from application import socketio
- from flask import request
- # 未定义事件通信,客户端没有指定事件名称
- @socketio.on("message",namespace="/mofang")
- def user_message(data):
- print("接收到来自%s发送的数据:" % request.sid)
- print(">>>>data:",data)
- print(">>>>uid:"data["uid"])
2.基于自定义事件进行通信
orchard.html
客户端代码:


- <!DOCTYPE html>
- <html>
- <head>
- <title>用户中心</title>
- <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
- <meta charset="utf-8">
- <link rel="stylesheet" href="../static/css/main.css">
- <script src="../static/js/vue.js"></script>
- <script src="../static/js/axios.js"></script>
- <script src="../static/js/main.js"></script>
- <script src="../static/js/uuid.js"></script>
- <script src="../static/js/settings.js"></script>
- <script src="../static/js/socket.io.js"></script>
- </head>
- <body>
- <div class="app orchard" id="app">
- <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
- <div class="orchard-bg">
- <img src="../static/images/bg2.png">
- <img class="board_bg2" src="../static/images/board_bg2.png">
- </div>
- <img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
- </div>
- <script>
- apiready = function(){
- init();
- new Vue({
- el:"#app",
- data(){
- return {
- music_play:true,
- token:"",
- socket: null,
- timeout: 0,
- prev:{name:"",url:"",params:{}},
- current:{name:"orchard",url:"orchard.html",params:{}},
- }
- },
- created(){
- this.checkout();
- },
- methods:{
- checkout(){
- var token = this.game.get("access_token") || this.game.fget("access_token");
- this.game.checkout(this,token,(new_access_token)=>{
- this.connect();
- });
- },
- connect(){
- // socket连接
- this.socket = io.connect(this.settings.socket_server + this.settings.socket_namespace, {transports: ['websocket']});
- this.socket.on('connect', ()=>{
- this.game.print("开始连接服务端");
- this.login();
- });
- },
- login(){
- var id = this.game.fget("id");
- // **自定义事件用emit提交**
- this.socket.emit("login",{"uid":id});
- },
- go_index(){
- this.game.outWin("orchard");
- },
- }
- });
- }
- </script>
- </body>
- </html>
基于自定义事件进行通信
服务端orchard/socket.html
代码:
- from application import socketio
- from flask import request
- # 自定义事件通信
- @socketio.on("login", namespace="/mofang")
- def user_login(data):
- print("接受来自客户端%s发送的数据:" % request.sid)
- print(data)
- print(data["uid"])
3.服务端响应信息
1.服务端响应信息给客户端
服务端通过socketio.emit将内容响应给客户端
- from application import socketio
- from flask import request
- from application.apps.users.models import User
- # 建立socket通信
- @socketio.on("connect", namespace="/mofang")
- def user_connect():
- # request.sid socketIO基于客户端生成的唯一会话ID
- print("用户%s连接过来了!" % request.sid)
- # ***主动响应数据给客户端***
- length = User.query.count()
- socketio.emit("server_response",{"count":length},namespace="/mofang")
客户端接收响应信息,orchard.html
代码:


- <!DOCTYPE html>
- <html>
- <head>
- <title>用户中心</title>
- <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
- <meta charset="utf-8">
- <link rel="stylesheet" href="../static/css/main.css">
- <script src="../static/js/vue.js"></script>
- <script src="../static/js/axios.js"></script>
- <script src="../static/js/main.js"></script>
- <script src="../static/js/uuid.js"></script>
- <script src="../static/js/settings.js"></script>
- <script src="../static/js/socket.io.js"></script>
- </head>
- <body>
- <div class="app orchard" id="app">
- <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
- <div class="orchard-bg">
- <img src="../static/images/bg2.png">
- <img class="board_bg2" src="../static/images/board_bg2.png">
- </div>
- <img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
- </div>
- <script>
- apiready = function(){
- init();
- new Vue({
- el:"#app",
- data(){
- return {
- music_play:true,
- token:"",
- socket: null,
- timeout: 0,
- prev:{name:"",url:"",params:{}},
- current:{name:"orchard",url:"orchard.html",params:{}},
- }
- },
- created(){
- this.checkout();
- },
- methods:{
- checkout(){
- var token = this.game.get("access_token") || this.game.fget("access_token");
- this.game.checkout(this,token,(new_access_token)=>{
- this.connect();
- });
- },
- connect(){
- // socket连接
- this.socket = io.connect(this.settings.socket_server + this.settings.socket_namespace, {transports: ['websocket']});
- this.socket.on('connect', ()=>{
- this.game.print("开始连接服务端");
- this.login();
- this.get_count();
- });
- },
- // **客户端接受响应信息**
- get_count(){
- this.socket.on("server_response",(res)=>{
- this.game.print(res.count);
- alert(`欢迎来到种植园,当前有${res.count}人在忙碌着~`)
- });
- },
- login(){
- var id = this.game.fget("id");
- this.socket.emit("login",{"uid":id});
- },
- go_index(){
- this.game.outWin("orchard");
- },
- }
- });
- }
- </script>
- </body>
- </html>
客户端接受响应信息
2.基于房间管理分发信息
- from application import socketio
- from flask import request
- from application.apps.users.models import User
- from flask_socketio import join_room, leave_room # ****
- # 自定义事件通信
- @socketio.on("login", namespace="/mofang")
- def user_login(data):
- print("接受来自客户端%s发送的数据:" % request.sid)
- print(data)
- # ***一般基于用户id分配不同的房间***
- room = data["uid"]
- join_room(room)
- socketio.emit("login_response", {"data": "登录成功"}, namespace="/mofang", room=room)
客户端代码:


- <!DOCTYPE html>
- <html>
- <head>
- <title>用户中心</title>
- <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
- <meta charset="utf-8">
- <link rel="stylesheet" href="../static/css/main.css">
- <script src="../static/js/vue.js"></script>
- <script src="../static/js/axios.js"></script>
- <script src="../static/js/main.js"></script>
- <script src="../static/js/uuid.js"></script>
- <script src="../static/js/settings.js"></script>
- <script src="../static/js/socket.io.js"></script>
- </head>
- <body>
- <div class="app orchard" id="app">
- <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
- <div class="orchard-bg">
- <img src="../static/images/bg2.png">
- <img class="board_bg2" src="../static/images/board_bg2.png">
- </div>
- <img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
- </div>
- <script>
- apiready = function(){
- init();
- new Vue({
- el:"#app",
- data(){
- return {
- music_play:true,
- token:"",
- socket: null,
- timeout: 0,
- prev:{name:"",url:"",params:{}},
- current:{name:"orchard",url:"orchard.html",params:{}},
- }
- },
- created(){
- this.checkout();
- },
- methods:{
- checkout(){
- var token = this.game.get("access_token") || this.game.fget("access_token");
- this.game.checkout(this,token,(new_access_token)=>{
- this.connect();
- });
- },
- connect(){
- // socket连接
- this.socket = io.connect(this.settings.socket_server + this.settings.socket_namespace, {transports: ['websocket']});
- this.socket.on('connect', ()=>{
- this.game.print("开始连接服务端");
- this.login();
- this.get_count();
- this.login_response(); //****
- });
- },
- // ****
- login_response(){
- this.socket.on("login_response",(res)=>{
- alert(res.data);
- });
- },
- get_count(){
- this.socket.on("server_response",(res)=>{
- this.game.print(res.count);
- alert(`欢迎${res.sid}来到种植园,当前有${res.count}人在忙碌着~`);
- });
- },
- login(){
- var id = this.game.fget("id");
- this.socket.emit("login",{"uid":id});
- },
- go_index(){
- this.game.outWin("orchard");
- },
- }
- });
- }
- </script>
- </body>
- </html>
基于房间管理分发信息
3.服务端定时推送数据
- from application import socketio
- from flask import request
- from application.apps.users.models import User
- from flask_socketio import join_room, leave_room
- # 建立socket通信
- @socketio.on("connect", namespace="/mofang")
- def user_connect():
- # request.sid socketIO基于客户端生成的唯一会话ID
- print("用户%s连接过来了!" % request.sid)
- # 主动响应数据给客户端
- length = User.query.count()
- socketio.emit("server_response",{"count":length,"sid":"%s"% request.sid},namespace="/mofang")
- # 断开socket通信
- @socketio.on("disconnect", namespace="/mofang")
- def user_disconnect():
- print("用户%s退出了种植园" % request.sid )
- # 未定义事件通信,客户端没有指定事件名称
- @socketio.on("message",namespace="/mofang")
- def user_message(data):
- print("接收到来自%s发送的数据:" % request.sid)
- print(data)
- print(data["uid"])
- # 自定义事件通信
- @socketio.on("login", namespace="/mofang")
- def user_login(data):
- print("接受来自客户端%s发送的数据:" % request.sid)
- print(data)
- # 一般基于用户id分配不同的房间
- room = data["uid"]
- join_room(room)
- socketio.emit("login_response", {"data": "登录成功"}, namespace="/mofang", room=room)
- """***定时推送数据***"""
- from threading import Lock
- import random
- thread = None
- thread_lock = Lock()
- @socketio.on('chat', namespace='/mofang')
- def chat(data):
- global thread
- with thread_lock:
- if thread is None:
- thread = socketio.start_background_task(target=background_thread)
- def background_thread(uid):
- while True:
- socketio.sleep(1)
- t = random.randint(1, 100)
- socketio.emit('server_response',
- {'count': t},namespace='/mofang')
客户端代码:


- <!DOCTYPE html>
- <html>
- <head>
- <title>用户中心</title>
- <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
- <meta charset="utf-8">
- <link rel="stylesheet" href="../static/css/main.css">
- <script src="../static/js/vue.js"></script>
- <script src="../static/js/axios.js"></script>
- <script src="../static/js/main.js"></script>
- <script src="../static/js/uuid.js"></script>
- <script src="../static/js/settings.js"></script>
- <script src="../static/js/socket.io.js"></script>
- </head>
- <body>
- <div class="app orchard" id="app">
- <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
- <div class="orchard-bg">
- <img src="../static/images/bg2.png">
- <img class="board_bg2" src="../static/images/board_bg2.png">
- </div>
- <img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
- <h1 style="position:absolute;top:20rem;">{{num}}</h1>
- </div>
- <script>
- apiready = function(){
- init();
- new Vue({
- el:"#app",
- data(){
- return {
- music_play:true,
- token:"",
- num:"",
- socket: null,
- timeout: 0,
- prev:{name:"",url:"",params:{}},
- current:{name:"orchard",url:"orchard.html",params:{}},
- }
- },
- created(){
- this.checkout();
- },
- methods:{
- checkout(){
- var token = this.game.get("access_token") || this.game.fget("access_token");
- this.game.checkout(this,token,(new_access_token)=>{
- this.connect();
- });
- },
- connect(){
- // socket连接
- this.socket = io.connect(this.settings.socket_server + this.settings.socket_namespace, {transports: ['websocket']});
- this.socket.on('connect', ()=>{
- this.game.print("开始连接服务端");
- this.login();
- this.get_count();
- this.login_response();
- });
- },
- login_response(){
- this.socket.on("login_response",(res)=>{
- alert(res.data);
- });
- },
- get_count(){
- this.socket.on("server_response",(res)=>{
- this.num = res.count;
- // alert(`欢迎${res.sid}来到种植园,当前有${res.count}人在忙碌着~`);
- });
- },
- login(){
- var id = this.game.fget("id");
- // this.socket.emit("login",{"uid":id});
- this.socket.emit("chat",{"uid":id}) // ****
- },
- go_index(){
- this.game.outWin("orchard");
- },
- }
- });
- }
- </script>
- </body>
- </html>
服务端定时推送数据
4.服务端推送广播信息
- from flask_socketio import emit
- @socketio.on('my_broadcast', namespace='/mofang')
- def my_broadcast(data):
- emit('broadcast_response', data, broadcast=True)
- socketio.emit('some event', {'data': 42})
- # 只要不声明房间ID,则默认返回给整个命名空间下所有的用户都可以接收
4.种植园页面展示
1.种植园主页面显示
主框架,html/orchard.html
,代码:


- <!DOCTYPE html>
- <html>
- <head>
- <title>用户中心</title>
- <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
- <meta charset="utf-8">
- <link rel="stylesheet" href="../static/css/main.css">
- <script src="../static/js/vue.js"></script>
- <script src="../static/js/axios.js"></script>
- <script src="../static/js/main.js"></script>
- <script src="../static/js/uuid.js"></script>
- <script src="../static/js/settings.js"></script>
- <script src="../static/js/socket.io.js"></script>
- </head>
- <body>
- <div class="app orchard" id="app">
- <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
- <div class="orchard-bg">
- <img src="../static/images/bg2.png">
- <img class="board_bg2" src="../static/images/board_bg2.png">
- </div>
- <img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
- <div class="header">
- <div class="info">
- <div class="avatar">
- <img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
- <img class="user_avatar" src="../static/images/avatar.png" alt="">
- <img class="avatar_border" src="../static/images/avatar_border.png" alt="">
- </div>
- <p class="user_name">好听的昵称</p>
- </div>
- <div class="wallet">
- <div class="balance">
- <p class="title"><img src="../static/images/money.png" alt="">钱包</p>
- <p class="num">99,999.00</p>
- </div>
- <div class="balance">
- <p class="title"><img src="../static/images/integral.png" alt="">果子</p>
- <p class="num">99,999.00</p>
- </div>
- </div>
- <div class="menu-list">
- <div class="menu">
- <img src="../static/images/menu1.png" alt="">
- 排行榜
- </div>
- <div class="menu">
- <img src="../static/images/menu2.png" alt="">
- 签到有礼
- </div>
- <div class="menu">
- <img src="../static/images/menu3.png" alt="">
- 道具商城
- </div>
- <div class="menu">
- <img src="../static/images/menu4.png" alt="">
- 邮件中心
- </div>
- </div>
- </div>
- <div class="footer">
- <ul class="menu-list">
- <li class="menu">新手</li>
- <li class="menu">背包</li>
- <li class="menu-center">商店</li>
- <li class="menu">消息</li>
- <li class="menu">好友</li>
- </ul>
- </div>
- </div>
- <script>
- apiready = function(){
- init();
- new Vue({
- el:"#app",
- data(){
- return {
- music_play:true,
- namespace: '/mofang_orchard',
- token:"",
- socket: null,
- timeout: 0,
- prev:{name:"",url:"",params:{}},
- current:{name:"orchard",url:"orchard.html",params:{}},
- }
- },
- created(){
- this.checkout();
- },
- methods:{
- checkout(){
- var token = this.game.get("access_token") || this.game.fget("access_token");
- this.game.checkout(this,token,(new_access_token)=>{
- this.connect();
- });
- },
- connect(){
- // socket连接
- this.socket = io.connect(this.settings.socket_server + this.namespace, {transports: ['websocket']});
- this.socket.on('connect', ()=>{
- this.game.print("开始连接服务端");
- });
- },
- go_index(){
- this.game.outWin("orchard");
- },
- }
- });
- }
- </script>
- </body>
- </html>
种植园主页面显示:orchard.html
2.我的种植园页面显示
我的果园,html/my_orchard.html
,代码:


- <!DOCTYPE html>
- <html>
- <head>
- <title>用户中心</title>
- <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
- <meta charset="utf-8">
- <link rel="stylesheet" href="../static/css/main.css">
- <script src="../static/js/vue.js"></script>
- <script src="../static/js/axios.js"></script>
- <script src="../static/js/main.js"></script>
- <script src="../static/js/uuid.js"></script>
- <script src="../static/js/settings.js"></script>
- <script src="../static/js/socket.io.js"></script>
- </head>
- <body>
- <div class="app orchard orchard-frame" id="app">
- <div class="background">
- <img class="grassland2" src="../static/images/grassland2.png" alt="">
- <img class="mushroom1" src="../static/images/mushroom1.png" alt="">
- <img class="stake1" src="../static/images/stake1.png" alt="">
- <img class="stake2" src="../static/images/stake2.png" alt="">
- </div>
- <div class="pet-box">
- <div class="pet">
- <img class="pet-item" src="../static/images/pet1.png" alt="">
- </div>
- <div class="pet turned_off">
- <img class="turned_image" src="../static/images/turned_off.png" alt="">
- <p>请购买宠物</p>
- </div>
- </div>
- <div class="tree-list">
- <div class="tree-box">
- <div class="tree">
- <img src="../static/images/tree4.png" alt="">
- </div>
- <div class="tree">
- <img src="../static/images/tree3.png" alt="">
- </div>
- <div class="tree">
- <img src="../static/images/tree4.png" alt="">
- </div>
- </div>
- <div class="tree-box">
- <div class="tree">
- <img src="../static/images/tree3.png" alt="">
- </div>
- <div class="tree">
- <img src="../static/images/tree2.png" alt="">
- </div>
- <div class="tree">
- <img src="../static/images/tree2.png" alt="">
- </div>
- </div>
- <div class="tree-box">
- <div class="tree">
- <img src="../static/images/tree1.png" alt="">
- </div>
- <div class="tree">
- <img src="../static/images/tree0.png" alt="">
- </div>
- <div class="tree">
- <img src="../static/images/tree0.png" alt="">
- </div>
- </div>
- </div>
- <div class="prop-list">
- <div class="prop">
- <img src="../static/images/prop1.png" alt="">
- <span>1</span>
- <p>化肥</p>
- </div>
- <div class="prop">
- <img src="../static/images/prop2.png" alt="">
- <span>0</span>
- <p>修剪</p>
- </div>
- <div class="prop">
- <img src="../static/images/prop3.png" alt="">
- <span>1</span>
- <p>浇水</p>
- </div>
- <div class="prop">
- <img src="../static/images/prop4.png" alt="">
- <span>1</span>
- <p>宠物粮</p>
- </div>
- </div>
- <div class="pet-hp-list">
- <div class="pet-hp">
- <p>宠物1 饱食度</p>
- <div class="hp">
- <div style="width: 85%;" class="process">85%</div>
- </div>
- </div>
- <div class="pet-hp">
- <p>宠物2 饱食度</p>
- <div class="hp">
- <div style="width: 0;" class="process">0%</div>
- </div>
- </div>
- </div>
- </div>
- <script>
- apiready = function(){
- init();
- new Vue({
- el:"#app",
- data(){
- return {
- namespace: '/mofang_orchard',
- token:"",
- socket: null,
- timeout: 0,
- prev:{name:"",url:"",params:{}},
- current:{name:"orchard",url:"orchard.html",params:{}},
- }
- },
- created(){
- },
- methods:{
- }
- });
- }
- </script>
- </body>
- </html>
我的种植园页面显示:my_orchard.html
3.种植园CSS样式
css样式,代码:


- .orchard-frame .background{
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100rem;
- }
- .orchard-frame .background .grassland1{
- width: 31.22rem;
- height: 13.53rem;
- position: absolute;
- top: 4rem;
- }
- .orchard-frame .background .grassland2{
- width: 31.22rem;
- height: 13.53rem;
- position: absolute;
- top: 5rem;
- }
- .orchard-frame .background .mushroom1{
- width: 4.56rem;
- height: 4.83rem;
- position: absolute;
- right: 1rem;
- top: 11rem;
- }
- .orchard-frame .background .stake1{
- width: 4.56rem;
- height: 4.83rem;
- position: absolute;
- top: 3rem;
- left: 0rem;
- }
- .orchard-frame .background .stake2{
- width: 6.31rem;
- height: 4.83rem;
- position: absolute;
- top: 3rem;
- left: 13rem;
- }
- .orchard-frame .pet-box{
- position: absolute;
- top: -2rem;
- left: 0;
- display: flex;
- }
- .orchard-frame .pet-box .pet{
- position: relative;
- width: 14.16rem;
- height: 15rem;
- flex: 1;
- margin-left: 1rem;
- margin-right: 1rem;
- background: url("../images/tree1.png") no-repeat 0 -0.5rem;
- background-size: 100%;
- }
- .orchard-frame .pet-box .turned_off .turned_image{
- width: 5.14rem;
- height: 6.83rem;
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- margin: auto;
- }
- .orchard-frame .pet-box .turned_off p{
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- margin: auto;
- border: 1px solid #fff;
- border-radius: 1rem;
- width: 8rem;
- height: 3rem;
- line-height: 3rem;
- font-size: 1.5rem;
- word-wrap: break-word;
- padding: 1rem;
- color: #000;
- text-align: center;
- background: rgba(255,255,255,.6);
- }
- .orchard-frame .pet-box .pet-item{
- width: 10rem;
- height: 10rem;
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- margin: auto;
- }
- .orchard-frame .tree-list{
- position: absolute;
- top: 9rem;
- width: 100%;
- }
- .orchard-frame .tree-box{
- margin-left: 3rem;
- margin-right: 3rem;
- }
- .orchard-frame .tree-box .tree{
- width: 9rem;
- height: 4rem;
- margin-bottom: 2rem;
- float: left;
- }
- .orchard-frame .tree-box .tree img{
- width: 9rem;
- height: 8rem;
- max-height: 8rem;
- }
- .orchard-frame .prop-list{
- position: absolute;
- bottom: 6rem;
- width: 100%;
- }
- .orchard-frame .prop-list .prop{
- float: left;
- margin-left: 1rem;
- width: 3rem;
- position: relative;
- }
- .orchard-frame .prop-list .prop img{
- width: 2.5rem;
- height: 2.5rem;
- margin: auto;
- display: block;
- }
- .orchard-frame .prop-list .prop span{
- position: absolute;
- top: -4px;
- right: -4px;
- border-radius: 50%;
- width: 1rem;
- height; 1rem;
- font-size: .8rem;
- color: #fff;
- background-color: #cc0000;
- text-align: center;
- line-height: 1rem;
- padding: 2px;
- }
- .orchard-frame .prop-list .prop p{
- text-align: center;
- color: #fff;
- }
- .orchard-frame .pet-hp-list{
- position: absolute;
- right: 0;
- bottom: 8rem;
- width: 11rem;
- height: 4rem;
- }
- .orchard-frame .pet-hp-list .pet-hp{
- margin-bottom: 5px;
- }
- .orchard-frame .pet-hp-list .pet-hp p{
- }
- .orchard-frame .pet-hp-list .pet-hp .hp{
- border: 1px solid #fff;
- border-radius: 5rem;
- width: 10rem;
- padding: 1px;
- }
- .orchard-frame .pet-hp-list .pet-hp .process{
- font-size: 0.5rem;
- background-color: red;
- color: #fff;
- border-radius: 5rem;
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- text-align: center;
- }
种植园CSS样式代码
day112:MoFang:种植园使用websocket代替http&服务端基于flask-socketio提供服务&服务端响应信息&种植园页面显示初始化的更多相关文章
- [转]python实现RESTful服务(基于flask)
python实现RESTful服务(基于flask) 原文: https://www.jianshu.com/p/6ac1cab17929 前言 上一篇文章讲到如何用java实现RESTful服务, ...
- .net core微服务之基于Docker+Consul+Registrator服务注册服务发现
一.Docker部分: 先拉最新的asp.net core的镜像: docker pull microsoft/aspnetcore 将下载下来的镜像重命名,为什么要重命名?等会讲Registrato ...
- .net core 跨平台开发 微服务架构 基于Nginx反向代理 服务集群负载均衡
1.概述 反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客 ...
- python实现RESTful服务(基于flask)
https://www.jianshu.com/p/6ac1cab17929 http://www.pythondoc.com/flask/quickstart.html 在java中调用python ...
- python实现Restful服务(基于flask)(2)
参考:https://blog.csdn.net/yelena_11/article/details/53404892 最简单的post例子: from flask import Flask, req ...
- iUAP云运维平台v3.0全面支持基于K8s的微服务架构
什么是微服务架构? 微服务(MicroServices)架构是当前互联网业界的一个技术热点,业内各公司也都纷纷开展微服务化体系建设.微服务架构的本质,是用一些功能比较明确.业务比较精练的服务去解决更大 ...
- 微服务架构:构建PHP微服务生态
微服务架构:构建PHP微服务生态 Linux系统技术交流QQ群(1675603)验证问题答案:刘遄 导读 诞生于 2014 年的“微服务架构”,其思想经由 Martin Fowler 阐述后,在近 ...
- 【认知服务 Azure Cognitive Service】使用认知服务的密钥无法访问语音服务[ErrorCode=AuthenticationFailure] (2020-08时的遇见的问题,2020-09月已解决)
问题情形 根据微软认知服务的文档介绍,创建认知服务(Cognitive Service)后,可以调用微软的影像(计算机视觉,人脸),语言(LUIS, 文本分析,文本翻译),语音(文本转语音,语音转文本 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(十二)——istio+dapr构建多运行时服务网格
多运行时是一个非常新的概念.在 2020 年,Bilgin Ibryam 提出了 Multi-Runtime(多运行时)的理念,对基于 Sidecar 模式的各种产品形态进行了实践总结和理论升华.那到 ...
随机推荐
- java8新特性LocalDate、LocalTime、LocalDateTime的学习
以前操作时间都是使用SimpleDateFormat类改变Date的时间格式,使用Calendar类操作时间.但是SimpleDateFormat是线程不安全的,源码如下: private Strin ...
- FL Studio钢琴卷轴之工具菜单的Riff命令
鼠标左键点击FL Studio钢琴卷轴窗口中的"工具"命令,我们就可以打开快捷工具菜单.快捷菜单中包含了用于音符编辑的各种工具.按照该菜单的顺序,我们先来看一下什么是Riff器命令 ...
- guitar pro系列教程(二十七):Guitar Pro教程之理解记谱法
前面的章节我们讲解了很多关于Guitar Pro'的功能使用,今天小编还是采用图文结合的方式为大家讲解它的理解记谱法,对于很多新人来说,在我们看谱之前,我们肯定要先熟悉他的一些功能如何使用以及一些关于 ...
- JS&Swift相互交互
加载本地HTML文件 x override func loadView() { super.loadView() let conf = WKWebViewCon ...
- mysql 分组查询
mysql 分组查询 获取id最大的一条 (1)分组查询获取最大id SELECT MAX(id) as maxId FROM `d_table` GROUP BY `parent_id` ; (2) ...
- JQuery案例:折叠菜单
折叠菜单(jquery) <html> <head> <meta charset="UTF-8"> <title>accordion ...
- 测试:DOCX
先拿到的是需求文档和接口文档以及测试用例模块,[以及之前写好的测试用例]再根据分配的任务进行编写用例 [智能看懂业务需求]现有功能点,在编写用例 [项目介绍]: 辽阳农商惠生活项目是作为一个农户和银行 ...
- IEEE754标准浮点数表示与舍入
原文地址:https://blog.fanscore.cn/p/26/ 友情提示:本文排版不太好,但内容简单,请耐心观看,总会搞懂的. 1. 定点数 对于一个无符号二进制小数,例如101.111,如果 ...
- 我劝!这位年轻人不讲MVCC,耗子尾汁!
目录 一.事物的隔离级别与MVCC? 二.Repeatable Read是如何实现的 本文是MySQL专题第15篇,全文近100篇(公众号首发) 三.Read Commited是如何实现的: 本文是M ...
- jquery on 动态生成绑定事件
$(document).on("mouseenter", ".v6-div-kind-ok", function () { alert();});