[Netty实践] 简单WebSocket服务实现

目录

一、介绍

二、依赖导入

三、基础类准备

四、Handler实现

五、WebSocketChannelInitializer实现

六、WebSocketServer实现

七、前端实现

八、测试

九、参考链接


一、介绍

关于WebSocket此处不进行过多介绍,本章主要着重通过Netty实现WebSocket通信服务端,并且实现一个简单的通过网页进行聊天的功能。

讲到WebSocket,这里简单介绍一下为什么要使用WebSocket。以往我们通过网页与服务器进行交互时,都是通过发起一个http/https请求,该请求是无状态的,发送请求后,等待获取服务器返回的结果之后,这次请求就结束了,客户端与服务端就断开了。如果此时服务端想向客户端推送消息的话,由于连接已经断开,服务端无法进行消息推送,此时可以通过使用WebSocket进行客户端与服务端建立长连接,当服务端想向客户端推送消息时,就可以向客户端进行消息推送了。

接下来就进行代码实现。

二、依赖导入

<dependencies><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.101.Final</version></dependency><!--添加tomcat依赖模块.--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><version>2.0.4.RELEASE</version></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.41</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version></dependency></dependencies>

三、基础类准备

1、ChannelManager, 用于管理Channel等相关信息

public class ChannelManager {public final static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);public final static Map<Channel, WebSocketServerHandshaker> handShakerMap = new ConcurrentHashMap<>();}

2、WebSocketRequestMessage,用于将客户端传递过来的json字符串转换该类的对象

@Data
@ToString
public class WebSocketRequestMessage {public String user;public String type;public String message;}

3、WebSocketResponseMessage,用于返回给客户端的类型数据

public class WebSocketResponseMessage {public String user;public String type;public String message;public String date;public WebSocketResponseMessage(WebSocketRequestMessage webSocketRequestMessage) {this.user = webSocketRequestMessage.getUser();this.type = webSocketRequestMessage.getType();this.message = webSocketRequestMessage.getMessage();this.date = new Date().toString();}}

四、Handler实现

建立WebSocket通信时,第一次发送的就是http请求,进行协议升级为WebSocket,需要实现针对该连接请求进行处理的handler,协议升级成功之后,后续发送的消息是数据帧,消息将由WebSocketFrame相关的Handler进行处理,为了方便理解,此处简单画个图:

1、DefaultHandler, 抽象类,重写一些基础的方法(处理触发的事件、建立连接、断开连接、遗异常处理的),供其他Handler继承,其他Handler则无需实现上述功能

public abstract class DefaultHandler<T> extends SimpleChannelInboundHandler<T> {// 配合心跳使用,由客户端进行发送心跳数据包@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {if (evt instanceof IdleStateEvent) {IdleStateEvent idleStateEvent = (IdleStateEvent) evt;// 触发读空闲, 关闭客户端if(idleStateEvent.state().equals(IdleState.READER_IDLE)) {System.out.println("触发读空闲,关闭channel");// 将消息传递CloseWebSocketFrame进行处理CloseWebSocketFrame closeWebSocketFrame = new CloseWebSocketFrame();ctx.fireChannelRead(closeWebSocketFrame);}}}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("与客户端建立连接: " + ctx.channel());ChannelManager.channelGroup.add(ctx.channel());}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {System.out.println("与客户端断开连接: " + ctx.channel());ChannelManager.channelGroup.remove(ctx.channel());}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();System.out.println("发生异常: " + ctx.channel());}}

2、FullHttpRequestHander

public class FullHttpRequestHandler extends DefaultHandler<FullHttpRequest> {private WebSocketServerHandshaker handShaker;@Overrideprotected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest httpRequest) throws Exception {System.out.println("客户端消息:" + httpRequest);if(httpRequest.decoderResult().isFailure() || !"websocket".equals(httpRequest.headers().get(HttpHeaderValues.UPGRADE))) {DefaultFullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);ctx.writeAndFlush(httpResponse);return;}WebSocketServerHandshakerFactory webSocketServerHandshakerFactory = new WebSocketServerHandshakerFactory("ws:/" + ctx.channel() + "/websocket", null, false);handShaker = webSocketServerHandshakerFactory.newHandshaker(httpRequest);if (null == handShaker) {WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());} else {// 在此处为channel添加WebSocketFrameEncoder和WebSocketFrameDecoderhandShaker.handshake(ctx.channel(), httpRequest);}ChannelManager.handShakerMap.put(ctx.channel(), handShaker);}}

3、TextWebSocketFrameHandler,当WebSocket通信建立成功之后,在此阶段发送的文本消息在该handler中进行处理,如果发送的是二进制流,那么请自己自行实现一个处理BinaryWebSocketFrame类型数据的Handler

public class TextWebSocketFrameHandler extends DefaultHandler<TextWebSocketFrame> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {WebSocketRequestMessage webSocketRequestMessage = JSON.parseObject(msg.text(), WebSocketRequestMessage.class);if("ping".equals(webSocketRequestMessage.getType())) {System.out.println("ping message");return;}System.out.println(ctx.channel() + ", 客户端消息:" + msg);WebSocketResponseMessage webSocketResponseMessage = new WebSocketResponseMessage(webSocketRequestMessage);TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame(JSON.toJSONString(webSocketResponseMessage));ChannelManager.channelGroup.writeAndFlush(textWebSocketFrame);}
}

4、CloseWebSocketFrameHandler,客户端进行关闭时,将通过该Handler进行处理

public class CloseWebSocketFrameHandler extends DefaultHandler<CloseWebSocketFrame> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, CloseWebSocketFrame msg) throws Exception {System.out.println("客户端要断开");WebSocketServerHandshaker handShaker = ChannelManager.handShakerMap.get(ctx.channel());handShaker.close(ctx.channel(), msg.retain());}}

五、WebSocketChannelInitializer实现

public class WebSocketChannelInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {ChannelPipeline pipeline = socketChannel.pipeline();// 将建立连接时发送的第一次http请求数据装换为转换为 HttpRequestpipeline.addLast(new HttpServerCodec());// 将拆分的http消息(请求内容、请求体)聚合成一个消息pipeline.addLast(new HttpObjectAggregator(65536));// 块写出pipeline.addLast(new ChunkedWriteHandler());// 配置读空闲Handler, 3秒该Channel没有产生读将会触发读空闲事件pipeline.addLast(new IdleStateHandler(3, 0, 0));pipeline.addLast(new FullHttpRequestHandler());pipeline.addLast(new TextWebSocketFrameHandler());pipeline.addLast(new CloseWebSocketFrameHandler());}}

六、WebSocketServer实现

public class WebSocketServer {public void bind(Integer port) {EventLoopGroup parent = new NioEventLoopGroup();EventLoopGroup child = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(parent, child).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024).childHandler(new WebSocketChannelInitializer());ChannelFuture channelFuture = serverBootstrap.bind(port).sync();System.out.println("web socket server 启动成功...");channelFuture.channel().closeFuture().sync();} catch (Exception e) {e.printStackTrace();} finally {parent.shutdownGracefully();child.shutdownGracefully();}}}

七、前端实现

接下来写一个简单的前端web页,无须引入框架,只需要使用原生HTML5和JavaScript即可。

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title></title></head><body><button id = "login-btn" onclick="login()">登录</button><button id = "logout-btn" onclick="logout()">退出</button><br/><p id="connection-status">连接状态:关闭</p><p id="username-tag">user:</p><input id="send-msg-box" type="text"/><button onclick="send()">发送</button><p>==================================群发消息=====================================</p><textarea  id="msg-box"></textarea ></body><script>var socket;var user;function login() {if (window.WebSocket){socket = new WebSocket("ws://localhost:8888/websocket");socket.onerror = function(event) {document.getElementById("connection-status").innerHTML = "连接状态:异常";document.getElementById("channel-tag").innerHTML = "channel:";}socket.onopen = function(event) {document.getElementById("connection-status").innerHTML = "连接状态:正常";var max = 999var min = 100user = 'admin' + (Math.floor(Math.random() * (max - min + 1)) + min)document.getElementById("username-tag").innerHTML = "user: " + user;// 每隔 2 秒钟发送一次 Ping 帧setInterval(function() {if (socket.readyState === socket.OPEN) {var pingMessage = {type: 'ping',message: ''}socket.send(JSON.stringify(pingMessage))}}, 2000);}socket.onclose = function(event){document.getElementById("connection-status").innerHTML = "连接状态:关闭";document.getElementById("username-tag").innerHTML = "user:"}socket.onmessage = function(event){var message = document.getElementById("msg-box").value;var response = JSON.parse(event.data)message = message + '\n' + response.date + " " + response.user + " " + response.message;document.getElementById("msg-box").value = message;}} else {alter("不支持websocket")}}function send() {if(socket == null) {alert('连接未建立')return;}if (socket.readyState == WebSocket.OPEN) {var value = document.getElementById("send-msg-box").value;var textMessage = {type: 'text',user: user,message: value}//alert(message);//console.log(message)socket.send(JSON.stringify(textMessage));} else {alert('连接未建立')}}function logout() {if(socket == null) {alert('连接未建立')return;}if (socket.readyState == WebSocket.OPEN) {socket.close();} else {alert('连接未建立')}}</script></html>

八、测试

1、启动服务端

new WebSocketServer().bind(8888);

控制台打印如下信息

web socket server 启动成功...

2、打开两个web页分别进行登录点击

点击登录之后,可以看到连接状态正常,以及随机分配了一个用户名

3、发送消息

在任一客户端进行消息发送,都能在所有客户端聊天框中看到如图信息:

4、关闭客户端,可手动点击关闭按钮,或则通过关闭网页以及浏览器,即可断开连接

九、参考链接

JS实时通信三把斧系列之一: websocket - 知乎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/302405.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

这个爱喝酒的酒鬼可真是让人操碎了心

全世界只有3.14 % 的人关注了数据与算法之美最近又有一道数学难题重现江湖&#xff0c;在数学的江湖上掀起了腥风血雨。为了这道题&#xff0c;武林中也衍生出了三个门派&#xff01;分别有75%派&#xff0c;90%派&#xff0c;50%派。打完这么多派字&#xff0c;怎么莫名有点饿…

这几家5月还在急招.NET,都是30k以上!

最近常看到鼓吹财务自由的文章&#xff0c;甚至将5月18号(谐音&#xff1a;我要发)都演变成了财务自由日&#xff0c;号称通过理财快速达到财务自由... 荒谬&#xff01;财务自由本身就是伪命题&#xff0c;更不提啥小白理财就变身财务自由了&#xff0c;完全收智商税&#xff…

mysql binlog oplog_mongodb 学习之oplog

背景&#xff1a;原来一个同事问我主从mongodb数据库为什么数据差距很大,我让他察看一下两边有啥不一样&#xff0c;发现主的local库有13G从却很小&#xff0c;进入local之后du发现有一个collection前缀的文件有13g&#xff0c;说明是local数据库中一个集合太大了&#xff0c;推…

WPF实现Map加载

WPF开发者QQ群&#xff1a; 340500857 欢迎转发、分享、点赞&#xff0c;谢谢大家~。 接着上一篇效果预览&#xff1a;一、MainWindow.xaml代码如下&#xff1a;<Window x:Class"WpfBingMap.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml…

和哪个专业的男生谈恋爱最惨?

全世界只有3.14 % 的人关注了数据与算法之美艺术类专业艺术类的男生是最懂女孩们的心思&#xff0c;也是最浪漫的一类人群&#xff0c;弹琴唱歌跳舞画画样样擅长。这类男生所做的一切&#xff0c;皆可以把女孩们的心俘获到。但是呢&#xff0c;这类男孩的身边总是会有很多玩的很…

只能选择分卷文件的第一部分。_为机器学习模型选择正确的度量评估(第一部分)...

作者&#xff1a;Alvira Swalin编译&#xff1a;ronghuaiyang导读对不同的应用场景&#xff0c;需要不同的模型&#xff0c;对于不同的模型&#xff0c;需要不同的度量评估方式。本系列的第一部分主要关注回归的度量在后现代主义的世界里&#xff0c;相对主义的各种形式一直是最…

多项式乘法与快速傅里叶变换

全世界只有3.14 % 的人关注了数据与算法之美第一节、多项式乘法我们知道&#xff0c;有两种表示多项式的方法&#xff0c;即系数表示法和点值表示法。什么是系数表示法?所谓的系数表示法&#xff0c;举个例子如下图所示&#xff0c;A&#xff08;x&#xff09;6x^3 7x^2 - 10…

WPF 模仿QQ音乐首页歌单效果

qq音乐桌面版做的效果感觉很不错&#xff0c;今天就模仿一下它首页歌单的效果&#xff0c;从简单做起。。。看一下效果&#xff1a;&#xff0c;其实也很简单&#xff0c;就是布局和动画&#xff0c;触发器。。。还用到了ItemsControl下面就看看代码&#xff1a;MainWindow的xa…

收藏 : 50个Excel逆天功能,一秒变“表哥”

全世界只有3.14 % 的人关注了数据与算法之美Excel的50个逆天功能&#xff0c;动画教程珍藏版&#xff01;先看几个简单的&#xff1a;1、自动筛选2、在Excel中字符替换3、在Excel中冻结行列标题4、在Excel中为导入外部数据5、在Excel中行列快速转换6、共享Excel工作簿7、在Exce…

实战~~整个网络无法浏览,提示网络不存在或者尚未启动

今天早上接到同事的电脑&#xff0c;说其他人访问不到他的电脑&#xff0c;他电脑上有文件要共享才能进行工作~~故障现象&#xff1a;能上网&#xff0c;能PING通其他电脑&#xff0c;但是通过网上邻居和IP不能访问其他电脑上的资源。 这是在故障本机上的提示~~ 这是其他工作站…

python ctp接口_使用ctp的python接口

在github上查到一个项目ctpwrapper在按照文档按照的时候报错>>>pip install cython --upgrade>>>pip install ctpwrapper --upgrade在安装第二个命令的时候第一个问题安装yum install -y gcc-c 解决第二个问题ctpwrapper/MdApi.cpp:39:20: 致命错误:Python.h…

C# 并行和多线程编程——认识和使用Task

对于多线程&#xff0c;我们经常使用的是Thread。在我们了解Task之前&#xff0c;如果我们要使用多核的功能可能就会自己来开线程&#xff0c;然而这种线程模型在.net 4.0之后被一种称为基于“任务的编程模型”所冲击&#xff0c;因为task会比thread具有更小的性能开销&#xf…

Facebook上的一道题,超过50万的评论和1万3500次分享

全世界只有3.14 % 的人关注了数据与算法之美近日&#xff0c;有网友在Facebook发了一道数学题&#xff1a;发布以后&#xff0c;目前已经收到超过50万的评论和1万3500次分享&#xff0c;图中包含四个等式&#xff0c;前面三个已经有答案了&#xff0c;最后一个问题要求你得出相…

从数学入手,3招打破机器学习的边界

全世界只有3.14 % 的人关注了数据与算法之美本文约2007余字&#xff0c;阅读需要约6分钟&#xff1b;系统资料领取见文末&#xff1b;关键词&#xff1a;人工智能&#xff0c;机器学习&#xff0c;深度学习&#xff0c;数学&#xff0c;学习建议01.机器学习工程师的边界是什么&…

.NET Core 基于 Grafana Loki 日志初体验

介绍Loki: like Prometheus, but for logs.Loki是一个轻量级的日志系统&#xff0c;受到Prometheus项目的启发&#xff0c;由Grafana团队设计和开发&#xff0c;所以在Grafana中是原生支持的&#xff0c;具有可水平扩展&#xff0c;高度可用等特性&#xff0c;通过存储压缩的、…

基于开源流程引擎Activiti5的工作流开发平台BPMX3

2019独角兽企业重金招聘Python工程师标准>>> BPMX3平台是宏天软件在ESTBPM2的基础上&#xff0c;追随开源工作流平台Activiti5&#xff0c;由原班开发团队&#xff0c;历时一年&#xff0c;现重新推出一套解决中国政府及企业的业务流程的开发平台。 相对商业的工作流…

通过Dapr实现一个简单的基于.net的微服务电商系统(十)——一步一步教你如何撸Dapr之绑定...

如果说Actor是dapr有状态服务的内部体现的话&#xff0c;那绑定应该是dapr对serverless这部分的体现了。我们可以通过绑定极大的扩展应用的能力&#xff0c;甚至未来会成为serverless的基础。最开始接触dapr的时候&#xff0c;会在其官方首页看到这么一句话“Dapr is a portabl…

三位一体,用游戏打通孩子记忆力、认知和双语启蒙的学前神器

对于孩子学习知识&#xff0c;现在父母多表现有2个极端&#xff0c;一种完全不让小小孩学硬知识&#xff0c;一种又希望孩子从很小开始就学硬知识。小木比较反对在孩子6岁前就给他们生硬地灌输知识&#xff0c;一定得认识多少个字&#xff0c;背多少个单词&#xff0c;但只要做…

分享一个CSS3的网格系统架构 - ResponsiveAeon

日期&#xff1a;2012-7-30 来源&#xff1a;GBin1.com 在线演示 本地下载 曾经介绍过其它类型的CSS3网格系统&#xff0c;今天我们介绍一款能够帮助你快速创建基于HTML5/CSS3的响应式布局框架 - ResponsiveAeon。 它拥有一个宽度为1104px并且基于12个列的网格框架系统&#…

网络协议,没有想象中那么难

十个人程序员里面&#xff0c;有十个都会说自己学过网络协议&#xff0c;九个人都会说自己懂网络协议。但是面试的时候&#xff0c;问几个问题&#xff0c;能回答的可能只有两三个。不信&#xff1f;来&#xff0c;我问你几道。1、TCP 协议跟 UDP 协议有什么区别&#xff1f;你…