Java实现WebSocket服务
一、使用Tomcat提供的WebSocket库
Java可以使用Tomcat提供的WebSocket库接口实现WebSocket服务,代码编写也非常的简单。现在的H5联网游戏基本上都是使用WebSocket协议,基于长连接,服务器可以主动推送消息,而不是传统的网页采用客户端轮询的方式获取服务器的消息。下面给出简单使用Tomcat的WebSocket服务的基本代码结构。
1 @ServerEndpoint("/webSocket")
2 public class WebSocket {
3 @OnOpen
4 public void onOpen(Session session) throws IOException{
5 logger.debug("新连接");
6 }
7 @OnClose
8 public void onClose(){
9 logger.debug("连接关闭");
10 }
11 @OnMessage
12 public void onMessage(String message, Session session) throws IOException {
13 logger.debug("收到消息");
14 }
15 @OnError
16 public void onError(Session session, Throwable error){
17 error.printStackTrace();
18 }
19 }
二、WebSocket协议的整个流程
1. 基于TCP协议
WebSocket本质是基于TCP协议的,采用Java编写WebSocket服务时可以使用NIO或者AIO实现高并发的服务。
2. 握手过程
客户端采用TCP协议连接服务器指定端口后,首先需要发送一条HTTP的握手协议
GET /web HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 127.0.0.1:8001
Origin: http://127.0.0.1:8001
Sec-WebSocket-Key: hj0eNqbhE/A0GkBXDRrYYw==
Sec-WebSocket-Version: 13
请求的头里面必须包含以下内容:
1. Connection 其值为Upgrade,表示升级协议
2. Upgrade 其值为websocket,表示升级为WebSocket协议
3. Sec-WebSocket-Key 客户端发送给服务器的密钥,用于标识每个客户端,其值是16位的随机base64编码。
4. Sec-WebSocket-Version WebSocket的协议版本
服务器收到这条协议验证成功后进行协议升级,并且不会关闭Socket连接,并发送给客户端响应升级握手成功的HTTP协议包。
HTTP/1.1 101 Switching Protocols
Content-Length: 0
Upgrade: websocket
Sec-Websocket-Accept: ZEs+c+VBk8Aj01+wJGN7Y15796g=
Connection: Upgrade
Date: Wed, 21 Jun 2017 03:29:14 GMT
响应的协议包里面,首先是101的状态码,更换协议;其中最重要的就是Sec-WebSocket-Accept字段。其值是通过客户端的Key加上固定的"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"密钥,通过采用16位的base64编码后发送给客户端验证,如果客户端也验证成功就表示握手完成。
1 String acc = secKey + WEBSOCK_MAGIC_TAG;
2 MessageDigest sh1 = MessageDigest.getInstance("SHA1");
3 String key = Base64.getEncoder().encodeToString(sh1.digest(acc.getBytes()));
3. 数据的读写
握手成功后就可以进行数据发送和读取,WebSocket的数据可以是二进制或者纯文本。每次读取和发送数据需要打包成帧数据,需要按照其标准的格式进行发送或读取才能够正常的进行数据通信。

上图就是帧数据的结构图,解析帧数据的代码如下,由于是摘录的部分代码,所以只能作为理解和参考,不可直接使用。
1 protected WebSocketFrameData ParseFrame(NetPacketBuffer bytes){
2 bytes.mark();
3 WebSocketFrameData frame = new WebSocketFrameData();
4 int opData = bytes.readByte();
5 frame.UnPackOpCodeHeader(opData); // 第一步
6 int length = frame.UnPackMaskHeader(bytes.readByte()); // 第二步
7 // 读取长度
8 if (length == 126) {
9 length = bytes.readShort();
10 } else if (length == 127){
11 length = (int) bytes.readInt64();
12 }
13 // 数据不足,进来的是半包
14 if(length + 4 > bytes.remaining()){
15 bytes.reset(); //
16 return null;
17 }
18 // 读取mask if frame.mMasked
19 byte[] masks = new byte[4]; // 第三步
20 for (int i = 0; i < 4; i++) {
21 masks[i] = (byte) bytes.readByte();
22 }
23 frame.mLength = length;
24 frame.mData = bytes.readMulitBytes(length);
25 frame.MaskData(masks); // 第四步
26 return frame;
27 }
上面代码中第一步是解析出当前帧是否是最后帧mFin标记、操作码mOpCode,采用位处理,具体的实现如下。
1 public void UnPackOpCodeHeader(int opData){
2 mRsv1 = (opData & 64) == 64;
3 mRsv2 = (opData & 32) == 32;
4 mRsv3 = (opData & 16) == 16;
5
6 mFin = (opData & 128) == 128;
7 mOpCode = (opData & 15);
8 }
第二步在读取长度前,先解析当前帧是否有采用Mask掩码加密处理,并且里面有可能包含整个帧的长度信息,具体看上面的判断代码。
1 public int UnPackMaskHeader(int mkData){
2 mMasked = (mkData & 128) == 128;
3 return (mkData & 127); // 这里返回的是长度信息
4 }
接下来就是读取Mask内容,注意只有客户端发送给服务端时需要采用Mask对数据做处理,服务端发送给客户端时不需要做处理。最后通过Mask掩码解析出真实数据。
1 public void MaskData(byte[] masks){
2 if (!mMasked or masks.length == 0) return ;
3 for (int i = 0; i < mLength; i++) {
4 mData[i] = (byte) (mData[i] ^ masks[i % 4]);
5 }
6 }
以上就解析出单帧的数据,帧数据可以分为消息数据(细分为文本数据和二进制数据)、PING包、PONG包、CLOSE包、CONTINUATION包(数据未发送完成包)。而且帧数据又有mFin标记数据是否完整,否则需要将多个帧数据合成一个完整的消息数据。
1 // 读取帧数据,可能存在多帧数据,因此需要手动拆分
2 WebSocketFrameData frame = ParseFrame(mCachePacket);
3 if(frame == null){
4 break; // 说明数据不完整,暂不处理。
5 }
6 // 不完整的帧的时候,只有第一帧会标记帧的类型
7 opCode = opCode == -1? frame.mOpCode: opCode;
8 mCacheFrame.append(frame.mData, 0, frame.mLength);
9 if(!frame.mFin) // 非完整的数据不处理。
10 {
11 continue;
12 }
13 // 处理完整的数据
14 switch(opCode)
15 {
16 case WebSocketFrameData.OP_TEXT:
17 case WebSocketFrameData.OP_BINARY:
18 mCacheFrame.flip();
19 this.OnMessage(mCacheFrame, opCode);
20 break;
21 case WebSocketFrameData.OP_PING:
22 this.OnPing(mCacheFrame);
23 break;
24 case WebSocketFrameData.OP_PONG:
25 this.OnPong(mCacheFrame);
26 break;
27 case WebSocketFrameData.OP_CLOSE:
28 this.OnClosed();
29 break;
30 case WebSocketFrameData.OP_CONTINUATION:
31 this.Close();
32 break;
33 }
34 opCode = -1;
35 mCacheFrame.clear();
读取整个客户端的协议数据流程就已经完成了,服务端发送回去的数据就只需要注意两点:
1. 大的数据包需要分帧数据发送。
2. 不需要采用Mask掩码加密,因此Mask位置设置为0,并且不写入掩码数据。
三、最后
WebSocket协议已经在H5的游戏中使用了,学习有助于以后工作中的使用.文章来自我的公众号,大家如果有兴趣可以关注,具体扫描关注下图。

Java实现WebSocket服务的更多相关文章
- Java和WebSocket开发网页聊天室
小编心语:咳咳咳,今天又是聊天室,到现在为止小编已经分享了不下两个了,这一次跟之前的又不大相同,这一次是网页聊天室,具体怎么着,还请各位看官往下看~ Java和WebSocket开发网页聊天室 一.项 ...
- Java用WebSocket + tail命令实现Web实时日志
原文:http://blog.csdn.net/xiao__gui/article/details/50041673 在Linux操作系统中,经常需要查看日志文件的实时输出内容,通常会使用tail - ...
- 使用websocket-sharp来创建c#版本的websocket服务
当前有一个需求,需要网页端调用扫描仪,javascript不具备调用能力,因此需要在机器上提供一个ws服务给前端网页调用扫描仪.而扫描仪有一个c#版本的API,因此需要寻找一个c#的websocket ...
- java中websocket的应用
在上一篇文章中,笔者简要介绍了websocket的应用场景及优点,戳这里 这篇文章主要来介绍一下在java项目中,特别是java web项目中websocket的应用. 场景:我做了一个商城系统,跟大 ...
- 基于Java的WebSocket推送
WebSocket的主动推送 关于消息推送,现在的解决方案如轮询.长连接或者短连接,当然还有其他的一些技术框架,有的是客户端直接去服务端拿数据. 其实推送推送主要讲的是一个推的概念,WebSocket ...
- java 实现websocket
最近了解了下websocket和socket这个东西,说不得不来说下为何要使用 WebSocket ,和为何不用http. 为何需要WebSocket ? HTTP 协议是一种无状态的.无连接的.单向 ...
- java集成WebSocket向指定用户发送消息
一.WebSocket简单介绍 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通 ...
- Java后端WebSocket的Tomcat实现(转载)
一.WebSocket简单介绍 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通 ...
- js使用WebSocket,java使用WebSocket
js使用WebSocket,java使用WebSocket 创建java服务端代码 import java.net.InetSocketAddress; import org.java_websock ...
随机推荐
- 盘点linux操作系统中的10条性能调优命令,一文搞懂Linux系统调优
原文链接:猛戳这里 性能调优一直是运维工程师最重要的工作之一,如果您所在的生产环境中遇到了系统响应速度慢,硬盘IO吞吐量异常,数据处理速度低于预期值的情况,又或者如CPU.内存.硬盘.网络等系统资源长 ...
- 深入理解C++11 阅读笔记
二 保证稳定性和兼容性保持与C99兼容 预定义宏 C99语言标准增加的一些预定义宏,C++11同样增加了对这些宏的支持 __func__预定义标识符 功能是返回所在函数的名字,在C++11中,标准甚至 ...
- Anaconda3中的python安装新模块
1.确认安装位置:D:\Anaconda3 2.进入: D:\Anaconda3\Scripts 3.pip install -i https://pypi.tuna.tsinghua.edu.cn/ ...
- Python图表库Matplotlib 组成部分介绍
图表有很多个组成部分,例如标题.x/y轴名称.大刻度小刻度.线条.数据点.注释说明等等. 我们来看官方给的图,图中标出了各个部分的英文名称 Matplotlib提供了很多api,开发者可根据需求定制图 ...
- 在不受支持的 Mac 上安装 macOS Monterey 12
请注意,当前为 Beta 版,后续会及时更新. 请访问原文链接:https://sysin.org/blog/install-macos-12-on-unsupported-mac/,查看最新版.原创 ...
- Beam Search快速理解及代码解析(上)
Beam Search 简单介绍一下在文本生成任务中常用的解码策略Beam Search(集束搜索). 生成式任务相比普通的分类.tagging等NLP任务会复杂不少.在生成的时候,模型的输出是一个时 ...
- Python Unittest简明教程
1 概述 单元测试框架是一种软件测试方法,通过来测试源代码中的各个单元,例如类,方法等,以确定它们是否符合要求.直观上来说,可以将单元视为最小的可测试部分.单元测试是程序员在开发过程中创建的短代码片段 ...
- tomcat默认端口
关于tomcat默认端口为8080: 网页浏览器的默认端口为80.
- odoo中接口开发
文章参考:https://blog.csdn.net/qq_33472765/article/details/81913627案例0000001接口调用请求说明:https请求方式:GET(请使用ht ...
- 第三十二篇 -- CreateFile、ReadFile、WriteFile
一.CreateFile 这是一个多功能的函数,可打开或创建文件或者I/O设备,并返回可访问的句柄:控制台,通信资源,目录(只读打开),磁盘驱动器,文件,邮槽,管道. 函数原型: HANDLE WIN ...