1.什么是WebSocket

HTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。WebSocket是一种通信协议,可在单个TCP连接上进行全双工通信。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以建立持久性的连接,并进行双向数据传输。首先,要明白WebSocket是一种通信协议,区别于HTTP协议,HTTP协议只能实现客户端请求,服务端响应的这种单项通信。而WebSocket可以实现客户端与服务端的双向通讯,说白了,最大也是最明显的区别就是可以做到服务端主动将消息推送给客户端。

对大部分web开发者来说,上面这段描述有点枯燥,其实只要记住几点:

  1. WebSocket可以在浏览器里使用

  2. 支持双向通信

  3. 使用很简单

WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算) 首先HTTP有1.1和1.0之说,也就是所谓的keep-alive(keep-alive是Vue提供的一个抽象组件,用来对组件进行缓存,从而节省性能)把多个HTTP请求合并为一个,但是Websocket其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充可以通过这样一张图理解

**

两者之间有交集,但是并不是全部。WebSocket 本质上跟 HTTP 完全不一样,只不过为了兼容性,WebSocket 的握手是以 HTTP 的形式发起的,如果服务器或者代理不支持 WebSocket,它们会把这当做一个不认识的 HTTP 请求从而优雅地拒绝掉。 另外Html5是指的一系列新的API,或者说新规范,新技术。Http协议本身只有1.0和1.1,而且跟Html本身没有直接关系。

二、Websocket是什么样的协议,具体有什么优点

优点,这里的对比参照物是HTTP协议,概括地说就是:支持双向通信,更灵活,更高效,可扩展性更好。

  • 1)支持双向通信,实时性更强;

  • 2)更好的二进制支持;

  • 3)较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2~10字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的4字节的掩码。而HTTP协议每次通信都需要携带完整的头部;

  • 4)支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)

    对于后面两点,没有研究过WebSocket协议规范的同学可能理解起来不够直观,但不影响对WebSocket的学习和使用。

    传统HTTP与WebSocket客户端与服务器请求响应模式如下图所示:

    上图对比可以看出,相对于传统HTTP每次请求-应答都需要客户端与服务端建立连接的模式,WebSocket是类似Socket的TCP长连接通讯模式。一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。在客户端断开WebSocket连接或Server端中断连接前,不需要客户端和服务端重新发起连接请求。在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。

    举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。

其他特点包括:

(1)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(2)没有同源限制,客户端可以与任意服务器通信。

(3协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

三、Websocket的作用

像http这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。在讲Websocket之前,我就顺带着讲下 long poll(长轮询) 和 ajax轮询 的原理。 首先是 ajax轮询 ,ajax轮询 的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。

ajax轮询场景再现:
客户端:啦啦啦,有没有新信息(Request)
服务端:没有(Response)
客户端:啦啦啦,有没有新信息(Request)
服务端:没有。。(Response)
客户端:啦啦啦,有没有新信息(Request)
服务端:你好烦啊,没有啊。。(Response)
客户端:啦啦啦,有没有新消息(Request)
服务端:好啦好啦,有啦给你。(Response)

long poll 其实原理跟 ajax轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。

long poll场景再现
客户端:啦啦啦,有没有新信息,没有的话就等有了才返回给我吧(Request)
服务端:额。。  等待到有消息的时间......来 给你(Response)
客户端:啦啦啦,有没有新信息,没有的话就等有了才返回给我吧(Request) -loop

从上面可以看出其实这两种方式,都是在不断地建立HTTP连接,然后等待服务端处理,可以体现HTTP协议的另外一个特点,被动性。 何为被动性呢,其实就是,服务端不能主动联系客户端,只能有客户端发起。简单地说就是,服务器很懒(不会、不能主动发起连接),但是如果有客户来,不管多么累都要好好接待。同时HTTP还是一个无状态协议,如果你把本次连接断开,再次访问得再告诉服务器一遍你需要的请求

ajax轮询 需要服务器有很快的处理速度和资源。(速度) long poll 需要有很高的并发,也就是说同时接待客户的能力。(场地大小)

在这种情况下出现了,Websocket的作用就体现出来了。 他解决了HTTP的这几个难题。 首先,被动性,当服务器完成协议升级后(HTTP->Websocket),服务端就可以主动推送信息给客户端啦。

所以上面的情景可以做如下修改。
客户端:啦啦啦,我要建立Websocket协议
服务端:ok,确认,已升级为Websocket协议
客户端:麻烦你有信息的时候推送给我噢。。
服务端:ok,有的时候会告诉你的。
服务端:耐心等待一会哦,暂时没有嘞
服务端:噜啦噜啦嘞
服务端:你要的信息已经成功推送哦!

情景就变成了这样,只需要经过一次HTTP请求,就可以做到源源不断的信息传送了。(在程序设计中,这种设计叫做回调,即:你有信息了再来通知我,而不是我傻乎乎的每次跑来问你) 这样的协议解决了上面同步有延迟,而且还非常消耗资源的这种情况。 那么为什么它能够解决服务器上消耗资源的问题呢? 其实我们所用的程序是要经过两层代理的,即HTTP协议在Nginx等服务器的解析下,然后再传送给相应的Handler(PHP等)来处理。 简单地说,我们有一个非常快速的接线员(Nginx),他负责把问题转交给相应的客服(Handler)。 本身接线员基本上速度是足够的,但是每次都卡在客服(Handler)了,老有客服处理速度太慢。,导致客服不够。 Websocket就解决了这样一个难题,建立后,可以直接跟接线员建立持久连接,有信息的时候客服想办法通知接线员,然后接线员在统一转交给客户。这样就可以解决客服处理速度过慢的问题了。

同时,在传统的方式上,要不断的建立,关闭HTTP协议,由于HTTP是非状态性的,每次都要重新传输identity info(鉴别信息),来告诉服务端你是谁。虽然接线员很快速,但是每次都要听这么一堆,效率也会有所下降的,同时还得不断把这些信息转交给客服,不但浪费客服的处理时间,而且还会在网路传输中消耗过多的流量/时间。 但是Websocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的非状态性,服务端会一直知道你的信息,直到你关闭请求,这样就解决了接线员要反复解析HTTP协议,还要查看identity info的信息。 同时由客户主动询问,转换为服务器(推送)有信息的时候就发送(当然客户端还是等主动发送信息过来的),没有信息的时候就交给接线员(Nginx),不需要占用本身速度就慢的客服(Handler)了

四、建连

网络协议:

前文说到,Websocket 是建立与 TCP 之上,那么其与 HTTP 协议有和关系呢?

Websocket 连接分为建连阶段与连接阶段,在建立连接阶段借助于 HTTP ,而在连接阶段则与 HTTP 无关。

建连阶段:

从浏览器的 Network 中,找到 ws 连接,可以看到相比于我们常见的 HTTP 请求协议,请求头中多了几个字段:

重点请求首部意义如下:

  • Connection: Upgrade:表示要升级协议

  • Upgrade: websocket:表示要升级到websocket协议。

  • Sec-WebSocket-Version: 13:表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。

  • Sec-WebSocket-Key :是一个 Base64 encode 的值,由浏览器随机生成的,用于验证服务器连接的正确性;与后面服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,或者无意的连接。

  • Connection 为 Upgrade ,Upgrade 为 websocket ,表示告知 Nginx 与 Apache 等服务器该次连接并非为 HTTP 连接,实质上是一个 websocket ,因此服务器会转发到相应的 websocket 任务处理;

  • Sec-WebSocket-Versio 表示为使用的 websocket 服务版本;

    General
    Request URL: ws://localhost:8080/chat/1622535375427
    Request Method: GET
    Status Code: 101
    ​
    Response Headers
    Connection: upgrade
    Date: Tue, 01 Jun 2021 08:26:16 GMT
    Sec-WebSocket-Accept: 0B5dd5OvoKZz8wZPwoThm7m7dkM=
    Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15
    Upgrade: websocket
    ​
    Request Headers
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9
    Cache-Control: no-cache
    Connection: Upgrade
    Cookie: JSESSIONID=22274088E9C9AC341031F73BB43165CD
    Host: localhost:8080
    Origin: http://localhost:8080
    Pragma: no-cache
    Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
    Sec-WebSocket-Key: 79Lm/KW+P41h25Yp5tqXYg==
    Sec-WebSocket-Version: 13
    Upgrade: websocket
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4484.7 Safari/537.36

    可以看到其返回状态码为 101 ,表示切换协议;Upgrade 与 Connection 用于回复客户端表示已经切换协议成功;Sec-WebSocket-Accept 字段与 Sec-WebSocket-Key 相对应,用于验证服务的正确性;Sec-WebSocket-Key是WebSocket客户端发送的一个 base64编码的密文,要求服务端必须返回一个对应加密的Sec-WebSocket-Accept应答,否则客户端会抛出Error during WebSocket handshake错误,并关闭连接。

    连接阶段:

    当通过 HTTP 建立连接握手后,接下来则是真正的 Websocket 连接了,其基于 TCP 收发数据,Websocket 封装并开放接口。

    WSS:

    在 HTTP 协议中,很多时候为了加密与安全需要使用 HTTPS 请求(HTTP + TCL); 相应的,在 Websocket 协议中,也是可以使用加密传输的 —— wss ,比如 wss://localhost:8080。

    使用的也是与 HTTPS 一样的证书,在这里一般是交由 Nginx 等服务层去做证书处理。

五、实践

在浏览器中使用 Websocket 非常简单,在支持 Websocket 的浏览器中会提供了原生的 WebSocekt 对象,其中对于消息的接收与数据帧处理在浏览器中已经封装好了。浏览器中提供了原生类 WebSocket ,使用 new 关键字实例化它:

格式:WebSocket WebSocket(String url,optional String | [] protocols);
例:this.websocket = new WebSocket(api.websocket(this.form.id))

接收两个参数:

  • url 表示需要连接的地址,比如:ws://localhost:8080;

  • protocols 可选参数,可以是一个字符串或者一个数组,用来表示子协议,这样做可以让一个服务器实现多种 WebSocket 子协议;

    实例化对象提供两个方法:

  • send 接收一个 String|ArrayBuffer|Blob 数据,作为数据发送到服务端;

  • close 接收一个(可选)的 code(关闭状态号,默认为 1000) 与一个(可选)的字符串(表示断开原因),客户端主动断开连接; 连接状态:

WebSocket 类提供了一些常量表示连接状态:

  • WebSocket.CONNECTING 0 连接还没开启;

  • WebSocket.OPEN 1 连接已开启并准备好进行通信;

  • WebSocket.CLOSING 3 连接正在关闭的过程中;

  • WebSocket.CLOSED 4 连接已经关闭,或者连接无法建立;

  • WebSocket 的实例对象中提供了 readyState 属性来判断当前状态;

实例化对象中可以监听到以下事件:

  • open 连接打开的回调事件,这时 readyState 变为 OPEN;

  • message 收到消息的回调事件,同时回调函数接收到一个 MessageEvent 数据;

  • close 连接关闭的回调事件,这时 readyState 变为 CLOSED;

  • error 建立与连接过程发生错误的回调事件;

    事件 事件处理程序 描述
    open Socket.onopen 连接建立时触发
    message Socket.onmessage 客户端接收服务端数据时触发
    error Socket.onerror 通信发生错误时触发
    close Socket.onclose 连接关闭时触发

    WebSocket 方法:以下是 WebSocket 对象的相关方法。

    方法 描述
    Socket.send() 使用连接发送数据
    Socket.close() 关闭连接
 //连接websocket的方法initWebSocket() {let $this = this;this.websocket = new WebSocket(api.websocket(this.form.id))//链接发送错误时调用this.websocket.onerror = function () {$this._notify('链接错误', 'WebSocket链接错误', 'error')}//链接成功时调用this.websocket.onopen = function () {$this._notify('链接成功', 'WebSocket链接成功', 'success')}//接收到消息时回调this.websocket.onmessage = function (event) {$this.clean()let entity = JSON.parse(event.data);//上线提醒if (entity.data == undefined) {$this.online = entity.online$this.initUser()$this._notify('消息', entity.msg, 'info')return;}//消息接收let data = JSON.parse(event.data).dataif (data.online != undefined) {$this.online = data.online}if (data.to != undefined) {//单个窗口发送,仅推送到指定的窗口if (data.to.id == this.current_window_id) {$this.messageList.push(data)}} else {//群发,推送到官方群组窗口$this.messageList.push(data)}}//链接关闭时调用this.websocket.onclose = function () {$this._notify('链接关闭', 'WebSocket链接关闭', 'info')}},//推送消息方法send() {if (this.form.message == null || this.form.message.trim() == '') {this._message('请输入消息内容', 'warning')return;}if (!this.current_window_id) {this.websocket.send(this.form.message.replace(/[\r\n]/g,""))this.initCommonMessage()} else {let data = {message: this.form.message,from: this.user}this.$http.post(api.pushId(this.current_window_id), JSON.stringify(data)).then(response => {this.intSelfMessage()this.clean()this._notify('推送成功', '消息推送成功', 'success')})}this.scroll()},//注销logout() {this.$http.delete(api.logout(this.form.id)).then(response => {this.websocket.close()window.location.href = "/"})},

事件与数据:

对 WebSocket 实例监听事件有两种方式,这里以 message 事件为例:

  • 对 onmessage 属性直接赋值,正如以上:websocket.onmessage = function () {};

  • 使用 addEventListener 监听事件,如:websocket.addEventListener('message', function () {});

在 message 回调函数中得到 MessageEvent 类型参数 e ,我们需要的数据可以通过 e.data 获取;

需要注意的一点是:不论服务端与客户端,其接受到的数据都是序列化后的字符串(当然也有 ArrayBuffer|Blob 类型数据),很多时候我们需要解析处理数据,比如 JSON.parse(e.data)

因为WebSocket的特性,虽然能实时接收到消息,但每次刷新浏览器,之前发送过的消息都会丢失,因此这里实现了消息储存功能。

利用HttpSession,将每次会话,用户推送的消息都储存到HttpSession中。前端利用Vue的 created() 钩子函数,每次刷新页面时都先请求获取HTTPSession中已储存的消息列表。

因为涉及到单窗口推送消息、群发消息的限定:

这里规定了HttpSession中会话消息的前缀标识,以此来区分不同的消息

  • CHAT_COMMON_PREFIX = "CHAT_COMMON_": 群发消息Session Key前缀标识

  • CHAT_FROM_PREFIX = "CHAT_FROM_": 推送方Session Key前缀标识

  • CHAT_TO_PREFIX = "_TO_": 接收方Session Key前缀标识

六、总结

没有其他能像 WebSocket 一样实现全双工传输的技术了,迄今为止,大部分开发者还是使用 Ajax 轮询来实现,但这是个不太优雅的解决办法,WebSocket 虽然用的人不多,可能是因为协议刚出来的时候有安全性的问题以及兼容的浏览器比较少,但现在都有解决。如果你有这些需求可以考虑使用 WebSocket:

  • 多个用户之间进行交互;

  • 需要频繁地向服务端请求更新数据。

比如弹幕、消息订阅、多玩家游戏、协同编辑、视频会议、在线教育等需要高实时的场景。

基于WebSocket的在线聊天室相关推荐

  1. .NET Core 实现基于Websocket的在线聊天室

    什么是Websocket 我们在传统的客户端程序要实现实时双工通讯第一想到的技术就是socket通讯,但是在web体系是用不了socket通讯技术的,因为http被设计成无状态,每次跟服务器通讯完成后 ...

  2. websocket一直无法链接_.NET Core 实现基于Websocket的在线聊天室

    什么是Websocket 我们在传统的客户端程序要实现实时双工通讯第一想到的技术就是socket通讯,但是在web体系是用不了socket通讯技术的,因为http被设计成无状态,每次跟服务器通讯完成后 ...

  3. workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的)...

    workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的) 一.总结 1.下面链接里面还有一个来聊的php聊天室源码可以学习 2. ...

  4. springboot+websocket构建在线聊天室(群聊+单聊)

    系列导读: 1.springboot+websocket构建在线聊天室(群聊+单聊) 2.Spring Boot WebSocket:单聊(实现思路) 3.Websocket Stomp+Rabbit ...

  5. php即时聊天的框架_workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的)...

    workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的) 一.总结 1.下面链接里面还有一个来聊的php聊天室源码可以学习 2. ...

  6. SpringBoot与webSocket实现在线聊天室——实现私聊+群聊+聊天记录保存

    SpringBoot与webSocket实现在线聊天室--实现私聊+群聊+聊天记录保存 引用参考:原文章地址:https://blog.csdn.net/qq_41463655/article/det ...

  7. 从头搭建一个基于 Python 的在线聊天室

    本场 Chat,是基于 Python + Redis + Flask 来搭建一个简单易用的在线聊天室.完全从零开始,一步一步完成整个项目. 主要分享内容: Flask 项目结构 Python Redi ...

  8. php+websocket实现在线聊天室(一)

    聊天室最终实现版:https://www.sinight.site/chatroom 可以自己多开几个窗口体验 前言:WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的 ...

  9. Vue3 -- 基于Websocket实现简易聊天室

    文章目录 标题 代码地址 表情包资源 chat.data.ts index.vue 标题 接上一篇博文 这里使用 Vue3 + Typescript + Websocket 实现在线聊天功能的前端部分 ...

最新文章

  1. 如何导入给定名称的模块为字符串?
  2. camel_Apache Camel 2.14中的更多指标
  3. Android ScrollView嵌套RecyclerView导致在三星s8曲面屏显示不全问题
  4. 为什么有时打不开爬取到的图片
  5. c语言编写一个函数判断闰年,C语言:实现一个函数判断year是不是闰年
  6. 什么是python函数_什么是python函数
  7. oracle什么时候用in,Oracle Study之---Oracle IN和NOT IN的使用
  8. Java RMI 介绍
  9. 5.26在网上看到的方法,实现图形缩放、对齐、图形修改后进行dirty check。(未实验过)...
  10. 引入外部机构需要注意的事项_如何与外部营销机构合作
  11. python列表推导式使用
  12. centos7 mysql升级漏洞5.7.30
  13. windows下配置NGINX实现内网穿透并配置开机自启动
  14. 2017 谷歌 I/O大会
  15. 基于qiankun.js的微前端应用实战
  16. 华为p40手机是不是android,华为P40新手机配新操作系统,网友:再见了安卓
  17. 【工作笔记】Springboot一个比较通用的数据脱敏处理办法
  18. Android音频学习之MediaExtractor,提取音频视频轨道数据(从视频中分离音频视频数据)
  19. 中文转拼音 java_Java中文转拼音
  20. 微信怎么和计算机发送文件格式,用微信怎么发送文件 手机微信发送文件、视频方法图文详解...

热门文章

  1. 游戏思考04总结:针对帧、状态、物理同步的总结(之前写的太长,现在简略下)
  2. 一个友好的扫雷程序————C初学者都能会的简单扫雷(一)
  3. oracle的dump头文件用ue显示,关于Oracle dmp文件导入随笔
  4. MySQL中CONCAT()函数用法详解
  5. JAVA采集图书的ISBN编号编码、出版社、出版时间、版次、正文语种、定价等信息
  6. timewait php,timewait是什么意思
  7. 灰度图转bmp文件 C++
  8. Springy_Facebook_Rebound
  9. JMeter安装配置及使用说明【最全面】
  10. MATLAB编程之PSYCHTOOLBOX(PTB):展示实验介绍等待按空格键进行下一步实验