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 文件。修改如下所示:
//开启promisify
constructor{
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>
复制代码
评论