前端筆記之微信小程序(四)WebSocket&Socket.io&搖一搖案例&地圖|地理位置


一、WebSocket概述

http://www.ruanyifeng.com/blog/2017/05/websocket.html

 

Workerman一款開源高性能異步PHP socket即時通訊框架https://workerman.net

 

HTTP是無連接的:有請求才會有響應,如果沒有請求,服務器想主動推送信息給瀏覽器是不可能的。

 

比如圖文直播、聊天室原理:長輪詢。

setInterval(function(){
$.get()
},1000)

間隔一定的時間,主動向服務器發起請求,詢問是否有新消息。

 

WebSocket是一種網絡通信協議,HTML5中的新協議。需要服務器和瀏覽器共同支持,實現全雙工通信。

 

服務器:PHP5.6Java1.7Nodejs 6以上。

瀏覽器:Android 6.0及以上版本。

WebSocket HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。

 

WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。

WebSocket API 中,瀏覽器和服務器只需要做一個握手的動作,然后,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。


二、Socket.io

socket.io是一個跨瀏覽器支持WebSocket的實時通訊的JSNodejs中實現socket非常好用的包。

 

API

https://www.npmjs.com/package/socket.io

https://socket.io/

npm install --save socket.io

默認有一個自動路由的js文件http://127.0.0.1:3000/socket.io/socket.io.js

 

 

前端代碼(從官網抄的模板):

<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
          <input id="m" autocomplete="off" />
        <button>Send</button>
    </form>
    <script type="text/javascript" src="/socket.io/socket.io.js"></script> <script type="text/javascript"> var socket = io(); </script>
  </body>
</html>

 

后端:

var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res){
      res.sendFile(__dirname + '/index.html');
});

//監聽客戶端,有用戶連接的時候觸發(建立前后端連接)
io.on('connection', function(socket){
      console.log('有個用戶連接了'); });

http.listen(3000);
node app.js

 

現在兩個端已經實時通訊連接上了:

 

 

消息收發的響應:

前端emit發:

<script type="text/javascript">
      var socket = io();
      $("button").click(function(){
             socket.emit("info", "你好");
             return false;
      });
</script>

 

服務端on收:

io.on('connection', function(socket){
      console.log('有個用戶連接了');
      socket.on("info", function(data){ console.log(data); });
});

接下來的事情:

實現聊天室功能:如果有某個客戶端用戶將消息發給了服務端,服務端要發給所有已經連接的客戶端,這里就涉及到廣播,廣播就是給所有已經連接服務端的socket對象進行集體的消息發送。

 

完整的聊天室前端:

<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
     ...
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
          <input id="m" autocomplete="off" />
        <button>Send</button>
    </form>
    <script type="text/javascript" src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <script type="text/javascript">
        var socket = io();

        //點擊按鈕發出數據
        $("button").click(function(){
            socket.emit("info" , {
                content : $("#m").val()
            });
            $("#m").val("");
            return false;  //阻止瀏覽器默認請求行為,禁止刷新頁面
        });

        //客戶端收到服務端的msg廣播消息的時候觸發的函數
        socket.on("msg", function(data){
            $("<li>" + data.content + "</li>").prependTo("ul");
        });
    </script>
  </body>
</html>

 

后端:

io.on('connection', function(socket){
      console.log('有個用戶連接了');
      //服務端收到了info的消息
      socket.on("info" , function(data){
          console.log(data.content);
          //立即廣播通知所有已連接的客戶端
          io.emit('msg', data);
      });
});

http.listen(3000);

三、微信小程序和WebSocket

小程序的上線版本,必須是https協議會wss協議(即websocket的安全版本)

 

如果結合微信小程序使用,Nodejs不能使用socket.io,因為socket.io在前端需要用script引入一個js文件,但是小程序不支持這樣引入。但是沒有關系,因為小程序自帶websocketAPI

 

 

前端開啟:

Page({
    onLoad(){
        wx.connectSocket({
            url: 'ws://127.0.0.1:8080',
        })
    }
})
示例代碼

 

后端需要安裝一個依賴:

https://www.npmjs.com/package/ws

npm install --save ws

后端app.js  

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws){
    console.log("有人鏈接了");
});
示例代碼

 

<!--index.wxml-->
<view class="container">
    <view class="t">{{a}}</view>
    <button bindtap="sendmsg">按我</button>
</view>
//index.js
Page({
    data: {
        a: 0
    },
    onLoad(){
        //前端發起websocket連接
        wx.connectSocket({
            // 可以在WiFi環境下的IP地址測試
            // url: 'ws://192.168.0.150:8080', 
            url: 'ws://127.0.0.1:8080'
        })

        //監聽WebSocket接受到服務器的廣播消息通知事件
        wx.onSocketMessage((res)=>{
            console.log(res.data)
            this.setData({
                a:res.data
            })
        })
    },
    //點擊按鈕發送消息給服務端
    send(){
        wx.sendSocketMessage({
            data: "你好!",
        })
    }
})
示例代碼

 

后端app.js

Nodejsws這個庫沒有廣播功能,必須讓開發者將socket對象存為數組,要廣播的時候,遍歷數組中每個項,依次給他們發送信息即可。

const WebSocket = require('ws');
//創建連接和監聽端口
const wss = new WebSocket.Server({port:8080});

var ws_arr = []; //存儲所有已經連接的人的ws對象
var a = 0;

//響應客戶端的連接
wss.on('connection', function(ws){
    console.log("有人連接了");
    ws_arr.push(ws); //將當前進來的人存儲到數組

    //監聽客戶端發送的消息
    ws.on("message", function(message){
        console.log("服務端收到了消息:" + message)

        a++;
        //遍歷所有人,廣播通知所有客戶端,把消息傳送給他們
        ws_arr.forEach(item=>{
            item.send(a);
        })
    })
})
示例代碼

四、搖一搖大PK

微信沒有提供搖一搖API,必須使用加速,自己寫代碼感應xyz的變化。

加速計的坐標軸如圖,是個三維的坐標。我們需要通過xyz三個軸的方向的加速度計算出搖動手機時,手機搖動方向的加速度。

 

 

 

index.js

page({
data:{
   x:0,
   y:0,
   z:0
},
onLoad(){
    var lastX = 0;
    var lastY = 0;
    wx.onAccelerometerChange((res)=>{
            //如果當前的x或y減去上一次x或y的差 大於0.5,就設定為搖一搖成功
            if(Math.abs(res.x - lastX) > 0.5 || Math.abs(res.y - lastY) > 0.5){
                wx.showToast({
                   title: "成功"
                });
                lastX = res.x;
                lastY = res.y;

                this.setData({
                   x : res.x,
                   y : res.y,
                   z : res.z 
                })
            }
            
})
}
})
示例代碼

后端app.js

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

//存儲所有人的ws對象
var ws_arr = [];
//存儲所有人的分數
// var score_arr = ["nickName":"測試賬戶","avatarUrl":"x.jpg", "n":0];
var score_arr = [];
var a = 0;

wss.on('connection', function(ws){
    console.log("有人鏈接了");
    ws_arr.push(ws); //將每個進來的用戶存儲到數組


    ws.on('message', function(message){
        console.log("服務器收到了推送:" + message);
        //變為JSON對象
        var messageObj = JSON.parse(message);
        //當搖一搖時,判斷數組中有沒有這個人,有就讓這個人的n++
        var isHave = false;
        score_arr.forEach(item=>{
            if(item.nickName == messageObj.nickName){
                item.n ++;
                isHave = true;
            }
        });
        //如果沒有就添加到數組中
        if(!isHave){
            score_arr.push({
                nickName : messageObj.nickName,
                avatarUrl: messageObj.avatarUrl,
                n : 0
            })
        }

        console.log({"score_arr" : score_arr})
        //廣播發送給客戶端(前端)
        ws_arr.forEach(item=>{
            item.send(JSON.stringify({
                "score_arr" : score_arr })); });  });
});

 

用戶搖一搖案例:

<!--index.wxml-->
<view class="container">
    <view class="userinfo">
        <button open-type="getUserInfo" bindgetuserinfo="getUserInfo">獲取頭像昵稱</button>
    </view>
    
    <view wx:for="{{arr}}">
        {{item.nickName}}
        <image style="width:90px;height:90px;" src="{{item.avatarUrl}}"></image>
        {{item.n}}
    </view>
</view>

 

index.js

const app = getApp()

Page({
    data: {
        userInfo: {},
        hasUserInfo: false,
        canIUse: wx.canIUse('button.open-type.getUserInfo'),
        arr : []
    },
    onLoad: function () {
        if (app.globalData.userInfo) {
            this.setData({
                userInfo: app.globalData.userInfo,
                hasUserInfo: true
            })
        } else if (this.data.canIUse) {
            // 由於 getUserInfo 是網絡請求,可能會在 Page.onLoad 之后才返回
            // 所以此處加入 callback 以防止這種情況
            app.userInfoReadyCallback = res => {
                this.setData({
                    userInfo: res.userInfo,
                    hasUserInfo: true
                })
            }
        } else {
            // 在沒有 open-type=getUserInfo 版本的兼容處理
            wx.getUserInfo({
                success: res => {
                    app.globalData.userInfo = res.userInfo
                    this.setData({
                        userInfo: res.userInfo,
                        hasUserInfo: true
                    })
                }
            })
        }

        //鏈接socket服務器(可以填WiFi的IP地址測試)
        wx.connectSocket({
            url: 'ws://127.0.0.1:8080'
        });

        //當socket連接打開后,監聽搖一搖:
        var self = this;
        var lastX = 0;
        wx.onSocketOpen(function(res){
            wx.onAccelerometerChange(function(res){
                //如果當前的x 減去上一次x的差 大於0.6,就設定為搖一搖成功
                if(Math.abs(res.x - lastX) > 0.6){
                    wx.showToast({
                        title: "搖一搖成功"
                    });
                    //告訴服務器我是誰
 wx.sendSocketMessage({
                        data: JSON.stringify({ "nickName": self.data.userInfo.nickName,
                            "avatarUrl": self.data.userInfo.avatarUrl })
                    })
                } lastX = res.x;
            }); }); //接收到服務器廣播信息的時候做的事情
        wx.onSocketMessage(function(res){
            var obj = JSON.parse(res.data); //轉對象
            var arr = obj.score_arr;
            //按照n值大小排序
            arr.sort((a,b)=>{
                return b.n - a.n
            })
            self.setData({arr}); //存儲到本地data中的arr數組
 });
    },
    getUserInfo: function (e) {
        console.log(e)
        app.globalData.userInfo = e.detail.userInfo
        this.setData({
            userInfo: e.detail.userInfo,
            hasUserInfo: true
        })
    } 
});

五、地圖和地理位置

騰訊地理位置服務https://lbs.qq.com/ 

地圖自己是不能定位的,需要獲取地理位置定位,而且地圖API和地理位置API是分開。

<!--index.wxml-->
<view class="container">
    <view class="userinfo">
        <button open-type="getUserInfo" bindgetuserinfo="getUserInfo">獲取頭像昵稱</button>
    </view>
    
   <map markers="{{markers}}" id="map" longitude="{{longitude}}" latitude="{{latitude}}" 
  scale
="14" style="width:100%;height:300px;"></map> </view>
Page({
    data: {
        markers : []
    },
    onLoad(){
        //頁面加載進來要先定位
        var self = this;
        wx.getLocation({
            type: 'gcj02',
            success: function(res){
                self.setData({
                    latitude: res.latitude, //緯度
                    longitude: res.longitude //經度
 }); }
       }); //連接socket服務器
        wx.connectSocket({
            url: 'ws://192.168.1.175:8080'
        });

//接收到服務器廣播信息的時候做的事情
        wx.onSocketMessage(function (res) {
            var obj = JSON.parse(res.data);
        console.log(obj)
}
//微信沒有提供當某人地理位置改變時候的on事件,所以setInterval()。
//每3秒更新一次定位,然后發送給服務端,服務端再通知客戶端
clearInterval(timer);
var timer = setInterval(function(){ wx.getLocation({
        type: 'gcj02',
        success: function(res){
            wx.sendSocketMessage({
                data: JSON.stringify({ "nickName": self.data.userInfo.nickName,
                    "avatarUrl": self.data.userInfo.avatarUrl,
                    "latitude": res.latitude,
                    "longitude": res.longitude })
            })
        }
  }); },3000);
    }
});

 

后端app.js

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

var ws_arr = []; //存儲所有人的ws對象
var location_arr = []; //存儲所有人的地理位置

wss.on('connection', function (ws) {
    console.log("有人鏈接了");
    //放入數組
    ws_arr.push(ws);
       
    ws.on('message', function (message) {
        console.log("服務器收到了推送" + message);
        //變為JSON對象
        var messageObj = JSON.parse(message);
        //判斷數組中有沒有我
        var isHave = false;
        location_arr.forEach(item=>{
            if(item.nickName == messageObj.nickName){
                item.latitude = messageObj.latitude;
                item.longitude = messageObj.longitude;
                isHave = true;
            }
        });
        //如果沒有
        if(!isHave){
            location_arr.push({
                nickName : messageObj.nickName,
                avatarUrl: messageObj.avatarUrl,
                latitude : messageObj.latitude, longitude: messageObj.longitude
            })
        }
        console.log({"location_arr" : location_arr})
    
        //廣播通知客戶端
        ws_arr.forEach(item=>{
            item.send(JSON.stringify({
                "location_arr" : location_arr
            }));
        });
    });
});

臨時設置大頭針markers

 

var tempPathObj = {}; //存儲這個人的昵稱,根據昵稱獲取頭像,如這個對象沒有這個人就要下載頭像
//接收到服務器廣播信息的時候做的事情
wx.onSocketMessage(function(res){
    var obj = JSON.parse(res.data);
    self.setData({ markers : [] //清空
});
//iconPath不支持網絡地址,要通過wx.download()接口下載得到臨時地址
obj.location_arr.forEach(item=>{
    //根據昵稱,判斷這個對象中有沒有這個人,如果有直接用
        if(tempPathObj.hasOwnProperty(self.data.userInfo.nickName)){
    self.setData({
                markers: [ ...self.data.markers,
                    {   //如果對象中有這個人,就直接用這個人的頭像
                        iconPath: tempPathObj[self.data.userInfo.nickName], 
                        id: 0,
                        latitude: item.latitude,
                        longitude: item.longitude,
                        width: 50,
                        height: 50
                    }
                ]
            });
        } else {
        //如果沒有就下載頭像,並且將這個人存起來,以后可以直接用
 wx.downloadFile({
            url: item.avatarUrl,
                success(data){
                    console.log(data.tempFilePath);
                    self.setData({
                        markers : [ ...self.data.markers,
                            {
                                iconPath: data.tempFilePath,
                                id: 0,
                                latitude: item.latitude,
                                longitude: item.longitude,
                                width: 50,
                                height: 50
                            }
                        ]
                    }); // console.log(self.data.markers);
                    //將頭像的臨時地址存儲給這個人
                    tempPathObj[self.data.userInfo.nickName] = data.tempFilePath;
               } })
        }
    });
});

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM