一款基于Netty开发的WebSocket服务器
一款基于Netty开发的WebSocket服务器
这是一款基于Netty框架开发的服务端,通信协议为WebSocket。主要用于Java后台服务器向浏览器进行消息推送。
需求
对于一个Web项目而言,客户端一般均为各种各样的浏览器,如何从后端服务器向浏览器客户端进行消息推送,便成了一个棘手的问题,好在在HTTP1.1之后,HTTP可以支持长连接,由此,我在Netty框架的基础上开发了这个WebSocket服务端。
当然,你依旧可以下载源码进行测试,集成,二次开发等等。
环境
- Intellij IDEA2018
- JDK 1.8
- 插件:Simple WebSocket Client 0.1.3
- FireFox Quantum 60.0.1 (64 位)
- Google Chrome 67.0.3396.99(正式版本)(64 位)
运行结果
请看下图:
实现步骤及源码
WebSocket
WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。 WebSocket通信协议于2011年被IETF定为标准RFC 6455,WebSocketAPI被W3C定为标准。 在WebSocket API中,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
目录结构
核心类讲解
- WebSocketChildHandler
- WebSocketServerHandler
WebSocketChildHandler
这个类的主要作用就是为Netty的通道注册事件。其核心代码见下,其中webSocketUrl就是客户端与服务端进行连接的请求路径,我将其写入了配置文件,交由Spring管理,以注入的方式传递到WebSocketServerHandler中。
ChannelPipeline pipeline = socketChannel.pipeline();
// 将请求与应答消息编码或者解码为HTTP消息
pipeline.addLast("http-codec", new HttpServerCodec());
// 将http消息的多个部分组合成一条完整的HTTP消息
pipeline.addLast("aggregator", new HttpObjectAggregator(HttpObjectConstant.MAX_CONTENT_LENGTH));
// 向客户端发送HTML5文件。主要用于支持浏览器和服务端进行WebSocket通信
pipeline.addLast("http-chunked", new ChunkedWriteHandler());
// 服务端Handler
pipeline.addLast("handler", new WebSocketServerHandler(webSocketUrl));
WebSocketServerHandler
这个类是真正的核心类,这个类的主要功能为:
- 进行第一次握手
- 对消息进行处理
- 可以实现点对点通信
- 可以实现广播功能
- 可以实现点对端通信
/**
* 接收客户端发送的消息
*
* @param channelHandlerContext ChannelHandlerContext
* @param receiveMessage 消息
*/
@Override
protected void messageReceived(ChannelHandlerContext channelHandlerContext, Object receiveMessage) throws Exception {
// 传统http接入 第一次需要使用http建立握手
if (receiveMessage instanceof FullHttpRequest) {
FullHttpRequest fullHttpRequest = (FullHttpRequest) receiveMessage;
LOGGER.info("├ [握手]: {}", fullHttpRequest.uri());
// 握手
handlerHttpRequest(channelHandlerContext, fullHttpRequest);
// 发送连接成功给客户端
channelHandlerContext.channel().write(new TextWebSocketFrame("连接成功"));
}
// WebSocket接入
else if (receiveMessage instanceof WebSocketFrame) {
WebSocketFrame webSocketFrame = (WebSocketFrame) receiveMessage;
handlerWebSocketFrame(channelHandlerContext, webSocketFrame);
}
}
/**
* 第一次握手
*
* @param channelHandlerContext channelHandlerContext
* @param req 请求
*/
private void handlerHttpRequest(ChannelHandlerContext channelHandlerContext, FullHttpRequest req) {
// 构造握手响应返回,本机测试
WebSocketServerHandshakerFactory wsFactory
= new WebSocketServerHandshakerFactory(webSocketUrl, Constant.NULL, Constant.FALSE);
// region 从连接路径中截取连接用户名
String uri = req.uri();
int i = uri.lastIndexOf("/");
String userName = uri.substring(i + 1, uri.length());
// endregion
Channel connectChannel = channelHandlerContext.channel();
// 加入在线用户
WebSocketUsers.put(userName, connectChannel);
socketServerHandShaker = wsFactory.newHandshaker(req);
if (socketServerHandShaker == null) {
// 发送版本错误
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(connectChannel);
} else {
// 握手响应
socketServerHandShaker.handshake(connectChannel, req);
}
}
/**
* webSocket处理逻辑
*
* @param channelHandlerContext channelHandlerContext
* @param frame webSocketFrame
*/
private void handlerWebSocketFrame(ChannelHandlerContext channelHandlerContext, WebSocketFrame frame) throws IOException {
Channel channel = channelHandlerContext.channel();
// region 判断是否是关闭链路的指令
if (frame instanceof CloseWebSocketFrame) {
LOGGER.info("├ 关闭与客户端[{}]链接", channel.remoteAddress());
socketServerHandShaker.close(channel, (CloseWebSocketFrame) frame.retain());
return;
}
// endregion
// region 判断是否是ping消息
if (frame instanceof PingWebSocketFrame) {
LOGGER.info("├ [Ping消息]");
channel.write(new PongWebSocketFrame(frame.content().retain()));
return;
}
// endregion
// region 纯文本消息
if (frame instanceof TextWebSocketFrame) {
String text = ((TextWebSocketFrame) frame).text();
LOGGER.info("├ [{} 接收到客户端的消息]: {}", new Date(), text);
channel.write(new TextWebSocketFrame(new Date() + " 服务器将你发的消息原样返回:" + text));
}
// endregion
// region 二进制消息 此处使用了MessagePack编解码方式
if (frame instanceof BinaryWebSocketFrame) {
BinaryWebSocketFrame binaryWebSocketFrame = (BinaryWebSocketFrame) frame;
ByteBuf content = binaryWebSocketFrame.content();
LOGGER.info("├ [二进制数据]:{}", content);
final int length = content.readableBytes();
final byte[] array = new byte[length];
content.getBytes(content.readerIndex(), array, 0, length);
MessagePack messagePack = new MessagePack();
WebSocketMessageEntity webSocketMessageEntity = messagePack.read(array, WebSocketMessageEntity.class);
LOGGER.info("├ [解码数据]: {}", webSocketMessageEntity);
WebSocketUsers.sendMessageToUser(webSocketMessageEntity.getAcceptName(), webSocketMessageEntity.getContent());
}
// endregion
}
至此,服务端算是开发完成。但可以看出,服务端中仍有很大的发展空间,细心的同学可以发现我在第一次握手时,将Channel存储了起来,对于上述的三种情况也有简易的实现方案。
如果有必要,我也会将非浏览器客户端代码(非Js客户端)写成例子,共享出来。
一款基于Netty开发的WebSocket服务器
注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权
一款基于Netty开发的WebSocket服务器的更多相关文章
- (二)基于Netty的高性能Websocket服务器(netty-websocket-spring-boot)
@toc Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,对比于BIO(Blocking I/O,阻塞IO),他的并发性能得到了很大提高. 1.Netty为 ...
- Xsoup 是一款基于 Jsoup 开发的
Xsoup 是一款基于Jsoup 开发的,使用XPath抽取Html元素的工具.它被用于作者的爬虫框架 WebMagic 中,进行XPath 解析和抽取. 此次更新主要增加了一些XPath语法的支持. ...
- 基于线程开发一个FTP服务器
一,项目题目:基于线程开发一个FTP服务器 二,项目要求: 基本要求: 1.用户加密认证 2.允许同时多用户登录 3.每个用户有自己的家目录 ,且只能访问自己的家目录 4.对用户进行磁盘配 ...
- easy-im:一款基于netty的即时通讯系统
介绍 easy-im是面向开发者的一款轻量级.开箱即用的即时通讯系统,帮助开发者快速搭建消息推送等功能. 基于easy-im,你可以快速实现以下功能: + 聊天软件 + IoT消息推送 基本用法 项目 ...
- VBox 一款基于vue开发的音乐盒 序章
己基于vue写了一个 Mplayer, github地址:https://github.com/xiangwenhu/MPlaer, 演示地址:http://babydairy2017.cloudap ...
- Netty实现简单WebSocket服务器
本文参考<Netty权威指南>├── WebSocketServerHandler.java├── WebSocketServer.java└── wsclient.html packag ...
- 推荐一款基于 AI 开发的 IDE 插件,帮助提升编码效率
最近在浏览技术社区的时候,发现了一款神奇 IDE 插件,官网称可以利用 AI 帮助程序员写代码,一下子吸引了我的好奇心.赶紧下载下来使用一番,感觉确实蛮神奇,可以火速提升编程效率. 这款插件叫做 ai ...
- 一款基于 Android 开发的离线版的 MM 图片浏览 App
一款离线版的 MM 图片浏览 App,有点类似掌上百度的图片专栏应用.图片采用瀑布流展示方式,点击图片集,支持左右手势滑动切换图片:支持放大缩小功能. 实现功能:1)图片完全离线,不耗个人 GPRS ...
- 谈谈如何使用Netty开发实现高性能的RPC服务器
RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道 ...
随机推荐
- WQS二分题集
WQS二分,一种优化一类特殊DP的方法. 很多最优化问题都是形如“一堆物品,取与不取之间有限制.现在规定只取k个,最大/小化总收益”. 这类问题最自然的想法是:设f[i][j]表示前i个取j个的最大收 ...
- BZOJ 1633 [Usaco2007 Feb]The Cow Lexicon 牛的词典(单调DP)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1633 [题目大意] 给出一个字符串和一个字符串集, 问要删去多少个字符该字符串才可以被 ...
- 【构造】Codeforces Round #405 (rated, Div. 1, based on VK Cup 2017 Round 1) A. Bear and Different Names
如果某个位置i是Y,直接直到i+m-1为止填上新的数字. 如果是N,直接把a[i+m-1]填和a[i]相同即可,这样不影响其他段的答案. 当然如果前面没有过Y的话,都填上0就行了. #include& ...
- 【DP+树状数组】BZOJ1264-[AHOI2006]基因匹配Match
[题目大意] 给定n个数和两个长度为n*5的序列,两个序列中的数均有1..n组成,且1..n中每个数恰好出现5次,求两个序列的LCS. [思路] 预处理每个数字在a[i]中出现的五个位置.f[i]示以 ...
- bzoj 3728: PA2014Final Zarowki
3728: PA2014Final Zarowki Description 有n个房间和n盏灯,你需要在每个房间里放入一盏灯.每盏灯都有一定功率,每间房间都需要不少于一定功率的灯泡才可以完全照亮.你可 ...
- 基于jquery,bootstrap数据验证插件bootstrapValidator
bootstrap:能够增加兼容性的强大框架. 因为项目需要数据验证,看bootstrapValidator 还不错,就上手一直,完美兼容,话不多说. 需要引用css: bootstrap.min.c ...
- Android学习进阶路线导航线路(Android源码分享)
转 ...
- 【mybatis】mybatis多表联查,存在一对多关系的,实体中使用List作为字段接收查询结果的写法
实体如下: IntegralGoods 积分商品 IntegralGoodsImg 积分商品图片 ShelfLog 积分商品自动上架记录 IntegralGoods :IntegralGoodsIm ...
- fmri 分析数据 fsl & spm 两大平台比对
基于下面这份ppt:Comparing SPM and FSL, by lChris Rorden fsl & spm都是免费的,都很受欢迎.spm更受欢迎. 两者的区别在于何时利用norma ...
- 如何提高iOS开发能力
① 阅读技术博客 在现在这个年代,博客的风头早已被微博盖过.但是每天早上上班后的半小时,一定是打开博客,其中有讨论技术的,也有总结个人的相关经历,读完后肯定会有所收获.阅读博客,还有一个原因是技术博客 ...