欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

前端笔记之微信小程序(四)WebSocket&Socket.io&摇一摇案例&地图|地理位置

程序员文章站 2022-12-22 08:22:30
一、WebSocket概述 http://www.ruanyifeng.com/blog/2017/05/websocket.html Workerman一款开源高性能异步PHP socket即时通讯框架https://workerman.net HTTP是无连接的:有请求才会有响应,如果没有请求, ......

一、websocket概述

 

workerman一款开源高性能异步php socket即时通讯框架

 

http是无连接的:有请求才会有响应,如果没有请求,服务器想主动推送信息给浏览器是不可能的。

前端笔记之微信小程序(四)WebSocket&Socket.io&摇一摇案例&地图|地理位置

 

比如图文直播、聊天室原理:长轮询。

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

间隔一定的时间,主动向服务器发起请求,询问是否有新消息。

 

websocket是一种网络通信协议,html5中的新协议。需要服务器和浏览器共同支持,实现全双工通信。

 前端笔记之微信小程序(四)WebSocket&Socket.io&摇一摇案例&地图|地理位置

服务器:php5.6java1.7nodejs 6以上。

浏览器:android 6.0及以上版本。

websocket html5 开始提供的一种在单个 tcp 连接上进行全双工通讯的协议。

 

websocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 websocket api 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

websocket api 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。


二、socket.io

socket.io是一个跨浏览器支持websocket的实时通讯的jsnodejs中实现socket非常好用的包。

 

api

npm install --save socket.io

默认有一个自动路由的js文件

 前端笔记之微信小程序(四)WebSocket&Socket.io&摇一摇案例&地图|地理位置

 

前端代码(从官网抄的模板):

<!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

 

现在两个端已经实时通讯连接上了:

 前端笔记之微信小程序(四)WebSocket&Socket.io&摇一摇案例&地图|地理位置

前端笔记之微信小程序(四)WebSocket&Socket.io&摇一摇案例&地图|地理位置

 

消息收发的响应:

前端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

 前端笔记之微信小程序(四)WebSocket&Socket.io&摇一摇案例&地图|地理位置

 

前端开启:

page({
    onload(){
        wx.connectsocket({
            url: 'ws://127.0.0.1:8080',
        })
    }
})

 

后端需要安装一个依赖:

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("有人链接了");
});

 

前端笔记之微信小程序(四)WebSocket&Socket.io&摇一摇案例&地图|地理位置

<!--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三个轴的方向的加速度计算出摇动手机时,手机摇动方向的加速度。

 

 前端笔记之微信小程序(四)WebSocket&Socket.io&摇一摇案例&地图|地理位置

前端笔记之微信小程序(四)WebSocket&Socket.io&摇一摇案例&地图|地理位置

 

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
        })
    } 
});

五、地图和地理位置

腾讯地理位置服务

地图自己是不能定位的,需要获取地理位置定位,而且地图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

 前端笔记之微信小程序(四)WebSocket&Socket.io&摇一摇案例&地图|地理位置

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;
                }
            })
        }
    });
});