websocket是一种协议,用于提供低延迟,全双工,和长期运行的连接。
全双工是指通信的两个参与方可以同时发送和接收数据,不需要等待对方的响应或传输完成,通过建立全双工的长时间连接,客户端和服务器之间就能实现高效、实时性更强的通信。
用的轮询长轮询,缺点会产生大量的请求和响应,造成不必要的网络开销和延迟
webSocket应用场景:低延迟实时连接的应用
双向实时通信,降低延迟,可以减少请求和响应的开销,因为它的连接只需要建立一次,
它允许服务器和客户端之间通过单个TCP连接进行双工通信并且进行实时的数据交换
客户端发送一个HTTP常规get请求给服务器,请求头中带上Upgrade告诉服务器升级为WebSocket协议。
服务器接受请求,并返回一个表示成功的响应。
客户端和服务器之间建立WebSocket连接,可以随时进行双向通信。
// WebSocket构造函数,创建WebSocket对象
let ws = new WebSocket('ws://localhost:8888')
// 连接成功后的回调函数
ws.onopen = function (params) {
console.log('客户端连接成功')
// 向服务器发送消息
ws.send('hello')
};
// 从服务器接受到信息时的回调函数
ws.onmessage = function (e) {
console.log('收到服务器响应', e.data)
};
// 连接关闭后的回调函数
ws.onclose = function(evt) {
console.log("关闭客户端连接");
};
// 连接失败后的回调函数
ws.onerror = function (evt) {
console.log("连接失败了");
};
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,这样服务端会抛异常。
window.onbeforeunload = function() {
ws.close();
}
1、为什么使用心跳机制:为了保持WebSoket稳定的长连接,在建立连接之后,连接状态可能会因各种状态出现异常,比如因为长时间没有数据传输而被切断,服务端和客户端之间通过心跳包来保持连接状态。
2、什么是心跳包:心跳包是一种特殊的数据包,它不包含任何实际数据,只是用来维持连接状态的
怎么使用:通常情况下心跳包由客户端和服务端定期发送一个空的数据帧,以确保双方的链接仍然有效,避免链接因为长时间没有数据传输而被中断,如果长时间没有收到对方的心跳包就可以认为连接已经断开需要重新建立连接。
不提供加密功能,不安全,可以用SSL协议对来webSoket进行加密,确保数据安全,也可以在服务端限制权限设置白名单或者黑名单只允许特定地址或者域名的客户端进行链接。
消耗服务器资源
//创建utils/webSocket文件
class Socket {
ws = null
wsUrl = ''
status = ''
reconnectTimes = 0
needReconnect = true
/**
*
* @param {Object} config
* @param {String} config.url ws链接
* @param {Boolean} config.needReconnect 是否需要重连
* @param {Number} config.reconnectTimes 重连次数
*/
constructor(config = {}) {
const {url, reconnectTimes, needReconnect} = config
// console.log('config', config);
this.wsUrl = url
this.reconnectTimes = reconnectTimes || 0
this.needReconnect = needReconnect
this.init()
}
init() {
if ('WebSocket' in window) {
// 实例化
this.ws = new WebSocket(this.wsUrl)
// 监听事件
this.ws.onopen = () => this._onOpenFn()
// 监听错误信息
this.ws.onerror = err => this._onErrorFn(err)
// 监听消息
this.ws.onmessage = msg => this._onMessageFn(msg)
this.ws.onclose = () => this._onCloseFn()
} else {
console.log('你的浏览器不支持 WebSocket')
}
}
_onOpenFn(){
this.status = 'open'
this.onopen && this.onopen()
}
_onErrorFn (err) {
this.status = 'error'
this.onerror && this.onerror(err)
if(this.needReconnect){
this.reconnect()
}else {
this._connnetFailed(err)
}
}
_onMessageFn (msg) {
const obj = JSON.parse(msg.data)
console.log('onmessage', obj);
this.onmessage && this.onmessage(obj)
}
_onCloseFn() {
this.status = 'closed'
this.onclose && this.onclose()
}
// 连接失败
_connnetFailed() {
console.log('retry failed');
this.connnetFailed && this.connnetFailed()
}
reconnect() {
if(this.reconnect.lock) return
if(this.reconnectTimes && this.reconnect.times >= this.reconnectTimes) {
this._connnetFailed()
return
}
this.reconnect.lock = true
this.reconnect.times = this.reconnect.times || 0
setTimeout(() => {
if(this.status!== 'open'){
this.init()
this.reconnect.lock = false
this.reconnect.times ++
}
}, 500)
}
close () {
this.ws.close()
}
send (data) {
const dataString = JSON.stringify(data)
this.ws.send(dataString)
}
}
export default Socket
//调用
import ws from 'utils/webSocket'
import { generateUUID } from 'utils'
import { busListenOnce, busTrigger } from '@/base/eventBus'
/** export const busListenOnce = (eventName = '', callback = () => { }) => {
if (!eventName) return
return getBus().$once(eventName, callback)
}
export const busTrigger = (eventName = '', opts = {}) => {
if (!eventName) return
return getBus().$emit(eventName, opts)
}**/
import { connectEvents } from './handleEvents'
let wsClient = null
let uri = ''
// 拉起客户端
async function connectWithDss() {
// 连接server获取port
const port = await connectServer()
uri = `ws://localhost:${port}`
// 连接client
await connectClient(uri)
}
/**
*
* 1. 连接Server中间件
* 2. sendMsg: getPort
* 3. 接收到端口W
* 3. 使用端口连接 client
*/
function connectServer() {
return new Promise((resolve, reject) => {
const url = `ws://localhost:13000`
const wsServer = new ws({ url, needReconnect: false, reconnectTimes: 2 })
wsServer.onopen = () => {
store.commit('sip/SET_IS_INSTALLED', true)
getPort(wsServer)
}
wsServer.onmessage = (msg) => {
const port = msg.port
wsServer.close()
resolve(port)
}
wsServer.onerror = (err) => {
console.log(err)
// reject(err)
}
wsServer.connnetFailed = (err) => {
store.commit('sip/SET_IS_INSTALLED', false)
busTrigger(connectEvents.SERVER_CONNECTED_FAILED, err)
}
})
}
function connectClient(url) {
return new Promise((resolve, reject) => {
wsClient = new ws({ url })
wsClient.onopen = () => {
busTrigger(connectEvents.CLIENT_CONNECTED_SUCCEED)
resolve()
}
wsClient.onmessage = (msg) => {
// console.log('sipCient msg received :>> ', msg)
if (!msg.sequence) {
// 客户端插件主动发消息
busTrigger(msg.method, msg)
} else {
// 客户端响应消息
busTrigger(msg.sequence, msg)
}
}
})
// const url = `ws://${location}:${port}`
// wsClient.oncloseCb = () => {
// wsClient.reconnect()
// }
}
function closeClient() {
console.log('closed');
wsClient && wsClient.close()
}
function getPort(ws) {
ws.send({
method: 'getPort',
uuid: generateUUID(), //用于标识客户端组件,由前端生成
sequence: '' //同步码
})
}
let retryTimes = 0
/**
* 登录
* @param {Object} params 话机登录参数
* { "1000","sN66vV0m"},
* { "1001","bgiT60y0"},
* { "1002","_jdqs0Vr@NGuCMVT"},
* { "1003","8nRYK@?hRmj?WSSL"},
* { "1004","5*Rx*JwXuN@?-~*~"},
* { "1005","nF?xbw2?_?CP@^^-"},
* { "1006","~p_~?RK0wsaQqYNO"},
*/
function login(params) {
if (!retryTimes) {
retryTimes = 0
} else if (retryTimes > 5) {
return
}
return new Promise((resolve, reject) => {
const sequence = `login_${Date.now()}`
wsClient.send({
method: 'login',
param: params,
sequence //同步码
})
busListenOnce(sequence, (msg) => {
if (msg.errorCode === '0') {
// Message.success('签入成功')
retryTimes = 0
resolve(msg)
} else {
retryTimes++
login()
}
})
})
}
// 登出
function logout () {
return new Promise((resolve, reject) => {
const sequence = `logout_${Date.now()}`
wsClient.send({
method: 'logout',
sequence
})
busListenOnce(sequence, (msg) => {
if (msg.errorCode === '0') {
console.log('签出成功')
// store.commit('sip/SET_IS_LOGIN', false)
resolve(msg)
} else {
reject(msg)
// login()
}
})
// listenCommon()
})
}
// 拨打电话
function makeCall({ phoneNumber }){
const sequence = `makeCall_${Date.now()}`
return new Promise((resolve, reject) => {
wsClient.send({
method: 'makeCall',
param: {
phoneNumber //电话号码
},
sequence //同步码
})
busListenOnce(sequence, (msg) => {
if (msg.errorCode === '0') {
// TODO :
store.commit('sip/SET_CALLING_STATE', 3)
resolve(msg)
} else {
reject(msg)
}
})
})
}
// 接听电话
function answerCall(){
const sequence = `answer_${Date.now()}`
return new Promise((resolve, reject) => {
wsClient.send({
method: 'answer',
param: {
callid: '' //目前只支持一路通话,不需要填写该参数
},
sequence //同步码
})
busListenOnce(sequence, (msg) => {
if (msg.errorCode === '0') {
store.commit('sip/SET_CALLING_STATE', 2)
resolve(msg)
} else {
reject(msg)
}
})
})
}
// 挂断电话
function hangUp () {
const sequence = `hangup_${Date.now()}`
return new Promise((resolve, reject) => {
wsClient.send({
method: 'hangup',
param: {
callid: '' //目前只支持一路通话,不需要填写该参数
},
sequence //同步码
})
busListenOnce(sequence, (msg) => {
if (msg.errorCode === '0') {
resolve(msg)
store.commit('sip/SET_CALLING_STATE', 0)
} else {
reject(msg)
}
})
})
}
// 保持通话
function setHold() {
wsClient.send({
method: 'setHold',
param: {
callid: '' //目前只支持一路通话,不需要填写该参数
},
sequence: 'setHold' //同步码
})
}
// 恢复通话
function reinvite () {
wsClient.send({
method: 'reinvite',
param: {
callid: '' //目前只支持一路通话,不需要填写该参数
},
sequence: 'reinvite' //同步码
})
}
function getWsClient() {
return wsClient
}
export { connectWithDss, connectServer, closeClient, login, logout, makeCall, answerCall, hangUp, setHold, reinvite, getWsClient}