WebSocket 学习记录

什么是 WebSocket

WebSocketHTML5开始提供的一种在单个TCP连接上进行双向通讯的协议。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务器主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

WebSocket的特点

  • 建立再TCP协议之上,一次连接成功便保持持久性的连接,服务器端的实现比较容易
  • 与HTTP协议有着良好的兼容性。默认端口上80/443,并且握手阶段采用HTTP协议,因此握手时不容易屏蔽,能通过各种HTTP代理服务器。
  • 协议标识符上ws/wss,普通80端口:ws://10.219.245.1:5560,加密443 端口:wss://10.219.245.1:5560
  • 没有跨域问题,不同于HTTP,WebSocket没有同源限制,客户端可以与任意服务器通信
  • 数据格式比较轻巧,新能开销小,通信高效
  • 可以发送文本,也可以发送二进制数据

客户端API

WebSocket 构造函数

// WebSocket对象作为一个构造函数,用于新建WebSocket实例。创建实例,客户端就会与服务器进行连接。
var ws = new WebSocket("ws://localhost:8080");

WebSocket.readyState 返回当前WebSocket的连接状态,只读

  • WebSocket.CONNECTING:值为0,表示正在连接中
  • WebSocket.OPEN:值为1,表示已经连接成功,可以通信了
  • WebSocket.CLOSING:值为2,表示连接正在关闭
  • WebSocket.CLOSED:值为3,表示连接已关闭,或者没有连接成功

WebSocket.onopen 连接成功后的回调函数

ws.onopen = function () {
    ws.send('在这里可以向服务器发送消息了');
};
// 如果需要指定多个回调函数,可以使用 addEventListener 方法进行绑定 
ws.addEventListener('open', function (event) {
    ws.send('Hello Server!');
});

WebSocket.onclose 连接关闭后的回调函数

ws.onclose = function (event) {
    // 返回一个事件监听器,这个监听器将在WebSocket连接的readyState变为CLOSED时被调用。
}

// 指定多个回调函数
ws.addEventListener('close', function(event) {
	// 遇到连接断开时可以在这来尝试重新连接
});	

WebSocket.onmessage 客户端接收到服务端数据时触发的回调函数

ws.onmessage = function(event) {
    var data = event.data; // 获取服务端的数据
    // 处理数据
}

// 指定多个回调函数
ws.addEventListener('message', function(event) {
    var data = event.data;
    // 处理数据
});

// 动态判断收到的数据类型, 可以是文本(blob),也可能是二进制数据(Arraybuffer)
ws.onmessage = function(event) { 
    if(typeof event.data == String) {
        console.log('文本类型数据')
    }
    if(event.data instanceof ArrayBuffer) {
        var buffer = event.data;
        console.log('二进制数据')
    }
}

WebSocket.onerror 通信发生错误时触发的回调函数

ws.onerror = function(event) {
    // 处理socket重连
}
ws.addEventListener("error", function() {
    // 处理socket重连 
});

WebSocket.send() 用于向服务器发送数据

ws.send('发送数据'); // 发送文本数据
// 发送 Blob 对象
var file = document.querySelector('input[type="file"]').files[0];
ws.send(file);
// 发送 ArrayBuffer 对象
var img = canvas_context.getImageData(0,0,400, 320);
var binary = new Uint8Array(img.data.length);
for(var i = 0; i < img.data.length; i++) {
    binary[i] = img.data[i];
}
ws.send(binary.buffer);

WebSocket.bufferedAmount 属性,表示还有多少字节的二进制数据没有发送出去。可以用来判断发送是否结束。

var data = new ArrayBuffer(1000000);
ws.send(data);
if(ws.bufferedAmount === 0) {
    // 发送完毕
}else{
    // 发送还没有结束
}

关于webSocket重连机制

在实际的运用中总是回遇到很多意外情况,包括网络不稳定、后端接口不稳定,或者长时间不与后台"联系"等因素,都有可能导致webSocket连接断开,所以想要一个稳定的webSocket连接,就必须有重连机制,webSocket在检测到连接断开时可以自动尝试重新连接,不影响数据的传输以及业务的开展。

var isReconnect = false; // 避免重复连接
var timer = null; // 避免重复连接,保证同一时刻只有一个重连
var ws = null;
// 重连方法
function reconnectWebSocket() {
    console.log("重新连接WebSocket...");
    if(isReconnect) { 
    	// 如果连接上了就直接退出重连
        return;
    }
    isReconnect = true;
    // 如果没有连接上会一直重连,设置延迟避免请求过多
    timer && clearTimeout(timer);
    timer = setTimeout(() => {
        initWebSocket(); // 初始化连接WebSocket
        isReconnect = false;
    }, 2000);
}
// 初始化连接WebSocket
function initWebSocket() {
    var socketUrl = 'ws://url';
    // 判断浏览器是否支持websocket,不支持给除提示
    window.WebSocket = window.WebSocket || window.MozWebSocket;
    if(!window.WebSocket) {
        console.log("当前浏览器不支持websocket");
        return;
    }
    // 创建实例,连接服务器
    ws = new WebSocket(socketUrl);
    // 连接成功,可以发送消息
    ws.onopen = function(event) {};
    // 收到服务器消息后触发
    ws.onmessage = function(event) {};
    // 连接断开,进行重连
    ws.onclose = function(event) {
        reconnectWebSocket(); 
    }
    // 发生错误时,进行重连
    ws.onerror = function(event) {
        reconnectWebSocket();
    }
}

webSocket 心跳机制

webSocket重连机制,一般的网络断开,连接不稳定断开可以重连成功,但是有些情况是无法被检测到连接已经断开的,比如潮涌无线连接时,设备连着wifi,凡是wifi实际上已经没有流量或者跟网络的连接已经断开,webSocket是不会知道的,或者检测到了会一直不停重连,但是一直连不上,等到wifi恢复正常了也很大机率是连接失败。这个时候需要加上心跳机制,心跳是前端和后端的一种约定,前端每过一段时间就向后端发送规定格式的"心跳",后台收到这个心跳后,返回相应的信息,只要两端一直有心跳的交互就任务交互还存在。如果在约定时间内(比如2分钟)前端都没有收到后端返回的心跳信息,那么久可以认为这是一个有问题的连接,前端就主动断掉这次连接,重新发起一个webSocket连接。

webSocket 简单的封装

(function() {
    // 消息推送
    function ChatNotice(options) {
        this.socket; // websocket对象  
        this.isConnection = false; // 是否连接服务器 
        this.timer = null; 
        this.loginData = options.login; 
        this.socketUrl = options.url;
        this.sessionid = ""; 
        // 初始化WebSocket连接
        this.initConnection();
    }

    // 初始化WebSocket连接
    ChatNotice.prototype.initConnection = function () {
        var _that = this; 
        // 判断浏览器是否支持websocket
        window.WebSocket = window.WebSocket || window.MozWebSocket;
        if (!window.WebSocket) {
            console.log("当前浏览器不支持websocket, 建议使用谷歌浏览器访问");
            return;
        }
        // 连接服务器
        this.socket = new WebSocket(this.socketUrl);
        console.log("readyState:::", this.socket.readyState);
        // 服务器连接成功后回调
        this.socket.onopen = function(event) {
            console.log(':::::::连接成功!!!:::::::'); 
            _that.login();
        };
        // 收到服务器消息后回调
        this.socket.onmessage = function(event) { 
            var data = JSON.parse(event.data);
            _that.handleMessage(data);
        }
        // 连接断开后回调
        this.socket.onclose = function(event) {
            console.log(":::::::断开连接:::::::");  
            _that.againConnectionWebSocket(); 
        };
        // 报错时回调
        this.socket.onerror = function(event) {
            console.log(":::::::websocket遇到错误:::::::"); 
            _that.againConnectionWebSocket();
        }
        return this;
    }

    // 发送消息给服务器进行登录
    ChatNotice.prototype.login = function() {
        var data = this.loginData;
        if(data.order == undefined) { 
            data.order = 1;
        }
        if(this.sessionid) {
            data.sessionid = this.sessionid;
        } 
        console.log(':::::::发送消息:::::::', data);
        // 向服务器发送消息
        this.socket.send(JSON.stringify(data));
    }

    // 处理消息
    ChatNotice.prototype.handleMessage = function(msg) { 
        console.log(':::::::处理消息:::::::', msg);
        var head = msg.head;
        var method = this.loginData.method;
        if (head.method == method && head.errcode == 0) {
            this.sessionid = msg.data.sessionid;
        } else {
            // 处理消息
        }
    }

    // 断掉重新连接服务器
    ChatNotice.prototype.againConnectionWebSocket = function() { 
        var _that = this;
        if(this.isConnection) { 
            return; 
        }
        console.log(':::::::尝试重新连接服务器:::::::');
        this.isConnection = true;
        this.timer && clearTimeout(this.timer);
        this.timer = setTimeout(function() { 
            _that.initConnection();
            this.isConnection = false;
        }, 1000);  
    }

    new ChatNotice({
        url: "ws://10.219.245.1:5560",
        login: {
            method: 8533,
            opname: socketAccount
        }
    }); 
}());

参考学习地址

原文地址:https://www.cnblogs.com/yuxi2018/p/11481003.html