千辛万苦一整天多发现一个隐蔽bug: uni.connectSocket 返回后,socket 很可能并未 open,导致这期间的 sendObject({skevent:'SOCKET_OWNER'}) 会静悄悄失败,导致后台的 socket.skid/usid 并未被正确记录。通过设置 messageQueue 来解决了这个问题。

This commit is contained in:
Luk 2024-09-18 10:23:16 +08:00
parent b92876fb09
commit 61e441b596

View File

@ -5,25 +5,35 @@ const my = {
listeners: {}, listeners: {},
heartbeatInterval: 20000, heartbeatInterval: 20000,
reconnectInterval: 5000, reconnectInterval: 5000,
messageQueue: []
} }
// uni.onSocket* 和 sendSocketMessage/closeSocket 方法,是全局唯一的 api不需要保存 socket 对象。
// SocketTask 的 on* 和 send/close 方法,是针对具体 socket 的,适用于多个连接(但不一定支持)或者需要对唯一连接做更精细控制。
export default { export default {
isAlive () { isAlive () {
return my.socket && my.socket.readyState === my.socket.OPEN return my.socket && my.socket.readyState === my.socket.OPEN
}, },
initSocket ({ url, relogin = false, stateManager = {}, heartbeat = false, heartbeatInterval, reconnectInterval } = {}) {
initSocket ({ url, relogin = false, stateManager = {} } = {}) {
if (!my.socket || (my.socket.readyState !== my.socket.OPEN && typeof url === 'string')) { if (!my.socket || (my.socket.readyState !== my.socket.OPEN && typeof url === 'string')) {
console.log({ _at: new Date().toJSON(), about: `WebSocket is connecting to ${url}...` }) console.log({ _at: new Date().toJSON(), about: `WebSocket is connecting to ${url}...` })
my.socket = uni.connectSocket({ my.socket = uni.connectSocket({
url: url.replace(/^http/, 'ws'), url: url.replace(/^http/, 'ws'),
complete: () => {}, complete: () => {},
}) })
my.socket.onOpen((res) => { my.socket.onOpen((res) => {
console.log({ _at: new Date().toJSON(), about: 'WebSocket onOpen: ', res }) console.log({ _at: new Date().toJSON(), about: 'WebSocket onOpen: ', res })
stateManager.socketAlive = true stateManager.socketAlive = true
if (my.messageQueue.length) {
console.log('WebSocket_onOpen: sending messageQueue')
my.messageQueue.forEach(dataObj => {
this.sendObject(dataObj)
})
my.messageQueue = []
}
clearInterval(my.reconnecting) clearInterval(my.reconnecting)
delete my.reconnecting delete my.reconnecting
@ -33,16 +43,17 @@ export default {
my.socket.send({ data: JSON.stringify({ skevent: 'SOCKET_OWNER_RECONNECT', _passtoken: uni.getStorageSync('_passtoken') }) }) my.socket.send({ data: JSON.stringify({ skevent: 'SOCKET_OWNER_RECONNECT', _passtoken: uni.getStorageSync('_passtoken') }) })
} }
my.heartbeating = setInterval(() => { if (heartbeat) {
if (my.socket && my.socket.readyState === my.socket.OPEN) { my.heartbeating = setInterval(() => {
my.socket.send({ data: JSON.stringify({ skevent: 'PING' }) }) if (my.socket && my.socket.readyState === my.socket.OPEN) {
} else { my.socket.send({ data: JSON.stringify({ skevent: 'PING' }) })
clearInterval(my.heartbeating) } else {
delete my.heartbeating clearInterval(my.heartbeating)
} delete my.heartbeating
}, my.heartbeatInterval) // 定期发送心跳,避免被关闭 }
}, heartbeatInterval || my.heartbeatInterval) // 定期发送心跳,避免被关闭
}
}) })
my.socket.onClose((res) => { my.socket.onClose((res) => {
console.log({ _at: new Date().toJSON(), about: 'Websocket onClose: ', res }) console.log({ _at: new Date().toJSON(), about: 'Websocket onClose: ', res })
stateManager.socketAlive = false stateManager.socketAlive = false
@ -50,14 +61,12 @@ export default {
my.reconnecting = setInterval(() => { my.reconnecting = setInterval(() => {
console.log({ _at: new Date().toJSON(), about: 'Websocket reconnecting...' }) console.log({ _at: new Date().toJSON(), about: 'Websocket reconnecting...' })
this.initSocket({ url, relogin: true, stateManager }) this.initSocket({ url, relogin: true, stateManager })
}, my.reconnectInterval) // 定时尝试重连 }, reconnectInterval || my.reconnectInterval) // 定时尝试重连
}) })
my.socket.onError((err) => { my.socket.onError((err) => {
console.log({ _at: new Date().toJSON(), about: 'Websocket onError: ', err }) console.log({ _at: new Date().toJSON(), about: 'Websocket onError: ', err })
stateManager.socketAlive = false stateManager.socketAlive = false
}) })
my.socket.onMessage(({ data }) => { my.socket.onMessage(({ data }) => {
// 在这里统一分发消息(用户端通常不需要返回结果给服务器,因此不用 rpc 模式,而用 event 模式。 // 在这里统一分发消息(用户端通常不需要返回结果给服务器,因此不用 rpc 模式,而用 event 模式。
try { try {
@ -75,7 +84,6 @@ export default {
} }
return this return this
}, },
closeSocket () { closeSocket () {
if (my.socket) my.socket.close() if (my.socket) my.socket.close()
setTimeout(() => { setTimeout(() => {
@ -83,13 +91,11 @@ export default {
delete my.reconnecting delete my.reconnecting
}, 2000) }, 2000)
}, },
initListener (skevent, listener) { initListener (skevent, listener) {
// 当该 skevent 尚不具有任何 listener 时,添加本 listener // 当该 skevent 尚不具有任何 listener 时,添加本 listener
my.listeners[skevent]?.length > 0 || this.addListener(skevent, listener) my.listeners[skevent]?.length > 0 || this.addListener(skevent, listener)
return this return this
}, },
addListener (skevent, listener) { addListener (skevent, listener) {
if (Array.isArray(my.listeners[skevent]) && typeof listener === 'function') { if (Array.isArray(my.listeners[skevent]) && typeof listener === 'function') {
my.listeners[skevent].push(listener) my.listeners[skevent].push(listener)
@ -98,19 +104,20 @@ export default {
} }
return this return this
}, },
countListener (skevent) { countListener (skevent) {
if (Array.isArray(my.listeners[skevent])) { if (Array.isArray(my.listeners[skevent])) {
return my.listeners[skevent].length return my.listeners[skevent].length
} }
return 0 return 0
}, },
sendObject (dataObj) { sendObject (dataObj) {
console.log('sendObject! socket.readyState =', my.socket.readyState)
if (my.socket && my.socket.readyState === my.socket.OPEN) { if (my.socket && my.socket.readyState === my.socket.OPEN) {
my.socket.send({ my.socket.send({
data: typeof dataObj !== 'string' ? JSON.stringify(dataObj) : dataObj, data: typeof dataObj !== 'string' ? JSON.stringify(dataObj) : dataObj,
}) })
} else {
my.messageQueue.push(dataObj)
} }
}, },
} }