目录

WebSocket协议是什么

WebSocket是应用层协议

WebSocket是基于TCP的应用层协议,用于在C/S架构的应用中实现双向通信,关于WebSocket协议的详细规范和定义参见rfc6455

需要特别注意的是:虽然WebSocket协议在建立连接时会使用HTTP协议,但这并意味着WebSocket协议是基于HTTP协议实现的。

WebSocket与Http的区别

实际上,WebSocket协议与Http协议有着本质的区别:

1.通信方式不同

WebSocket是双向通信模式,客户端与服务器之间只有在握手阶段是使用HTTP协议的“请求-响应”模式交互,而一旦连接建立之后的通信则使用双向模式交互,不论是客户端还是服务端都可以随时将数据发送给对方;而HTTP协议则至始至终都采用“请求-响应”模式进行通信。也正因为如此,HTTP协议的通信效率没有WebSocket高。

2.协议格式不同

WebSocket与HTTP的协议格式是完全不同的,具体来讲:

(1)HTTP协议(参见:rfc2616)比较臃肿,而WebSocket协议比较轻量。

(2)对于HTTP协议来讲,一个数据包就是一条完整的消息;而WebSocket客户端与服务端通信的最小单位是帧(frame),由1个或多个帧组成一条完整的消息(message)。即:发送端将消息切割成多个帧,并发送给服务端;服务端接收消息帧,并将关联的帧重新组装成完整的消息。

WebSocket协议格式:

 0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+

HTTP请求消息格式:

Request-LineCRLF
general-headerCRLF
request-headerCRLF
entity-headerCRLF
CRLF
[ message-body ]

HTTP响应消息格式:

Status-LineCRLF
general-headerCRLF
response-headerCRLF
entity-headerCRLF
CRLF
[ message-body ]

虽然WebSocket和HTTP是不同应用协议,但rfc6455规定:“WebSocket设计为通过80和443端口工作,以及支持HTTP代理和中介”,从而使其与HTTP协议兼容。为了实现兼容性,WebSocket握手时使用HTTP Upgrade头从HTTP协议更改为WebSocket协议,参考:WebSocket维基百科

为什么要使用WebSocket

随着Web应用的发展,特别是动态网页的普及,越来越多的场景需要实现数据动态刷新。

在早期的时候,实现数据刷新的方式通常有如下3种:

1.客户端定时查询

客户端定时查询(如:每隔10秒钟查询一次)是最原始也是最简单的实现数据刷新的方法,服务端不用做任何改动,只需要在客户端添加一个定时器即可。但是这种方式的缺点也很明显:大量的定时请求都是无效的,因为服务端的数据并没有更新,相应地也导致了大量的带宽浪费。

2.长轮训机制

长轮训机制是对客户端定时查询的一种改进,即:客户端依旧保持定时发送请求给服务端,但是服务端并不立即响应,而是等到真正有数据更新的时候才发送给客户端。实际上,并不是当没有数据更新时服务端就永远都不响应客户端,而是需要在等待一个超时时间之后结束该次长轮训请求。相对于客户端定时查询方式而言,当数据更新频率不确定时长轮训机制能够很明显地减少请求数。但是,在数据更新比较频繁的场景下,长轮训方式的优势就没那么明显了。

在Web开发中使用得最为普遍的长轮训实现方案为Comet(Comet (web技术)),Tomcat和Jetty都有对应的实现支持,详见:WhatIsCometWhy Asynchronous Servlets

3.HTTP Streaming

不论是长轮训机制还是传统的客户端定时查询方式,都需要客户端不断地发送请求以获取数据更新,而HTTP Streaming则试图改变这种方式,其实现机制为:客户端发送获取数据更新请求到服务端时,服务端将保持该请求的响应数据流一直打开,只要有数据更新就实时地发送给客户端。

虽然这个设想是非常美好的,但这带来了新的问题:

(1)HTTP Streaming的实现机制违背了HTTP协议本身的语义,使得客户端与服务端不再是“请求-响应”的交互方式,而是直接在二者建立起了一个单向的“通信管道”。

(2)在HTTP Streaming模式下,服务端只要得到数据更新就发送给客户端,那么就需要客户端与服务端协商如何区分每一个更新数据包的开始和结尾,否则就可能出现解析数据错误的情况。

(3)另外,处于客户端与服务端的网络中介(如:代理)可能会缓存响应数据流,这可能会导致客户端无法真正获取到服务端的更新数据,这实际上与HTTP Streaming的本意是相违背的。

鉴于上述原因,在实际应用中HTTP Streaming并没有真正流行起来,反之使用得最多的是长轮训机制。

显然,上述几种实现数据动态刷新的方式都是基于HTTP协议实现的,或多或少地存在这样那样的问题和缺陷;而WebSocket是一个全新的应用层协议,专门用于Web应用中需要实现动态刷新的场景。

相比起HTTP协议,WebSocket具备如下特点:

  1. 支持双向通信,实时性更强。
  2. 更好的二进制支持。
  3. 较少的控制开销:连接创建后,WebSockete客户端、服务端进行数据交换时,协议控制的数据包头部较小。
  4. 支持扩展。

如何使用WebSocket

客户端API

在Web应用的网页中使用WebSocket,WebSocket对象提供了用于创建和管理WebSocket连接,以及可以通过该连接发送和接收数据的API。

1.构造函数

可以使用WebSocket类的构造函数(WebSocket(url[, protocols]))实例化一个对象,如:

var url = "ws://host:port/endpoint";
var ws = new WebSocket(url);

执行上述语句之后,浏览器将与服务端建立一个WebSocket连接,同时返回一个WebSocket实例对象ws。

2.对象属性

WebSocket实例对象具备如下属性:

  • WebSocket.binaryType: 返回websocket连接所传输二进制数据的类型。
  • WebSocket.bufferedAmount:只读属性,用于返回已经被send()方法放入队列中但还没有被发送到网络中的数据的字节数。一旦队列中的所有数据被发送至网络,则该属性值将被重置为0。但是,若在发送过程中连接被关闭,则属性值不会重置为0。如果你不断地调用send(),则该属性值会持续增长。
  • WebSocket.extensions:只读属性,返回服务器已选择的扩展值。目前,链接可以协定的扩展值只有空字符串或者一个扩展列表。
  • WebSocket.protocol:只读属性,用于返回服务器端选中的子协议的名字;这是一个在创建WebSocket对象时,在参数protocols中指定的字符串。
  • WebSocket.readyState:只读属性,返回当前WebSocket对象的链接状态,可能的值为WebSocket中定义的常量:WebSocket.CONNECTING,WebSocket.OPEN,WebSocket.CLOSING,WebSocket.CLOSED。
  • WebSocket.url:只读属性,返回值为当构造函数创建WebSocket实例对象时URL的绝对路径。
  • WebSocket.onopen:用于指定连接成功后的回调函数,当WebSocket的连接状态readyState变为“OPEN”时调用;这意味着当前连接已经准备好发送和接受数据,这个事件处理程序通过事件(建立连接时)触发。
  • WebSocket.onclose:用于指定连接关闭后的回调函数,当WebSocket的连接状态readyState变为“CLOSED”时被调用,它接收一个名字为“close”的CloseEvent事件对象。
  • WebSocket.onmessage:用于指定当从服务器接受到信息时的回调函数,当从服务器收到一条消息时,该回调函数将被调用,在函数中接受一命名为“message”的MessageEvent事件对象。
  • WebSocket.onerror:用于指定连接失败后的回调函数,定义一个发生错误时执行的回调函数,此事件的事件名为"error"。

3.对象方法

WebSocket定义了2个方法:

(1)WebSocket.send(data):向服务器发送数据,将需要通过WebSocket连接传输至服务器的数据排入队列,并根据所需要传输的数据字节的大小来增加属性bufferedAmount的值 。若数据无法传输(例如数据需要缓存而缓冲区已满)时,套接字会自行关闭。

参数data为传输至服务器的数据,它必须是以下类型之一:

  • USVString:文本字符串。字符串将以UTF-8格式添加到缓冲区,并且属性bufferedAmount将加上该字符串以UTF-8格式编码时的字节数的值。
  • ArrayBuffer:您可以使用一个有类型的数组对象发送底层二进制数据,其二进制数据内存将被缓存于缓冲区,属性bufferedAmount将加上所需字节数的值。
  • Blob:Blob类型将队列blob中的原始数据以二进制传输,属性bufferedAmount将加上原始数据的字节数的值。
  • ArrayBufferView:以二进制帧的形式发送任何JavaScript类数组对象,其二进制数据内容将被队列于缓冲区中,属性bufferedAmount将加上对应字节数的值。

(2)WebSocket.close([code[, reason]]):关闭当前连接,如果连接已经关闭,则此方法不执行任何操作。

参数:

  • code:可选,为一个数字状态码,它解释了连接关闭的原因。如果没有传这个参数,默认使用1005。CloseEvent的允许的状态码见状态码列表
  • reason:可选,一个人类可读的字符串,它解释了连接关闭的原因,这个UTF-8编码的字符串不能超过123个字节。

异常:

  • INVALID_ACCESS_ERR:一个无效的code。
  • SYNTAX_ERR:reason字符串太长(超过123字节)。

更多WebSockete API的详细内容参见W3C的定义:The WebSocket API

在客户端使用WebSocket

如下为在网页中使用原生WebSocket的实现方式。

var url = "ws://localhost:8080/websocket/text";
var ws = new WebSocket(url);
ws.onopen = function(event) {
console.log("websocket connection open.");
console.log(event);
}; ws.onmessage = function(event) {
console.log("websocket message received.")
console.log(event.data);
}; ws.onclose = function (event) {
console.log("websocket connection close.");
console.log(event.code);
}; ws.onerror = function(event) {
console.log("websocket connection error.");
console.log(event);
};

在Web网页中使用WebSocket需要浏览器支持,不同浏览器软件版本对WebSocket的支持情况详见浏览器兼容性

另外,WebSocket客户端除了可以在网页中使用,目前还存在一些独立的客户端组件,如:

1.Jetty WebSocket Client API

2.websockets-api-java-spring-client

3.Java-WebSocket

在服务端使用WebSocket

在服务端使用WebSocket需要服务器组件支持,如下以在Tomcat 8.5.41(Tomcat 7之后才支持WebSocket)中使用原生WebSocket为例。

由于在服务端使用WebSocket需要使用到WebSocket的API,因此需要添加API依赖管理:

<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-websocket-api</artifactId>
<version>8.5.41</version>
</dependency>

使用注解方式编写WebSocket服务端:

@ServerEndpoint(value="/websocket/text")
public class WebSocketTest {
private static final Logger logger = LoggerFactory.getLogger(WsChatAnnotation.class); private static final AtomicInteger counter = new AtomicInteger(0); // 客户端计数器
private static final Set<WsChatAnnotation> connections = new CopyOnWriteArraySet<WsChatAnnotation>(); // 客户端websocket连接集合
private Session session = null; // WebSocket会话对象
private Integer number = 0; // 客户端编号 public WsChatAnnotation() {
number = counter.incrementAndGet();
} /**
* 客户端建立websocket连接
* @param session
*/
@OnOpen
public void start(Session session) {
logger.info("on open");
this.session = session;
connections.add(this);
try {
session.getBasicRemote().sendText(new StringBuffer().append("Hello: ").append(number).toString());
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 客户端断开websocket连接
*/
@OnClose
public void close() {
logger.info("session close");
try {
this.session.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
connections.remove(this);
}
} /**
* 接收客户端发送的消息
* @param message
*/
@OnMessage
public void message(String message) {
logger.info("message: {}", message);
for(WsChatAnnotation client : connections) {
synchronized (client) {
try {
client.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
} @OnError
public void error(Throwable t) {
logger.error("client: {} error", number, t.getMessage());
}
}

反向代理对WebSocket的支持

当下的Web应用架构通常都是集群化部署,前端使用反向代理或者直接部署负载均衡器,这就要求反向代理或者负载均衡器必须支持WebSocket协议。

目前Nginx,Haporxy都已经支持WebSocket协议。

如下为在使用nginx作为反向代理的场景下,配置nginx代理websocket协议。

# add websocket proxy
location ~ /ws {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://8080;
}

【参考】

https://spring.io/blog/2012/05/08/spring-mvc-3-2-preview-techniques-for-real-time-updates/ Spring MVC 3.2 Preview: Techniques for Real-time Updates

https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket#构造函数 WebSocket

https://www.cnblogs.com/chyingp/p/websocket-deep-in.html WebSocket协议:5分钟从入门到精通

http://www.ruanyifeng.com/blog/2017/05/websocket.html WebSocket 教程

https://blog.csdn.net/chszs/article/details/26369257 Nginx担当WebSockets代理

http://blog.fens.me/nodejs-websocket-nginx/ Nginx反向代理Websocket

WebSocket协议入门介绍的更多相关文章

  1. WebSocket协议:5分钟从入门到精通

    一.内容概览 WebSocket的出现,使得浏览器具备了实时双向通信的能力.本文由浅入深,介绍了WebSocket如何建立连接.交换数据的细节,以及数据帧的格式.此外,还简要介绍了针对WebSocke ...

  2. 八问WebSocket协议:为你快速解答WebSocket热门疑问

    一.引言 WebSocket是一种比较新的协议,它是伴随着html5规范而生的,虽然还比较年轻,但大多主流浏览器都已经支持.它使用方面.应用广泛,已经渗透到前后端开发的各种场景中. 对http一问一答 ...

  3. internet协议入门

    前言 劳于读书,逸于作文. 原文地址:internet协议入门 博主博客地址:Damonare的个人博客 博主之前写过一篇博客:网络协议分析,在这篇博客里通过抓包,具体的分析了不同网络协议的传送的数据 ...

  4. Websocket 协议解析

    WebSocket protocol 是HTML5一种新的协议.它是实现了浏览器与服务器全双工通信(full-duplex).          现 很多网站为了实现即时通讯,所用的技术都是轮询(po ...

  5. Network - 互联网协议入门

    珠玉在前,不在赘言 互联网协议入门(一) 互联网协议入门(二) 通信协议:HTTP.TCP.UDP 互联网协议入门 互联网协议入门深入 当你输入一个网址,实际会发生什么? Wireshark基本介绍和 ...

  6. SNMP协议入门

    SNMP协议入门 1.引言 基于TCP/IP的网络管理包含3个组成部分: 1) 一个管理信息库MIB(Management Information Base).管理信息库包含所有代理进程的所有可被查询 ...

  7. WebSocket协议开发

    一直以来,网络在很大程度上都是围绕着HTTP的请求/响应模式而构建的.客户端加载一个网页,然后直到用户点击下一页之前,什么都不会发生.在2005年左右,Ajax开始让网络变得更加动态了.但所有的HTT ...

  8. [Python爬虫] 在Windows下安装PhantomJS和CasperJS及入门介绍(上)

    最近在使用Python爬取网页内容时,总是遇到JS临时加载.动态获取网页信息的困难.例如爬取CSDN下载资源评论.搜狐图片中的“原图”等,此时尝试学习Phantomjs和CasperJS来解决这个问题 ...

  9. WebSocket协议

    websocket 简介 (2013-04-09 15:39:28) 转载▼   分类: websocket 一 WebSocket是html5新增加的一种通信协议,目前流行的浏览器都支持这个协议,例 ...

随机推荐

  1. openStack kvm 虚拟机CPU颗粒化控制

    前一篇理解cpu topology对CPU Topology进行了学习总结,这里想总结下OpenStack下vCPU与pCPU常用的的绑定方式. 在尝试这些绑定之前,尤其是处理NUMA架构时还是建议看 ...

  2. [Uva12260]Free Goodies(dp+贪心)

    解题关键:先对p进行排序,消除p的影响,然后对w进行01背包即可.注意p对w的约束.j<=(cur+1)/2 #include<cstdio> #include<cstring ...

  3. C++类中的常数据成员和静态数据成员的区别

    刚开始学习C++的类和对象的部分,对类中的常数据成员和静态数据成员的概念和用法经常混淆,所以今天整理一下,顺便说一下,今天是我的生日,祝我生日快乐,呵呵. 常数据成员 常数据成员是指在类中定义的不能修 ...

  4. 34、NCBI的子库名称

    转载:http://fhqdddddd.blog.163.com/blog/static/1869915420107188527933/ 参考资料: http://www.ncbi.nlm.nih.g ...

  5. 《精通Spring4.X企业应用开发实战》读后感第七章(AOP基础知识、jdk动态代理,CGLib动态代理)

  6. 想以编程为职业,现在正在看毕向东的java基础,接下来应该看什么视频,求前辈们指教。

    想以编程为职业,现在正在看毕向东的java基础,接下来应该看什么视频,求前辈们指教. https://zhidao.baidu.com/question/1368482680246425699.htm ...

  7. Java引用的四种状态

    强引用 用的最广,我们平时写代码时,new一个Object存放在堆内存,然后用一个引用指向它.它就是强引用. 如果一个对象具有强引用,那么垃圾回收期绝不会回收它,当内存空间不足,java虚拟机宁愿抛出 ...

  8. 2.XML实体注入漏洞攻与防

    XML实体注入基础 当允许引用外部实体时,通过构造恶意内容,可导致读取任意文件.执行系统命令.探测内网端口.攻击内网网站等危害. 简单了解XML以后,我们知道要在XML中使用特殊字符,需要使用实体字符 ...

  9. 20169201 2016-2017-2 实验二《Java面向对象程序设计》

    实验一:程序设计中临时变量的使用 代码托管 1.删除数组中的元素5 for(int i = 4; i < arr.length - 1; i ++){ arr[i] = arr[i + 1]; ...

  10. CocoaPods常用操作命令

    查看镜像: gem sources -l 删除镜像 gem sources --remove https://rubygems.org/ 添加镜像 gem sources -a https://gem ...