代码地址如下:
http://www.demodashi.com/demo/13577.html

一款基于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服务器

代码地址如下:
http://www.demodashi.com/demo/13577.html

注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

一款基于Netty开发的WebSocket服务器的更多相关文章

  1. (二)基于Netty的高性能Websocket服务器(netty-websocket-spring-boot)

    @toc Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,对比于BIO(Blocking I/O,阻塞IO),他的并发性能得到了很大提高. 1.Netty为 ...

  2. Xsoup 是一款基于 Jsoup 开发的

    Xsoup 是一款基于Jsoup 开发的,使用XPath抽取Html元素的工具.它被用于作者的爬虫框架 WebMagic 中,进行XPath 解析和抽取. 此次更新主要增加了一些XPath语法的支持. ...

  3. 基于线程开发一个FTP服务器

    一,项目题目:基于线程开发一个FTP服务器 二,项目要求: 基本要求: 1.用户加密认证   2.允许同时多用户登录   3.每个用户有自己的家目录 ,且只能访问自己的家目录   4.对用户进行磁盘配 ...

  4. easy-im:一款基于netty的即时通讯系统

    介绍 easy-im是面向开发者的一款轻量级.开箱即用的即时通讯系统,帮助开发者快速搭建消息推送等功能. 基于easy-im,你可以快速实现以下功能: + 聊天软件 + IoT消息推送 基本用法 项目 ...

  5. VBox 一款基于vue开发的音乐盒 序章

    己基于vue写了一个 Mplayer, github地址:https://github.com/xiangwenhu/MPlaer, 演示地址:http://babydairy2017.cloudap ...

  6. Netty实现简单WebSocket服务器

    本文参考<Netty权威指南>├── WebSocketServerHandler.java├── WebSocketServer.java└── wsclient.html packag ...

  7. 推荐一款基于 AI 开发的 IDE 插件,帮助提升编码效率

    最近在浏览技术社区的时候,发现了一款神奇 IDE 插件,官网称可以利用 AI 帮助程序员写代码,一下子吸引了我的好奇心.赶紧下载下来使用一番,感觉确实蛮神奇,可以火速提升编程效率. 这款插件叫做 ai ...

  8. 一款基于 Android 开发的离线版的 MM 图片浏览 App

    一款离线版的 MM 图片浏览 App,有点类似掌上百度的图片专栏应用.图片采用瀑布流展示方式,点击图片集,支持左右手势滑动切换图片:支持放大缩小功能. 实现功能:1)图片完全离线,不耗个人 GPRS ...

  9. 谈谈如何使用Netty开发实现高性能的RPC服务器

    RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道 ...

随机推荐

  1. 大素数判断(miller-Rabin测试)

    题目:PolandBall and Hypothesis A. PolandBall and Hypothesis time limit per test 2 seconds memory limit ...

  2. python 写文件write(string), writelines(list) ,读文件

    read()方法用于直接读取字节到字符串中,可以接参数给定最多读取的字节数,如果没有给定,则文件读取到末尾. readline()方法读取打开文件的一行(读取下个行结束符之前的所有字节),然后整行,包 ...

  3. nyoj(表达式求值)

    描述 ACM队的mdd想做一个计算器,但是,他要做的不仅仅是一计算一个A+B的计算器,他想实现随便输入一个表达式都能求出它的值的计算器,现在请你帮助他来实现这个计算器吧. 比如输入:"1+2 ...

  4. 【矩阵乘法】CDOJ1610 黑红梅方

    考虑用4^n-不存在连续4个相同的. f(i,j,k,l)表示以i为结尾的序列,最后三位分别是j,k,l时的方案. 可以转移,写一个64*64的转移矩阵. 貌似可以优化?……未完待续. #includ ...

  5. jsp ajax实例讲解

    下面介绍JSP前台表单内容通过Ajax异步提交到后台Servlet进行校验(校验方式多种,包括提取数据库信息,校验用户名是否重复等),异步在JSP表单页面显示校验结果信息的基本过程. 一.说明: 1. ...

  6. python实现多播数据的发送和接收

    在项目中,YS私有协议用到多播技术,在验证其安全特性时用到python去发送多播包,在此做个记录. 多播服务器用于向多播组发送多播数据包,其实现代码如下: #coding:utf-, import s ...

  7. 多线程--Task,等待用户输入AutoResetEvent

    上一篇文章:.NET:如何让线程支持超时?已经说明目前微软主推的多线程方案是task: 注意:Task最好引用.NET4.5. 4.0也行,但不成熟.Thread引用2.0就够了. 1.通过构造函数创 ...

  8. spring的@Async异步使用

    pring的@Async功能,用的时候一定要注意: 1.异步方法和调用类不要在同一个类中. 2.xml里需要加入这一行 <task:annotation-driven/> 下面的可以直接粘 ...

  9. jQuery旋转插件—rotate

    jQuery旋转插件,支持Internet Explorer 6.0+ .Firefox 2.0 .Safari 3 .Opera 9 .Google Chrome rotate(angle) 正值表 ...

  10. Oracle SQL执行缓慢的原因以及解决方案

     以下的文章抓哟是对Oracle SQL执行缓慢的原因的分析,如果Oracle数据库中的某张表的相关数据已是2亿多时,同时此表也创建了相关的4个独立的相关索引.由于业务方面的需要,每天需分两次向此表中 ...