初识websocket协议

websocket协议是什么

WebSocket是HTML5中的协议,支持持久连接,http协议不支持持久性连接(长连接,循环连接的不算)。Http1.0和HTTP1.1都不支持持久性的链接,HTTP1.1中的keep-alive,将多个http请求合并为1个,也就是不用再去重复建立TCP连接了。

WebSocket是基于TCP的应用层协议,用于在C/S架构的应用中实现双向通信,
需要特别注意的是:虽然WebSocket协议在建立连接时会使用HTTP协议,但这并意味着WebSocket协议是基于HTTP协议实现的。
在这里插入图片描述

和http协议的区别是什么

HTTP的生命周期通过Request来界定,也就是Request一个Response,那么在Http1.0协议中,这次Http请求就结束了。在Http1.1中进行了改进,是有一个connection:Keep-alive字段,也就是说,在一个Http连接中,可以发送多个Request,接收多个Response。但是必须记住,在Http中一个Request只能对应有一个Response,而且这个Response是被动的,不能主动发起。

WebSocket是基于Http协议的,或者说借用了Http协议来完成一部分握手,在握手阶段与Http是相同的。我们来看一个websocket握手协议的实现,基本是2个属性,upgrade,connection

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

多了下面2个属性:

Upgrade:webSocket
Connection:Upgrade

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

为什么要使用websocket

WebSocket 协议,实现了客户端和服务端双向通信的能力。介绍 WebSocket 之前,还是让我们先了解下轮询实现推送的方式。

Ajax短轮询(Polling)

短轮询的实现思路就是浏览器端每隔几秒钟向服务器端发送 HTTP 请求,服务端在收到请求后,不论是否有数据更新,都直接进行响应。在服务端响应完成,就会关闭这个 TCP 连接,代码实现也最简单,就是利用 XHR, 通过 setInterval 定时向后端发送请求,以获取最新的数据。

setInterval(function() {
  fetch(url).then((res) => {
      // success code
  })
}, 3000);

优点:实现简单。
缺点:会造成数据在一小段时间内不同步和大量无效的请求,安全性差、浪费资源。

长轮询(Long-Polling)

客户端发送请求后服务器端不会立即返回数据,服务器端会阻塞请求连接不会立即断开,直到服务器端有数据更新或者是连接超时才返回,客户端才再次发出请求新建连接、如此反复从而获取最新数据。大致效果如下:
在这里插入图片描述

function async() {
    fetch(url).then((res) => {
    	async();
    	// success code
	}).catch(() => {
		// 超时
        async();
	})
}

优点:比 Polling 做了优化,有较好的时效性。
缺点:保持连接挂起会消耗资源,服务器没有返回有效数据,程序超时。

WebSocket

前面提到的短轮询(Polling)和长轮询(Long-Polling), 都是先由客户端发起 Ajax 请求,才能进行通信,走的是 HTTP 协议,服务器端无法主动向客户端推送信息。
当出现类似体育赛事、聊天室、实时位置之类的场景时,轮询就显得十分低效和浪费资源,因为要不断发送请求,连接服务器。WebSocket 的出现,让服务器端可以主动向客户端发送信息,使得浏览器具备了实时双向通信的能力。

怎么使用websocket

WebSocket协议分为两部分:握手和数据传输

在握手建立连接的时候使用的是http协议,连接之后通过Upgrade:websocket来改为websocket协议。

通信原理

当客户端要和服务端建立 WebSocket 连接时,在客户端和服务器的握手过程中,客户端首先会向服务端发送一个 HTTP 请求,包含一个 Upgrade 请求头来告知服务端客户端想要建立一个 WebSocket 连接。

let ws = new WebSocket('ws://localhost:9000');

类似于 HTTP 和 HTTPS,ws 相对应的也有 wss 用以建立安全连接,本地以 ws 为例。这时的请求头如下:

Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
Connection: Upgrade	// 表示该连接要升级协议
Cookie: _hjMinimizedPolls=358479; ts_uid=7852621249; CNZZDATA1259303436=1218855313-1548914234-%7C1564625892; csrfToken=DPb4RhmGQfPCZnYzUCCOOade; JSESSIONID=67376239124B4355F75F1FC87C059F8D; _hjid=3f7157b6-1aa0-4d5c-ab9a-45eab1e6941e; acw_tc=76b20ff415689655672128006e178b964c640d5a7952f7cb3c18ddf0064264
Host: localhost:9000
Origin: http://localhost:9000
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: 5fTJ1LTuh3RKjSJxydyifQ==		// 与响应头 Sec-WebSocket-Accept 相对应
Sec-WebSocket-Version: 13	// 表示 websocket 协议的版本
Upgrade: websocket	// 表示要升级到 websocket 协议
User-Agent: Mozilla/5.0 (Macintosh

响应头如下:

Connection: Upgrade
Sec-WebSocket-Accept: ZUip34t+bCjhkvxxwhmdEOyx9hE=
Upgrade: websocket

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"

在这里插入图片描述
据有如下方法:

close()
WebSocket.close() 方法关闭 WebSocket  连接或连接尝试(如果有的话)。 如果连接已经关闭,则此方法不执行任何操作。
send()
 WebSocket.send() 方法将需要通过 WebSocket 链接传输至服务器的数据排入队列,并根据所需要传输的data bytes的大小来增加 bufferedAmount的值 。若数据无法传输(例如数据需要缓存而缓冲区已满)时,套接字会自行关闭。

在这里插入图片描述

在客户端使用:

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);
};

在服务端使用WebSocket

<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-websocket-api</artifactId>
    <version>8.5.41</version>
</dependency>
@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的兼容性吧:
在这里插入图片描述

WebSocket.readyState

返回当前 WebSocket 的链接状态,只读。
0 (WebSocket.CONNECTING)
正在链接中

1 (WebSocket.OPEN)
已经链接并且可以通讯

2 (WebSocket.CLOSING)
连接正在关闭

3 (WebSocket.CLOSED)
连接已关闭或者没有链接成功

更多可以查看mdn文档:传送门here


版权声明:本文为qq_40409143原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。