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

 

Workerman一款开源高性能异步PHP socket即时通讯框架https://workerman.net

 

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

 

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

  1. setInterval(function(){
  2. $.get()
  3. },1000)

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

 

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

 

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

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

WebSocket HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

 

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

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


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

 

API

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

https://socket.io/

  1. npm install --save socket.io

默认有一个自动路由的js文件http://127.0.0.1:3000/socket.io/socket.io.js

 

 

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

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <title>Socket.IO chat</title>
  5. <style>
  6. * { margin: 0; padding: 0; box-sizing: border-box; }
  7. body { font: 13px Helvetica, Arial; }
  8. form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
  9. form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
  10. form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
  11. #messages { list-style-type: none; margin: 0; padding: 0; }
  12. #messages li { padding: 5px 10px; }
  13. #messages li:nth-child(odd) { background: #eee; }
  14. </style>
  15. </head>
  16. <body>
  17. <ul id="messages"></ul>
  18. <form action="">
  19. <input id="m" autocomplete="off" />
  20. <button>Send</button>
  21. </form>
  22. <script type="text/javascript" src="/socket.io/socket.io.js"></script>
  23. <script type="text/javascript">
  24. var socket = io();
  25. </script>
  26. </body>
  27. </html>

 

后端:

  1. var express = require(\'express\');
  2. var app = express();
  3. var http = require(\'http\').Server(app);
  4. var io = require(\'socket.io\')(http);
  5. app.get(\'/\', function(req, res){
  6. res.sendFile(__dirname + \'/index.html\');
  7. });
  8. //监听客户端,有用户连接的时候触发(建立前后端连接)
  9. io.on(\'connection\', function(socket){
  10. console.log(\'有个用户连接了\');
  11. });
  12. http.listen(3000);
  1. node app.js

 

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

 

 

消息收发的响应:

前端emit发:

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

 

服务端on收:

  1. io.on(\'connection\', function(socket){
  2. console.log(\'有个用户连接了\');
  3. socket.on("info", function(data){
  4. console.log(data);
  5. });
  6. });

接下来的事情:

实现聊天室功能:如果有某个客户端用户将消息发给了服务端,服务端要发给所有已经连接的客户端,这里就涉及到广播,广播就是给所有已经连接服务端的socket对象进行集体的消息发送。

 

完整的聊天室前端:

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <title>Socket.IO chat</title>
  5. <style>
  6. ...
  7. </style>
  8. </head>
  9. <body>
  10. <ul id="messages"></ul>
  11. <form action="">
  12. <input id="m" autocomplete="off" />
  13. <button>Send</button>
  14. </form>
  15. <script type="text/javascript" src="/socket.io/socket.io.js"></script>
  16. <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
  17. <script type="text/javascript">
  18. var socket = io();
  19. //点击按钮发出数据
  20. $("button").click(function(){
  21. socket.emit("info" , {
  22. content : $("#m").val()
  23. });
  24. $("#m").val("");
  25. return false; //阻止浏览器默认请求行为,禁止刷新页面
  26. });
  27. //客户端收到服务端的msg广播消息的时候触发的函数
  28. socket.on("msg", function(data){
  29. $("<li>" + data.content + "</li>").prependTo("ul");
  30. });
  31. </script>
  32. </body>
  33. </html>

 

后端:

  1. io.on(\'connection\', function(socket){
  2. console.log(\'有个用户连接了\');
  3. //服务端收到了info的消息
  4. socket.on("info" , function(data){
  5. console.log(data.content);
  6. //立即广播通知所有已连接的客户端
  7. io.emit(\'msg\', data);
  8. });
  9. });
  10. http.listen(3000);

小程序的上线版本,必须是https协议会wss协议(即websocket的安全版本)

 

如果结合微信小程序使用,Nodejs不能使用socket.io,因为socket.io在前端需要用script引入一个js文件,但是小程序不支持这样引入。但是没有关系,因为小程序自带websocketAPI

 

 

前端开启:

  1. Page({
  2. onLoad(){
  3. wx.connectSocket({
  4. url: \'ws://127.0.0.1:8080\',
  5. })
  6. }
  7. })

示例代码

 

后端需要安装一个依赖:

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

  1. npm install --save ws

后端app.js  

  1. const WebSocket = require(\'ws\');
  2. const wss = new WebSocket.Server({ port: 8080 });
  3. wss.on(\'connection\', function connection(ws){
  4. console.log("有人链接了");
  5. });

示例代码

 

  1. <!--index.wxml-->
  2. <view class="container">
  3. <view class="t">{{a}}</view>
  4. <button bindtap="sendmsg">按我</button>
  5. </view>

  1. //index.js
  2. Page({
  3. data: {
  4. a: 0
  5. },
  6. onLoad(){
  7. //前端发起websocket连接
  8. wx.connectSocket({
  9. // 可以在WiFi环境下的IP地址测试
  10. // url: \'ws://192.168.0.150:8080\',
  11. url: \'ws://127.0.0.1:8080\'
  12. })
  13. //监听WebSocket接受到服务器的广播消息通知事件
  14. wx.onSocketMessage((res)=>{
  15. console.log(res.data)
  16. this.setData({
  17. a:res.data
  18. })
  19. })
  20. },
  21. //点击按钮发送消息给服务端
  22. send(){
  23. wx.sendSocketMessage({
  24. data: "你好!",
  25. })
  26. }
  27. })

示例代码

 

后端app.js

Nodejsws这个库没有广播功能,必须让开发者将socket对象存为数组,要广播的时候,遍历数组中每个项,依次给他们发送信息即可。

  1. const WebSocket = require(\'ws\');
  2. //创建连接和监听端口
  3. const wss = new WebSocket.Server({port:8080});
  4. var ws_arr = []; //存储所有已经连接的人的ws对象
  5. var a = 0;
  6. //响应客户端的连接
  7. wss.on(\'connection\', function(ws){
  8. console.log("有人连接了");
  9. ws_arr.push(ws); //将当前进来的人存储到数组
  10.  
  11. //监听客户端发送的消息
  12. ws.on("message", function(message){
  13. console.log("服务端收到了消息:" + message)
  14. a++;
  15. //遍历所有人,广播通知所有客户端,把消息传送给他们
  16. ws_arr.forEach(item=>{
  17. item.send(a);
  18. })
  19. })
  20. })

示例代码


微信没有提供摇一摇API,必须使用加速,自己写代码感应xyz的变化。

加速计的坐标轴如图,是个三维的坐标。我们需要通过xyz三个轴的方向的加速度计算出摇动手机时,手机摇动方向的加速度。

 

 

 

index.js

  1. page({
  2. data:{
  3. x:0,
  4. y:0,
  5. z:0
  6. },
  7. onLoad(){
  8. var lastX = 0;
  9. var lastY = 0;
  10. wx.onAccelerometerChange((res)=>{
  11. //如果当前的x或y减去上一次x或y的差 大于0.5,就设定为摇一摇成功
  12. if(Math.abs(res.x - lastX) > 0.5 || Math.abs(res.y - lastY) > 0.5){
  13. wx.showToast({
  14. title: "成功"
  15. });
  16. lastX = res.x;
  17. lastY = res.y;
  18. this.setData({
  19. x : res.x,
  20. y : res.y,
  21. z : res.z
  22. })
  23. }
  24. })
  25. }
  26. })

示例代码

后端app.js

  1. const WebSocket = require(\'ws\');
  2. const wss = new WebSocket.Server({ port: 8080 });
  3. //存储所有人的ws对象
  4. var ws_arr = [];
  5. //存储所有人的分数
  6. // var score_arr = ["nickName":"测试账户","avatarUrl":"x.jpg", "n":0];
  7. var score_arr = [];
  8. var a = 0;
  9. wss.on(\'connection\', function(ws){
  10. console.log("有人链接了");
  11. ws_arr.push(ws); //将每个进来的用户存储到数组
  12. ws.on(\'message\', function(message){
  13. console.log("服务器收到了推送:" + message);
  14. //变为JSON对象
  15. var messageObj = JSON.parse(message);
  16. //当摇一摇时,判断数组中有没有这个人,有就让这个人的n++
  17. var isHave = false;
  18. score_arr.forEach(item=>{
  19. if(item.nickName == messageObj.nickName){
  20. item.n ++;
  21. isHave = true;
  22. }
  23. });
  24. //如果没有就添加到数组中
  25. if(!isHave){
  26. score_arr.push({
  27. nickName : messageObj.nickName,
  28. avatarUrl: messageObj.avatarUrl,
  29. n : 0
  30. })
  31. }
  32. console.log({"score_arr" : score_arr})
  33. //广播发送给客户端(前端)
  34. ws_arr.forEach(item=>{
  35. item.send(JSON.stringify({
  36. "score_arr" : score_arr
  37. }));
  38. });
  39. });
  40. });

 

用户摇一摇案例:

  1. <!--index.wxml-->
  2. <view class="container">
  3. <view class="userinfo">
  4. <button open-type="getUserInfo" bindgetuserinfo="getUserInfo">获取头像昵称</button>
  5. </view>
  6. <view wx:for="{{arr}}">
  7. {{item.nickName}}
  8. <image style="width:90px;height:90px;" src="{{item.avatarUrl}}"></image>
  9. {{item.n}}
  10. </view>
  11. </view>

 

index.js

  1. const app = getApp()
  2. Page({
  3. data: {
  4. userInfo: {},
  5. hasUserInfo: false,
  6. canIUse: wx.canIUse(\'button.open-type.getUserInfo\'),
  7. arr : []
  8. },
  9. onLoad: function () {
  10. if (app.globalData.userInfo) {
  11. this.setData({
  12. userInfo: app.globalData.userInfo,
  13. hasUserInfo: true
  14. })
  15. } else if (this.data.canIUse) {
  16. // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
  17. // 所以此处加入 callback 以防止这种情况
  18. app.userInfoReadyCallback = res => {
  19. this.setData({
  20. userInfo: res.userInfo,
  21. hasUserInfo: true
  22. })
  23. }
  24. } else {
  25. // 在没有 open-type=getUserInfo 版本的兼容处理
  26. wx.getUserInfo({
  27. success: res => {
  28. app.globalData.userInfo = res.userInfo
  29. this.setData({
  30. userInfo: res.userInfo,
  31. hasUserInfo: true
  32. })
  33. }
  34. })
  35. }
  36. //链接socket服务器(可以填WiFi的IP地址测试)
  37. wx.connectSocket({
  38. url: \'ws://127.0.0.1:8080\'
  39. });
  40. //当socket连接打开后,监听摇一摇:
  41. var self = this;
  42. var lastX = 0;
  43. wx.onSocketOpen(function(res){
  44. wx.onAccelerometerChange(function(res){
  45. //如果当前的x 减去上一次x的差 大于0.6,就设定为摇一摇成功
  46. if(Math.abs(res.x - lastX) > 0.6){
  47. wx.showToast({
  48. title: "摇一摇成功"
  49. });
  50. //告诉服务器我是谁
  51. wx.sendSocketMessage({
  52. data: JSON.stringify({
  53. "nickName": self.data.userInfo.nickName,
  54. "avatarUrl": self.data.userInfo.avatarUrl
  55. })
  56. })
  57. }
  58. lastX = res.x;
  59. });
  60. });
  61. //接收到服务器广播信息的时候做的事情
  62. wx.onSocketMessage(function(res){
  63. var obj = JSON.parse(res.data); //转对象
  64. var arr = obj.score_arr;
  65. //按照n值大小排序
  66. arr.sort((a,b)=>{
  67. return b.n - a.n
  68. })
  69. self.setData({arr}); //存储到本地data中的arr数组
  70. });
  71. },
  72. getUserInfo: function (e) {
  73. console.log(e)
  74. app.globalData.userInfo = e.detail.userInfo
  75. this.setData({
  76. userInfo: e.detail.userInfo,
  77. hasUserInfo: true
  78. })
  79. }
  80. });

腾讯地理位置服务https://lbs.qq.com/ 

地图自己是不能定位的,需要获取地理位置定位,而且地图API和地理位置API是分开。

  1. <!--index.wxml-->
  2. <view class="container">
  3. <view class="userinfo">
  4. <button open-type="getUserInfo" bindgetuserinfo="getUserInfo">获取头像昵称</button>
  5. </view>
  6. <map markers="{{markers}}" id="map" longitude="{{longitude}}" latitude="{{latitude}}"
      scale
    ="14" style="width:100%;height:300px;"></map>
  7. </view>
  1. Page({
  2. data: {
  3. markers : []
  4. },
  5. onLoad(){
  6. //页面加载进来要先定位
  7. var self = this;
  8. wx.getLocation({
  9. type: \'gcj02\',
  10. success: function(res){
  11. self.setData({
  12. latitude: res.latitude, //纬度
  13. longitude: res.longitude //经度
  14. });
  15. }
  16. });
  17. //连接socket服务器
  18. wx.connectSocket({
  19. url: \'ws://192.168.1.175:8080\'
  20. });
  21. //接收到服务器广播信息的时候做的事情
  22. wx.onSocketMessage(function (res) {
  23. var obj = JSON.parse(res.data);
  24. console.log(obj)
  25. }
  26. //微信没有提供当某人地理位置改变时候的on事件,所以setInterval()。
  27. //每3秒更新一次定位,然后发送给服务端,服务端再通知客户端
  28. clearInterval(timer);
  29. var timer = setInterval(function(){
  30. wx.getLocation({
  31. type: \'gcj02\',
  32. success: function(res){
  33. wx.sendSocketMessage({
  34. data: JSON.stringify({
  35. "nickName": self.data.userInfo.nickName,
  36. "avatarUrl": self.data.userInfo.avatarUrl,
  37. "latitude": res.latitude,
  38. "longitude": res.longitude
  39. })
  40. })
  41. }
  42. });
  43. },3000);
  44. }
  45. });

 

后端app.js

  1. const WebSocket = require(\'ws\');
  2. const wss = new WebSocket.Server({ port: 8080 });
  3. var ws_arr = []; //存储所有人的ws对象
  4. var location_arr = []; //存储所有人的地理位置
  5. wss.on(\'connection\', function (ws) {
  6. console.log("有人链接了");
  7. //放入数组
  8. ws_arr.push(ws);
  9. ws.on(\'message\', function (message) {
  10. console.log("服务器收到了推送" + message);
  11. //变为JSON对象
  12. var messageObj = JSON.parse(message);
  13. //判断数组中有没有我
  14. var isHave = false;
  15. location_arr.forEach(item=>{
  16. if(item.nickName == messageObj.nickName){
  17. item.latitude = messageObj.latitude;
  18. item.longitude = messageObj.longitude;
  19. isHave = true;
  20. }
  21. });
  22. //如果没有
  23. if(!isHave){
  24. location_arr.push({
  25. nickName : messageObj.nickName,
  26. avatarUrl: messageObj.avatarUrl,
  27. latitude : messageObj.latitude,
  28. longitude: messageObj.longitude
  29. })
  30. }
  31. console.log({"location_arr" : location_arr})
  32. //广播通知客户端
  33. ws_arr.forEach(item=>{
  34. item.send(JSON.stringify({
  35. "location_arr" : location_arr
  36. }));
  37. });
  38. });
  39. });

临时设置大头针markers

 

  1. var tempPathObj = {}; //存储这个人的昵称,根据昵称获取头像,如这个对象没有这个人就要下载头像
  2. //接收到服务器广播信息的时候做的事情
  3. wx.onSocketMessage(function(res){
  4. var obj = JSON.parse(res.data);
  5. self.setData({
  6. markers : [] //清空
  7. });
  8. //iconPath不支持网络地址,要通过wx.download()接口下载得到临时地址
  9. obj.location_arr.forEach(item=>{
  10. //根据昵称,判断这个对象中有没有这个人,如果有直接用
  11. if(tempPathObj.hasOwnProperty(self.data.userInfo.nickName)){
  12.     self.setData({
  13. markers: [
  14. ...self.data.markers,
  15. { //如果对象中有这个人,就直接用这个人的头像
  16. iconPath: tempPathObj[self.data.userInfo.nickName],
  17. id: 0,
  18. latitude: item.latitude,
  19. longitude: item.longitude,
  20. width: 50,
  21. height: 50
  22. }
  23. ]
  24. });
  25. } else {
  26. //如果没有就下载头像,并且将这个人存起来,以后可以直接用
  27. wx.downloadFile({
  28. url: item.avatarUrl,
  29. success(data){
  30. console.log(data.tempFilePath);
  31. self.setData({
  32. markers : [
  33. ...self.data.markers,
  34. {
  35. iconPath: data.tempFilePath,
  36. id: 0,
  37. latitude: item.latitude,
  38. longitude: item.longitude,
  39. width: 50,
  40. height: 50
  41. }
  42. ]
  43. });
  44. // console.log(self.data.markers);
  45. //将头像的临时地址存储给这个人
  46. tempPathObj[self.data.userInfo.nickName] = data.tempFilePath;
  47. }
  48. })
  49. }
  50. });
  51. });

 

版权声明:本文为rope原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/rope/p/10750781.html