一款基于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)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道 ...
随机推荐
- 关于oracle存储过程需要注意的问题
在使用oracle存储过程时,有一些需要注意的地方,下面就来总结一下. 1.在oracle的存储过程中,数据表别名不能加as 也许是为了区分存储过程中的as,怕与过程中的as冲突. 如: select ...
- JZYZOJ 1382 光棍组织 状压dp
http://172.20.6.3/Problem_Show.asp?id=1382 水得过分了,本来以为要用lzx学长的写法写,抱着试试看的想法写了个特暴力的dp+dfs,过了,真是... ...
- BZOJ 2733 [HNOI2012]永无乡(启发式合并+Treap+并查集)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2733 [题目大意] 给出n个点,每个点都有自己的重要度,现在有连边操作和查询操作, 查 ...
- BZOJ 4884 [Lydsy2017年5月月赛]太空猫(单调DP)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=4884 [题目大意] 太空猫(SpaceCat)是一款画面精致.玩法有趣的休闲游戏, 你 ...
- Token-Pasting Operator (##) and Stringizing Operator (#)
Token-Pasting Operator (##)The double-number-sign or “token-pasting” operator (##), which is sometim ...
- Activity(活动)生命周期(1)--返回栈
Android是使用任务(task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称为返回栈(Back stack).栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的 ...
- bzoj 1650: [Usaco2006 Dec]River Hopscotch 跳石子
1650: [Usaco2006 Dec]River Hopscotch 跳石子 Time Limit: 5 Sec Memory Limit: 64 MB Description Every ye ...
- codevs 1966 乘法游戏
1966 乘法游戏 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 乘法游戏是在一行牌上进行的.每一张牌包括了一个正整数.在每 ...
- Codeforces Round #344 (Div. 2) C. Report 其他
C. Report 题目连接: http://www.codeforces.com/contest/631/problem/C Description Each month Blake gets th ...
- php根据汉字获取拼音(php基于拼音搜索实现原理)
php根据汉字获取拼音(php基于拼音搜索实现原理) 代码一:获取字符串汉字首字母,兼容GBK和UTF-8 <?php function getfirstchar($s0){ //获取单个汉 ...