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
npm install -g ws
全局安裝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>
wepy是微信小程序中的一個框架,它集合了vue的優勢。
故而上述js代碼中操作data數據時直接this.xxx而不是this.data.xxx