socket 通常也称作“套接字”,用于描述 IP 地址和端口,是一个通信链的句柄。可以用来实现不同虚拟机或不同计算机之间的通信。网络上的两个程序通过一个双线的通信连接实现数据的交换,这个连接的一端称为一个 socket。
WebSocket 是基于 TCP 的一种新的网络协议,它实现了浏览器与服务器全双工通信 —— 允许服务器主动发信息给客户端。和 HTTP 的 Request 请求不同,在实现 websocket 连接的过程中,浏览器需要发出 websocket 连接请求,然后服务器做出回应,这个过程也就是常说的“握手”。
在 websocket API 中,浏览器和服务器只需要做一个握手的动作,然后浏览器和服务器之间就形成了一条快速通道。
websocket 一般用在“客户端和服务器端交互紧密并且极度频繁”的场景下(比如:端对端的聊天和网络游戏)。打通两者之间的数据通路,而不用定时一次次地发起普通 http 请求(轮询)。
//启动一个socket代码(客户端)wx.connectSocket({ //连接一个socket url:'wss://example.qq.com', data:{}, header:{ 'content-type':'application/json' }, protocols:['protocol1'], method:'GET'})
复制代码
表面上看,和普通请求很像,但它的不凡之处就在于:该请求成功连接一个 socket 以后,将会保持这个连接的状态,而普通的 get/post 等请求则是随着 http 的断开而断开。
这时候,可以调用wx.onSocketOpen这个 API 监听 websocket 连接打开事件:
wx.onSocketOpen(function(res){ console.log('WebSocket连接已打开!');})
复制代码
当一个 socket 打开以后,最重要的内容则是通过该 socket 发送一个需要的信息——这需要用到 API:wx.sendSocketMessage;当然,这个“发送”必须在“打开”(的回调 success)之后(WePY 中是在 then 之后):
wx.onSocketOpen(function(res){ wx.sendSocketMessage({ data:msg })})
复制代码
(不过实际中并不这样写,在页面 Load 中 init“Open”,open 中取 receive,这个 send 反而是放在具体监听的事件中调用)
既然发送出去了,就得接受服务器端的消息(不然怎么“对话”啊~):在打开 socket 之后,可以调用wx.onSocketMessage API 来接收服务器的消息事件
wx.onSocketMessage(function(res){ console.log('收到服务器的消息:'+res.data)})
复制代码
而在消息的发送和接收过程中,因为某些原因出现一些错误是不可避免的——比如客户端设备无法打开 socket、或者网络掉线/延迟、或者服务端请求过多造成拥堵...这时就需要“手动”提示开发者或用户了:
wx.onSocketError(function(res){ console.log('websocket连接打开失败,请检查系统及网络!');})
复制代码
最后,我们完成了一个 socket 连接,用户却不用了,那就要及时断开 —— 一个服务器接收和承载连接数是有限的,及时地断开不需要的链接可以极大地减轻服务器的压力,减少资源的浪费:
wx.onSocketClose(function(res){ console.log('websocket连接已关闭!');})
复制代码
我们将上面的知识点总结一下:实战:简单的 socket 聊天室小程序后端:node.js
全局安装 websocket 用到的 npm 包。
安装完成后,在项目中新建一个 server.js 文件:
const WebsocketServer=require('ws').Server;let wbsocketServer=new WebsocketServer({ port:8081, autoAcceptConnections:true})
let clients=[]let connectNum=0//监听连接和消息wbsocketServer.on('connection',(ws)=>{ clients.push(ws); ++connectNum; console.log('连接的数量:'+connectNum);
ws.on('message',(message)=>{ let objMessage=JSON.parse(message); console.log(objMessage.data); //可以做一些处理或者转发其它客户端 }) //随机发送消息 setInterval(()=>{ if(connectNum!==0){ setTimeout(()=>{ //从连接池中获取最新连接 clients[clients.length-1].send(JSON.stringify({data:'来自服务器的消息'})) },Math.random()*1000*3) }else{ console.log('无客户端连接') } },10000) ws.on('close',()=>{ console.log('有连接断开'); //删除不需要的连接——一般是“最老的”一条数据 clients.pop(); --connectNum; })})
复制代码
完后就可以用nodemon server.js命令启动服务器。
小程序中开发时一定要勾选“未校验合法域名...”这一项
客户端开发——WePY
npm install wepy-cli -g
wepy init standard chat--创建了一个chat项目,完成基本配置后,进入该目录
npm i
--生成、监控小程序wepy build -watch
复制代码
在 app.wpy 文件的 config 配置中新增一个 chat 页面,并且开启 promisify,并且在 pages 文件夹下创建 chat.wpy 文件。修改如下所示:
//开启promisifyconstructor{ super(); this.use('requestfix'); this.use('promisify');}pages:[ 'pages/chat'],
复制代码
同一般的微信小程序的<block></block>,我们可以用一个数组存储对话,而使用<repeat></repeat>循环显示聊天内容。chat.wpy -> template
<template> <view class="page"> <view class="chats"> <repeat for="{{chats}}" item="item"> <view style="font-size:20rpx;color:#ababab">{{item.item}}</view> <view style="font-size:25rpx;padding-bottom:20rpx;">{{item.text}}</view> </repeat> </view> <view class="chatInput"> <input placeholder="请输入聊天内容" bindinput="userSay" /> </view> <button @tap="sendMessage" size="mini" class="btn">发送消息</button> </view></template>
复制代码
chat.wpy -> style
<style lang="less"> .page{ position:fixed; height:100vh; width:100vw; background:#e8e9d2; } .chats{ text-align:center; margin:10vh 10vh 10vw 10vw; height:65vh; width:80vw; background-color:aliceblue; overflow:auto; } .chatInput{ background:aliceblue; height:40rpx; font-size:20rpx; padding:10rpx; width:70vw; margin-left:15vw; border-radius:20rpx; margin-bottom:3vh; } .btn{ width:70vw; mnargin-left:15vw; }</style>
复制代码
chat.wpy -> js/script
<script> import wepy from 'wepy' //监听是否打开的状态量 let socketOpen=false export default class chat extends wepy.page{ //wepy中的固定格式 data={ say:'', chats:[ {time:'聊天开始',text:''} ] } methods={ //用户输入相关 userSay(e){ this.say=e.detail.value this.$apply() }, //发送对话 sendMessage(){ let time=new Date() this.chats=this.chats.concat([time:time.toLocaleTimeString(),text:'我说:'+this.say]) this.handleSendMessage() this.$apply() } } //启动一个socket startSocket(){ wepy.connectSocket({ url:'ws://127.0.0.1:8081' }) } wssInit(){ const that=this this.startSocket() //连接失败显示 wepy.onSocketError(function(res){ socketOpen=false console.log('websocket连接打开失败,请检查!',res) setTimeout(()=>{ that.startSocket() },2000) }) //监听连接成功 wepy.onSocketOpen(function(res){ socketOpen=true console.log('websocket连接已打开') //接收服务器的消息 that.receiveMessage() }) } receiveMessage(){ const that=this if(socketOpen){ console.log('读取socket服务器...') wepy.onSocketMessage(function(res){ let time=new Date() let resData=JSON.parse(res.data) if(resData.data){ that.chats=that.chats.concat([{time:time.toLocaleTimeString(),text:'服务器说:'+resData.data}]) that.$apply() } }) }else{ //未打开状态需要延时重新连接 console.log('服务器没有连接上') setTimeout(()=>{ that.receiveMessage() },2000) } } //发送消息 handleSendMessage(){ const that=this wepy.sendSocketMessage({ data:JSON.stringify({data:that.say}) }) } events={}
onLoad(){ const that=this setTimeout(()=>{ that.wssInit() },1000) } }</script>
复制代码
评论