WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。
——百度百科
什么是WebSocket
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
原理
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
连接过程
- 浏览器、服务器建立TCP连接,三次握手。这是通信的基础,传输控制层,若失败后续都不执行。
- TCP连接成功后,浏览器通过HTTP协议向服务器传送WebSocket支持的版本号等信息。(开始前的HTTP握手)
- 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据。
- 当收到了连接成功的消息后,通过TCP通道进行传输通信。
目的
现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
以下 API 用于创建 WebSocket 对象。
var Socket = new WebSocket(url, [protocol] );
以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。
WebSocket与HTTP的关系
相同点
- 1. 都是一样基于TCP的,都是可靠性传输协议。
- 2. 都是应用层协议。
不同点
- 1. WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息。HTTP是单向的。
- 2. WebSocket是需要握手进行建立连接的。
联系
WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的。
WebSocket 属性
以下是 WebSocket 对象的属性。假定我们使用了以上代码创建了 Socket 对象:
属性 | 描述 |
---|---|
Socket.readyState | 只读属性 readyState 表示连接状态,可以是以下值: 0 - 表示连接尚未建立。1. 表示连接已建立,可以进行通信。2. 表示连接正在进行关闭。 3. 表示连接已经关闭或者连接不能打开。 |
Socket.bufferedAmount | 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。 |
WebSocket 事件
以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:
事件 | 事件处理程序 | 描述 |
---|---|---|
open | Socket.onopen | 连接建立触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通信发生错误时触发 |
close | Socket.onclose | 连接关闭时触发 |
WebSocket 方法
以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:
方法 | 描述 |
---|---|
Socket.send() | 使用连接发送数据 |
Socket.close() | 关闭连接 |
WebSocket实例
WebSocket 协议本质上是一个基于 TCP 的协议。
为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
客户端的 HTML 和 JavaScript
注意:websocket地址:ws://{ip}:{端口}/{工程名}/{ServerEndpoint路径}}
地址使用localhost会出现如下错误:
WebSocket connection to 'ws://localhost:8080/aidTibet/websocket' failed: Error during WebSocket handshake: Unexpected response code: 302
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试WebSocket</title>
<script type="text/javascript">
function WebSocketTest()
{
if ("WebSocket" in window)
{
alert("您的浏览器支持WebScoket!")
// 打开一个web socket
var ws = new WebSocket("ws://172.20.241.171:8080/websocket")
ws.onopen = function(){
// web socket已连接上,使用 send() 发送数据
ws.send("发送数据")
alert("数据发送中...")
}
ws.onmessage = function(evt){
var received_msg = evt.data
alert("数据已接收..." + received_msg)
}
ws.onclose = function() {
// 关闭WebSocket
alert("连接已关闭...")
}
} else {
// 浏览器不支持 WebSocket
alert("您的浏览器不支持 WebSocket!")
}
}
</script>
</head>
<body>
<div>
<a href="javascript:WebSocketTest()">运行 WebSocket</a>
</div>
</body>
</html>
服务端
服务端使用SpringBoot结合WebSocket的方式实现。
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- 新建WebSocket配置类
@Configuration
public class WebsocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
- 新建WebSocket服务端
@ServerEndpoint("/websocket")
@Component
@Slf4j
public class MyWebsocketServer {
/**
* 存放所有在线的客户端
*/
private static Map<String, Session> clients = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session) {
log.info("有新的客户端连接了: {}", session.getId());
//将新用户存入在线的组
clients.put(session.getId(), session);
}
/**
* 客户端关闭
* @param session session
*/
@OnClose
public void onClose(Session session) {
log.info("有用户断开了, id为:{}", session.getId());
//将掉线的用户移除在线的组里
clients.remove(session.getId());
}
/**
* 发生错误
* @param throwable e
*/
@OnError
public void onError(Throwable throwable) {
throwable.printStackTrace();
}
/**
* 收到客户端发来消息
* @param message 消息对象
*/
@OnMessage
public void onMessage(String message) {
log.info("服务端收到客户端发来的消息: {}", message);
this.sendAll(message);
}
/**
* 群发消息
* @param message 消息内容
*/
private void sendAll(String message) {
for (Map.Entry<String, Session> sessionEntry : clients.entrySet()) {
sessionEntry.getValue().getAsyncRemote().sendText(message);
}
}
}
启动后,使用客户端发送数据:
参考:https://www.jianshu.com/p/8c4983a3ca2e(包含创建连接时传递userId已区分不同客户端)