之前做web頁面的時候,前後端用的@microsoft/signalr庫做長連接,現在微信小程序不支持signalr庫的直接使用,uniapp微信小程序的websocket長連接只能用uni.connectSocket,所以我們要用uni.connectSocket實現與signalr庫的長連接。
頁面結構
<view>
<button @click="onLink">啓動連接</button>
<button @click="onSend">發送消息</button>
<button @click="stopLink">停止</button>
</view>
先封裝一個uni.connectSocket類,該類可以直接複製到你的項目中使用,例如新建一個socket.js文件
class WebSocketClient {
constructor(url, reconnectTimer, token) {
this.url = url; // wss url
this.SocketTask = null; // uni.connectSocket實例,SocketTask
this.timer = null; // 定時器-斷線重連
this.reconnectTimer = reconnectTimer; // 重連時長
this.token = token; // token
this.reconnectCount = 0; // 重連次數
this.maxReconnectCount = 5; // 最大重連數
// 響應回調
this.callbacks = {
onMessage: null,
onOpen: null,
onClose: null,
onError: null
};
// 建立連接
this.connect();
}
// 連接websocket
connect() {
this.SocketTask = uni.connectSocket({
url: this.url + '?access_token=' + this.token,
method: 'GET',
success: (e) => {
console.log('連接成功', e);
},
fail: (e) => {
console.log('連接失敗', e);
}
})
// 連接打開
this.SocketTask.onOpen((e) => {
// 重要!!!
// 連接打開的時候發送一條消息
// 告訴服務器使用 JSON 的形式進行消息的序列化
this.SocketTask.send({
data: `{"protocol":"json", "version":1}${String.fromCharCode(0x1e)}`
});
this.callbacks.onOpen?.(e);
});
// 連接響應
this.SocketTask.onMessage((e) => {
this.callbacks.onMessage?.(e);
});
// 連接關閉
this.SocketTask.onClose((e) => {
this.callbacks.onClose?.(e);
});
// 連接錯誤
this.SocketTask.onError((e) => {
this.callbacks.onError?.(e);
this.reconnect(); // 連接錯誤時開啓重連
});
}
// 註冊傳入的回調函數,用於頁面響應
on(event, callback) {
if (this.callbacks.hasOwnProperty(event)) {
this.callbacks[event] = callback;
}
return this;
}
// 斷線重連
reconnect() {
if (this.reconnectCount < this.maxReconnectCount) {
this.reconnectCount++;
this.reconnectTimer = setTimeout(() => {
this.connect();
}, 2000 * this.reconnectCount);
}
}
// 發送消息
send(data) {
this.SocketTask.send({
data,
success: (e) => {
console.log('發送成功', e);
},
fail: (e) => {
console.log('發送失敗', e);
}
});
}
// 關閉連接
close() {
this.SocketTask.close({
code: 1000,
success: (e) => {
console.log('關閉成功', e);
},
fail: (e) => {
console.log('關閉失敗', e);
}
});
}
}
export default WebSocketClient;
引入封裝的uni.connectSocket,頁面中使用
// 連接socket
let SocketTask = null;
const onLink = () => {
indexs = 0;
let url = `wss://xxx.xxxxxxxx.com/api/ChatHub`;
let token = `Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxx`;
// 連接websocket並註冊響應事件
SocketTask = new WebSocketClient(url, 5000, token).on('onOpen', onOpen).on('onMessage', onMessage).on('onClose', onClose).on('onError', onError);
};
const onOpen = (e) => {
console.log('連接打開', e);
};
const onMessage = (e) => {
console.log('連接響應',e)
};
const onClose = (e) => {
console.log('連接關閉', e);
};
const onError = (e) => {
console.log('連接錯誤', e);
};
// 發送消息
const onSend = () => {
// 你要發送的內容
let data = [{ text: '今天天氣晴朗', isFinish: false }];
// 與後端匹配的方法名稱
let methodName = 'SendModelReplyText';
// type: 1 是固定值
// 發送消息,格式是固定的,否則後端signalr不識別
SocketTask.send(`${JSON.stringify({ arguments: data, target: methodName, type: 1 })}${String.fromCharCode(0x1e)}`);
};
發送消息後,websocket會響應,我們再頁面上已經寫了響應函數,這裏要稍微改動下
const onMessage = (e) => {
if (!e.data) return;
// 解析規則是固定的
let msg = e.data.replace(String.fromCharCode(0x1e), ''); //替換消息結束符
let msgData = JSON.parse(msg);
console.log('響應', msgData)
};
// 停止連接
const stopLink = () => {
// 你要發送的內容
let data = [{text: '',isFinish: true}];
// 與後端匹配的方法名稱
let methodName = 'SendModelReplyText';
// type: 1 是固定值
// 發送消息,格式是固定的,否則後端signalr不識別
SocketTask.send(`${JSON.stringify({ arguments: data, target: methodName, type: 1 })}${String.fromCharCode(0x1e)}`);
// 這裏不能直接SocketTask.close();
// 因為連接是根據websocket來的,我們發送isFinish: true給後端後,應該後websokcet返回真實可斷開標記
// 然後在onMessage響應函數裏判斷isFinish: true時再SocketTask.close();
};
uni.connectSocket與signalr的長連接主要在於發送前端的內容後端不識別,如果不做轉碼發送,後端很大慨率會返回“中斷握手請求” ,下面幾個點是關鍵:
1、建立連接後發送一條消息,告訴服務器使用 JSON 的形式進行消息的序列化
2、send消息時要轉碼發送,結構必須是signalr可識別的
3、onMessage接收響應消息時要解碼,否則前端用不了
4、關閉連接也要轉發發送,並且不能直接關閉,需要在響應中判斷是否可真實關閉再斷開連接。