目录
·前言
一、背景介绍
二、原理解析
1.连接过程(握手)
2.报文格式
三、代码示例
1.服务端代码
(1)TestAPI 类
(2)WebSocketConfig 类
2.客户端代码
3.代码演示
·结尾
·前言
从本篇文章开始,我就来与大家分享网页版五子棋项目的一个实现过程了,这个项目一共有以下几个核心模块:用户模块、匹配模块、对战模块,后面文章会按照顺序来对每个模块进行介绍,并且此项目用到的核心技术有:Spring/SpringBoot/SpringMVC、WebSocket、MySQL、MyBatis、HTML/CSS/JS/AJAX,本篇文章来介绍一下 WebSocket 协议的内容、原理及代码示例,下面就开始本篇文章的内容介绍。
一、背景介绍
WebSocket 是一种支持网页端与服务端保持长连接的消息推送机制,什么是消息推送机制呢?在我们传统的 web 程序中,都是属于“一问一答”的形式,如下图所示:
这种情况下,服务器是被动的一方,如果客户端不主动发送请求,服务器就不会主动给客户端响应,我们所要编写的五子棋程序使用这种形式就会出现下面的情况:
此时由于玩家2 没有向服务器发送请求,就会导致玩家2 收不到玩家1 的落子情况,消息推送机制就是可以让服务器主动给客户端发送数据,如果想使用传统“一问一答”的形式实现消息推送的效果也是可以做到,那就要采用“轮询”的方式,如下图所示:
此时,在玩家1 没有落子之前,玩家2 就会以一定的时间间隔不断的向服务器发起请求,来询问玩家1 有没有落子,很明显,使用这种轮询操作的体验效果与开销与询问的时间间隔有关,关系如下:
- 如果轮询时间间隔比较长:玩家1 落子之后,玩家2 不能及时获取到落子结果,对弈的体验性降低;
- 如果轮询时间间隔比较短:虽然及时性得到改善,但是玩家2 就要浪费更多的机器资源,比如:网络带宽。
为了更好的实现消息推送机制,在我们五子棋项目中,就引入了 WebSocket 协议,这是一种接近于 TCP 级别的通信方式,一旦建立好连接,客户端与服务器都可以主动向对方发送数据。
基于 WebSocket 来实现的消息推送在五子棋项目中就会起到下图的效果:
此时,玩家1 的落子请求发送给服务器后,服务器就会自动把响应推送给玩家1 与玩家2 。
二、原理解析
1.连接过程(握手)
在我们使用网页端想尝试和服务器建立 WebSocket 连接会经过以下几个步骤:
- 网页端先给服务器发送一个 HTTP 请求,请求中会带有特殊的 header(Connection:Upgrade、Upgrade:WebSocket)这个 header 就是告诉服务器,我们需要进行协议的升级;
- 如果服务器支持 WebSocket,就会返回一个特殊的 HTTP 响应,这个响应的状态码是 101 (切换协议);
- 客户端与服务器之间开始使用 WebSocket 来进行通信。
上述的具体过程可以如下图所示:
2.报文格式
WebSocket 的报文格式如下图所示:
WebSocket 也是一种应用层的协议,它的下层是基于 TCP 的,下面我来对上图的部分内容做一个简单介绍:
- FIN:当这里的内容为 1 时,表示要断开 WebSocket 连接;
- RSV1~3:这是保留位,一般为0;
- opcode:这是操作码,描述了当前 WebSocket 报文是什么类型,它可以描述当前报文是一个文本帧还是一个二进制帧,是一个 ping 帧还是一个 pong 帧;
- Payload length:这是表示当前数据报携带的数据载荷的长度,它是一个可边长的字段,一个 WebSocket 的数据报能承载的载荷长度是非常长的;
- Payload data:这是表示当前 WebSocket 实际报文要传输的数据载荷。
三、代码示例
下面我来实现一个简单的 WebSocket 代码,这里服务端的代码由 Java 来编写,客户端的代码由 JS 来编写。
1.服务端代码
编写服务端代码的流程如下:
- 创建出一个 Spring 项目,在 Spring 中内置了 WebSocket 可以直接使用;
- 创建 api.TestAPI 类,这个类用来处理 WebSocket 请求,并返回响应;
- 创建 config.WebSocketConfig 类,这个类用来配置请求路径和 TextWebSocketHandler 之间的对应关系。
代码结构如下所示:
(1)TestAPI 类
在这里,继承了 TextWebSocketHandler 这个类,我们需要重写里面的几个方法,如下图所示的选中方法: 关于重写的这四个方法都是什么作用,我在代码的注释中会进行详细的标注,那么关于 TestAPI 这个类的全部代码及注释如下所示:
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
// 添加 @Component 注解,交给 Spring 管理
@Component
public class TestAPI extends TextWebSocketHandler {
@Override
// 连接就绪后就会触发这个方法来告知连接成功
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("连接成功");
}
@Override
// 客户端/服务器 给 服务器/客户端 发送信息通过这个方法就可以接收到信息
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("收到消息" + message.getPayload());
// 让服务器收到消息之后原封不动的返回
session.sendMessage(message);
}
@Override
// 传输出现异常就会触发这个方法
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
System.out.println("连接异常");
}
@Override
// 如果客户端/服务器关闭连接就会执行这个方法
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("连接关闭");
}
}
(2)WebSocketConfig 类
在这里,实现了 WebSocketConfigurer 接口,用于 WebSocket 的相关配置,代码及介绍注释如下所示:
import com.example.springgobang.aip.TestAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
// @EnableWebSocket 注解用来告诉 Spring 这是配置 WebSocket 的类
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
// 自动注入
@Autowired
private TestAPI testAPI;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 当客户端连接 /test 这样的路径后就会触发 testAPI 进而调用其内部的方法
registry.addHandler(testAPI,"/test");
}
}
2.客户端代码
这里我们编写一个 html 页面来与服务器代码建立 WebSocket 连接,TestAPI.html 页面代码及介绍如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--输入框-->
<input type="text" id="message">
<!--按钮-->
<button id="sendButton">发送</button>
<script>
// 创建 WebSocket 实例, 要保证这里的路径与配置的路径一致 /test
let webSocket = new WebSocket("ws://127.0.0.1:8080/test");
// 连接成功后会调用这个函数
webSocket.onopen = function() {
console.log("连接成功");
}
// 收到信息后会调用这个函数
webSocket.onmessage = function(e) {
console.log("收到消息: " + e.data);
}
// 连接异常时会执行这个函数
webSocket.onerror = function() {
console.log("连接异常");
}
// 连接断开后会调用这个函数
webSocket.onclose = function() {
console.log("连接关闭");
}
// 实现点击按钮后,通过 WebSocket 发送请求
let messageInput = document.querySelector('#message');
let sendButton = document.querySelector('#sendButton');
sendButton.onclick = function() {
console.log("发送消息: " + messageInput.value);
// send 函数就会把输入框中的内容发送出去
webSocket.send(messageInput.value);
}
</script>
</body>
</html>
3.代码演示
此时,关于 WebSocket 的演示代码就编写完毕了,下面我们来启动程序,在电脑的浏览器上输入:http://127.0.0.1:8080/TestAPI.html 来访问 TestAPI.html 页面,然后鼠标右键点击检查,或者按键盘上的 F12 按钮,进入控制台观察日志信息,然后在输入框中输入数据点击发送就可以观察到效果,具体的效果演示如下图所示:
此时在服务端断开连接(停止程序),结果如下图所示:
通过上述的运行结果可以看出来,服务端可以随时调用 session.sendMessage() 方法来给客户端发送响应,从而可以实现消息推送这样的机制。
·结尾
文章到此就要结束了,本篇文章介绍了 WebSocket 协议的一些基础原理,并用 WebSocket 进行了简单的网络编程,来演示了消息推送的效果,这是五子棋项目重要逻辑的实现依据,使用 WebSocket 就可以保证对弈双方可以实时在页面上观察到对方的落子情况,介绍完 WebSocket 之后就要开始五子棋项目的正式编写了,如果对本篇文章的内容有所疑惑,欢迎在评论区进行留言,我们下一篇文章再见吧~~~