一、使用Tomcat提供的WebSocket库 

Java可以使用Tomcat提供的WebSocket库接口实现WebSocket服务,代码编写也非常的简单。现在的H5联网游戏基本上都是使用WebSocket协议,基于长连接,服务器可以主动推送消息,而不是传统的网页采用客户端轮询的方式获取服务器的消息。下面给出简单使用TomcatWebSocket服务的基本代码结构。

 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服务的更多相关文章

  1. Java和WebSocket开发网页聊天室

    小编心语:咳咳咳,今天又是聊天室,到现在为止小编已经分享了不下两个了,这一次跟之前的又不大相同,这一次是网页聊天室,具体怎么着,还请各位看官往下看~ Java和WebSocket开发网页聊天室 一.项 ...

  2. Java用WebSocket + tail命令实现Web实时日志

    原文:http://blog.csdn.net/xiao__gui/article/details/50041673 在Linux操作系统中,经常需要查看日志文件的实时输出内容,通常会使用tail - ...

  3. 使用websocket-sharp来创建c#版本的websocket服务

    当前有一个需求,需要网页端调用扫描仪,javascript不具备调用能力,因此需要在机器上提供一个ws服务给前端网页调用扫描仪.而扫描仪有一个c#版本的API,因此需要寻找一个c#的websocket ...

  4. java中websocket的应用

    在上一篇文章中,笔者简要介绍了websocket的应用场景及优点,戳这里 这篇文章主要来介绍一下在java项目中,特别是java web项目中websocket的应用. 场景:我做了一个商城系统,跟大 ...

  5. 基于Java的WebSocket推送

    WebSocket的主动推送 关于消息推送,现在的解决方案如轮询.长连接或者短连接,当然还有其他的一些技术框架,有的是客户端直接去服务端拿数据. 其实推送推送主要讲的是一个推的概念,WebSocket ...

  6. java 实现websocket

    最近了解了下websocket和socket这个东西,说不得不来说下为何要使用 WebSocket ,和为何不用http. 为何需要WebSocket ? HTTP 协议是一种无状态的.无连接的.单向 ...

  7. java集成WebSocket向指定用户发送消息

    一.WebSocket简单介绍 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通 ...

  8. Java后端WebSocket的Tomcat实现(转载)

    一.WebSocket简单介绍 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通 ...

  9. js使用WebSocket,java使用WebSocket

    js使用WebSocket,java使用WebSocket 创建java服务端代码 import java.net.InetSocketAddress; import org.java_websock ...

随机推荐

  1. MySQL存储引擎——InnoDB和MyISAM的区别

    MySQL5.5后,默认存储引擎是InnoDB,5.5之前默认是MyISAM. InnoDB(事务性数据库引擎)和MyISAM的区别补充: InnoDB是聚集索引,数据结构是B+树,叶子节点存K-V, ...

  2. Linux | 浏览(切换)目录命令

    例出目录和文件 --> ls ls 命令是最常用的 Linux 命令之一,ls 是 list 的缩写,表示:列出 在 Linux 中 ls 命令用于列出文件和目录 一些常用的参数 ls -a # ...

  3. BPDU、Hybrid、MSTP

    BPDU.Hybrid.MSTP      一.BPDU         1)BPDU概述         2)BPDU类型         3)BPDU报文字段      二.Hybrid     ...

  4. 使用 VSCode 搭建 Flutter环境

    概述 编辑器使用 vscode,不再安装 Android Studio. 安装 Git 点击这里 下载并安装 Git 配置 Java 环境 下载和安装 JDK 点击下载 Java SE Develop ...

  5. Java基础00-Stream流34

    1. Stream流 Stream流 1.1 体验Stream流 代码示例: //需求:按照下面的要求完成集合的创建和遍历 public class StreamDemo { public stati ...

  6. clickhouse分布式集群

    一.环境准备: 主机 系统 应用 ip ckh-01 centos 8 jdk,zookeeper,clickhouse 192.168.205.190 ckh-02 centos 8 jdk,zoo ...

  7. lucene Hello World

    一个lucene创建索引和查找索引的样例: 创建索引: public class Indexer { private IndexWriter indexWriter; /** * 构造器实例化inde ...

  8. 深入理解javascript按值传递与按引用传递

    https://segmentfault.com/a/1190000012829900

  9. sql-1-准备

    一.准备工作 1.mysql安装和配环境 不要以exe文件安装,要下载压缩包安装 下载地址:https://dev.mysql.com/downloads/mysql 在系统path中加上bin目录 ...

  10. mysql免安装版下载及安装教程

    第一步:下载 下载地址:http://dev.mysql.com/downloads/mysql/ 点击图中红色箭头Archives,可以下载自己想要的mysql版本,如图: 下载后解压,放在自己想要 ...