zoukankan      html  css  js  c++  java
  • 前端笔记之微信小程序(四)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="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="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,
                             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,
                                     50,
                                    height: 50
                                }
                            ]
                        });
                        // console.log(self.data.markers);
                        //将头像的临时地址存储给这个人
                        tempPathObj[self.data.userInfo.nickName] = data.tempFilePath;
                    }
                })
            }
        });
    });

  • 相关阅读:
    httpmime-session 会话保持
    5.5 准备创建bean
    5.4 获取单例
    当有“Button1.Attributes.Add("onclick", "return confirm('你确定要保存修改吗?')");”时,验证控件失效的解决方法
    asp.net刷新本页面的六种方法总结
    return 、break和continue的区别和作用
    OnClientClick事件
    抽象接口
    C# 中的EventHandler实例详解-转
    GridView点击行触发SelectedIndexChanged事件
  • 原文地址:https://www.cnblogs.com/rope/p/10750781.html
Copyright © 2011-2022 走看看