day112:MoFang:種植園使用websocket代替http&服務端基於flask-socketio提供服務&服務端響應信息&種植園頁面顯示初始化

目錄

1.種植園使用websocket代替http

2.服務端基於socket提供服務

3.服務端響應信息

4.種植園頁面展示

1.種植園使用websocket代替http

我們需要完成的種植園,是一個互動頻繁,並且要求有一定即時性的模塊,所以如果繼續基於http協議開發,那麼需要通過ajax發送大量http請求,同時因為http本身屬於單向通訊,所以服務端無法主動發送信息提供給客戶端。所以對於客戶端使用來說,非常不友好,所以我們需要基於socket通訊來完成這個模塊的開發。當然,如果我們服務端基於socket實現tcp/ip通訊的同時,那麼客戶端必須也要使用websocket來實現tcp/ip通訊才能正常運作。

1.websocket協議簡介

文檔://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協議報文,均可使用。

導讀://blog.csdn.net/zhusongziye/article/details/80316127

客戶端報文

GET /mofang/websocket HTTP/1.1
Host: 127.0.0.1
Origin: //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模塊.

官方文檔://flask-socketio.readthedocs.io/en/latest/

注意:

因為目前還有會存在一小部分的設備或者應用是不支持websocket的.所以為了保證功能的可用性,我們使用socektio,但是由此帶來了2個問題,必須要注意的:

  1. python服務端使用基於socketio進行通信服務,則另一端必須也是基於socetio來進行對接通信,否則無法進行通信

  2. 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的官方文檔: //socket.io/docs/v3

socket.io.js的github: //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樣式代碼