使用 Netty 实现群聊功能的步骤和注意事项
文章目录
- 前言
- 声明
- 功能说明
- 实现步骤
- WebSocket 服务启动
- Channel 初始化
- HTTP 请求处理
- HTTP 页面内容
- WebSocket 请求处理
- 效果展示
- 总结
前言
通过之前的文章介绍,我们可以深刻认识到Netty在网络编程领域的卓越表现和强大实力。这篇文章将介绍如何利用 Netty 框架开发一个 WebSocket 服务端,从而实现一个简单的在线聊天功能。
声明
文章中所提供的代码仅供参考,旨在帮助无 Netty 经验的开发人员快速上手。请注意,这些代码并不适用于实际应用中。
功能说明
聊天页面:
- 用户进入页面后,会看到一个简单的文本框,可以用来发送消息。
- 页面下方会显示聊天的消息内容。
服务端主要有以下三个功能:
- 响应聊天页面:用来接收和响应聊天页面的请求。
- 处理消息:对接收到的消息进行处理。
- 实现群聊功能:提供群聊的功能,使多个用户能够在同一个聊天室中进行交流。
功能很简单,但是可以通过这个示例实现更多复杂的场景。
实现步骤
创建一个简单的 Maven 项目,直接引入 netty-all 包即可编码。
<dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.28.Final</version>
</dependency>
实现该功能共有五个类,如下:
├── MakeIndexPage.java
├── ProcessWsIndexPageHandler.java
├── ProcesssWsFrameHandler.java
├── WebSocketServer.java
└── WebSocketServerInitializer.java
下面对实现该功能所涉及的五个类的代码进行详细说明
WebSocket 服务启动
这个类是一个基于 Netty 启动的常规服务端。它包含了一些配置项,包括 Reactor 模式、IO 类型以及消息处理配置,大部分都是这样。代码如下:
/*** 类说明:*/
public final class WebSocketServer {/*创建 DefaultChannelGroup,用来保存所有已经连接的 WebSocket Channel,群发和一对一功能可以用上*/private final static ChannelGroup channelGroup =new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);static final boolean SSL = false;//是否启用ssl/*通过ssl访问端口为8443,否则为8080*/static final int PORT= Integer.parseInt(System.getProperty("port", SSL? "8443" : "80"));public static void main(String[] args) throws Exception {/*SSL配置*/final SslContext sslCtx;if (SSL) {SelfSignedCertificate ssc = new SelfSignedCertificate();sslCtx = SslContextBuilder.forServer(ssc.certificate(),ssc.privateKey()).build();} else {sslCtx = null;}EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new WebSocketServerInitializer(sslCtx,channelGroup));Channel ch = b.bind(PORT).sync().channel();System.out.println("打开浏览器访问: " +(SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/');ch.closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}
Channel 初始化
这个类的主要功能是创建了一个 ChannelInitializer,用于初始化 ChannelPipeline,并添加了一些通道处理器。这些处理器包括由Netty提供的处理SSL协议、处理HTTP协议和支持WebSocket协议的功能,还有一些由业务自定义的处理器,用于处理页面展示和处理WebSocket数据。代码如下:
/*** 类说明:增加handler*/
public class WebSocketServerInitializerextends ChannelInitializer<SocketChannel> {private final ChannelGroup group;/*websocket访问路径*/private static final String WEBSOCKET_PATH = "/chat";private final SslContext sslCtx;public WebSocketServerInitializer(SslContext sslCtx,ChannelGroup group) {this.sslCtx = sslCtx;this.group = group;}@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();if (sslCtx != null) {pipeline.addLast(sslCtx.newHandler(ch.alloc()));}/*增加对http的支持*/pipeline.addLast(new HttpServerCodec());pipeline.addLast(new HttpObjectAggregator(65536));/*Netty提供,支持WebSocket应答数据压缩传输*/pipeline.addLast(new WebSocketServerCompressionHandler());/*Netty提供,对整个websocket的通信进行了初始化(发现http报文中有升级为websocket的请求),包括握手,以及以后的一些通信控制*/pipeline.addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PATH,null, true));/*浏览器访问时展示index页面*/pipeline.addLast(new ProcessWsIndexPageHandler(WEBSOCKET_PATH));/*对websocket的数据进行处理*/pipeline.addLast(new ProcesssWsFrameHandler(group));}
}
HTTP 请求处理
这个类的主要功能是在收到 HTTP 请求时,当 URI 为“/”或“/index.html”时,会返回一个聊天界面作为响应。代码如下:
/*** 类说明:对http请求,将index的页面返回给前端*/
public class ProcessWsIndexPageHandlerextends SimpleChannelInboundHandler<FullHttpRequest> {private final String websocketPath;public ProcessWsIndexPageHandler(String websocketPath) {this.websocketPath = websocketPath;}@Overrideprotected void channelRead0(ChannelHandlerContext ctx,FullHttpRequest req) throws Exception {// 处理错误或者无法解析的http请求if (!req.decoderResult().isSuccess()) {sendHttpResponse(ctx, req,new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));return;}//只允许Get请求if (req.method() != GET) {sendHttpResponse(ctx, req,new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN));return;}// 发送index页面的内容if ("/".equals(req.uri()) || "/index.html".equals(req.uri())) {//生成WebSocket的访问地址,写入index页面中String webSocketLocation= getWebSocketLocation(ctx.pipeline(), req,websocketPath);System.out.println("WebSocketLocation:["+webSocketLocation+"]");//生成index页面的具体内容,并送往浏览器ByteBuf content= MakeIndexPage.getContent(webSocketLocation);FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content);res.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/html; charset=UTF-8");HttpUtil.setContentLength(res, content.readableBytes());sendHttpResponse(ctx, req, res);} else {sendHttpResponse(ctx, req,new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND));}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}/*发送应答*/private static void sendHttpResponse(ChannelHandlerContext ctx,FullHttpRequest req,FullHttpResponse res) {// 错误的请求进行处理 (code<>200).if (res.status().code() != 200) {ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(),CharsetUtil.UTF_8);res.content().writeBytes(buf);buf.release();HttpUtil.setContentLength(res, res.content().readableBytes());}// 发送应答.ChannelFuture f = ctx.channel().writeAndFlush(res);//对于不是长连接或者错误的请求直接关闭连接if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) {f.addListener(ChannelFutureListener.CLOSE);}}/*根据用户的访问,告诉用户的浏览器,WebSocket的访问地址*/private static String getWebSocketLocation(ChannelPipeline cp,HttpRequest req,String path) {String protocol = "ws";if (cp.get(SslHandler.class) != null) {protocol = "wss";}return protocol + "://" + req.headers().get(HttpHeaderNames.HOST)+ path;}
}
HTTP 页面内容
这个类的主要目的是生成一个包含消息发送框和内容展示功能的HTML页面,并实现WebSocket的相关功能,包括建立连接、向服务端发送消息以及接收服务端的响应。当然,也可以单独写一个HTML文件。代码如下:
/*** 类说明:生成index页面的内容*/
public final class MakeIndexPage {private static final String NEWLINE = "\r\n";public static ByteBuf getContent(String webSocketLocation) {return Unpooled.copiedBuffer("<html><head><title>Web Socket Test</title><meta charset=\"utf-8\" /></head>"+ NEWLINE +"<body>" + NEWLINE +"<script type=\"text/javascript\">" + NEWLINE +"var socket;" + NEWLINE +"if (!window.WebSocket) {" + NEWLINE +" window.WebSocket = window.MozWebSocket;" + NEWLINE +'}' + NEWLINE +"if (window.WebSocket) {" + NEWLINE +" socket = new WebSocket(\"" + webSocketLocation + "\");"+ NEWLINE +" socket.onmessage = function(event) {" + NEWLINE +" var ta = document.getElementById('responseText');"+ NEWLINE +" ta.value = ta.value + '\\n' + event.data" + NEWLINE +" };" + NEWLINE +" socket.onopen = function(event) {" + NEWLINE +" var ta = document.getElementById('responseText');"+ NEWLINE +" ta.value = \"Web Socket opened!\";" + NEWLINE +" };" + NEWLINE +" socket.onclose = function(event) {" + NEWLINE +" var ta = document.getElementById('responseText');"+ NEWLINE +" ta.value = ta.value + \"Web Socket closed\"; "+ NEWLINE +" };" + NEWLINE +"} else {" + NEWLINE +" alert(\"Your browser does not support Web Socket.\");"+ NEWLINE +'}' + NEWLINE +NEWLINE +"function send(message) {" + NEWLINE +" if (!window.WebSocket) { return; }" + NEWLINE +" if (socket.readyState == WebSocket.OPEN) {" + NEWLINE +" socket.send(message);" + NEWLINE +" } else {" + NEWLINE +" alert(\"The socket is not open.\");" + NEWLINE +" }" + NEWLINE +'}' + NEWLINE +"</script>" + NEWLINE +"<form οnsubmit=\"return false;\">" + NEWLINE +"<input type=\"text\" name=\"message\" " +"value=\"Hi, 你好啊\"/>" +"<input type=\"button\" value=\"发送\""+ NEWLINE +" οnclick=\"send(this.form.message.value)\" />"+ NEWLINE +"<h3>消息内容</h3>" + NEWLINE +"<textarea id=\"responseText\" " +"style=\"width:500px;height:300px;\"></textarea>"+ NEWLINE +"</form>" + NEWLINE +"</body>" + NEWLINE +"</html>" + NEWLINE, CharsetUtil.UTF_8);}}
WebSocket 请求处理
这个类的主要功能是处理与 Channel 相关的事件。例如,当一个 Channel 连接成功时,会将该 Channel 添加到一个 ChannelGroup 中。当接收到该 Channel 的数据时,可以通过向 ChannelGroup 写入数据来实现群聊效果。代码如下
/*** 类说明:对websocket的数据进行处理*/
public class ProcesssWsFrameHandlerextends SimpleChannelInboundHandler<WebSocketFrame> {private final ChannelGroup group;public ProcesssWsFrameHandler(ChannelGroup group) {this.group = group;}private static final Logger logger= LoggerFactory.getLogger(ProcesssWsFrameHandler.class);@Overrideprotected void channelRead0(ChannelHandlerContext ctx,WebSocketFrame frame) throws Exception {//判断是否为文本帧,目前只处理文本帧if (frame instanceof TextWebSocketFrame) {// Send the uppercase string back.String request = ((TextWebSocketFrame) frame).text();logger.info("{} received {}", ctx.channel(), request);
// ctx.channel().writeAndFlush(
// new TextWebSocketFrame(request.toUpperCase(Locale.CHINA)));/*群发实现:一对一道理一样*/group.writeAndFlush(new TextWebSocketFrame(ctx.channel().remoteAddress() + " :" + request.toUpperCase(Locale.CHINA)));} else {String message = "unsupported frame type: "+ frame.getClass().getName();throw new UnsupportedOperationException(message);}}/*重写 userEventTriggered()方法以处理自定义事件*/@Overridepublic void userEventTriggered(ChannelHandlerContext ctx,Object evt) throws Exception {/*检测事件,如果是握手成功事件,做点业务处理*/if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {//通知所有已经连接的 WebSocket 客户端新的客户端已经连接上了group.writeAndFlush(new TextWebSocketFrame("Client " + ctx.channel().remoteAddress() + " joined"));//将新的 WebSocket Channel 添加到 ChannelGroup 中,// 以便它可以接收到所有的消息group.add(ctx.channel());} else {super.userEventTriggered(ctx, evt);}}
}
效果展示
服务端启动
聊天页面1
聊天页面2
总结
总的来说,基于 Netty 实现一个 WebSocket 功能是非常方便且高效的,但是我们需要知其所以然,要理解 Websocket 协议,也要懂的在 Netty 中,通过添加 ChannelHandler 来处理各种异常情况,例如握手失败、连接关闭等,当然,还要考虑安全性问题,例如处理跨站脚本攻击(XSS)、防止恶意数据传输等。
相关文章:

使用 Netty 实现群聊功能的步骤和注意事项
文章目录 前言声明功能说明实现步骤WebSocket 服务启动Channel 初始化HTTP 请求处理HTTP 页面内容WebSocket 请求处理 效果展示总结 前言 通过之前的文章介绍,我们可以深刻认识到Netty在网络编程领域的卓越表现和强大实力。这篇文章将介绍如何利用 Netty 框架开发一…...

一篇文章搞定《WebView的优化及封装》
一篇文章搞定《WebView的优化及封装》 前言WebView的过程分析确定优化方案一、预加载,复用缓冲池(初始化优化)优化的解析说明具体的实现 二、预置模版(请求、渲染优化)优化的解析说明具体的实现1、离线包2、预获取数据…...

FreeSWITCH 1.10.10 简单图形化界面5 - 使用百度TTS
FreeSWITCH 1.10.10 简单图形化界面5 - 使用百度TTS 0、 界面预览1、注册百度AI开放平台,开通语音识别服务2、获取AppID/API Key/Secret Key3、 安装百度语音合成sdk4、合成代码5、在PBX中使用百度TTS6、音乐文件-TTS7、拨号规则-tts_command 0、 界面预览 http://…...

DP读书:不知道干什么就和我一起读书吧
DP读书:不知道干什么就和我一起读书吧 为啥写博客:好处一:记录自己的学习过程优点二:让自己在各大社群里不那么尴尬推荐三:坚持下去,找到一个能支持自己的伙伴 虽然清楚知识需要靠时间沉淀,但在…...

【Linux】进程通信 — 信号(上篇)
文章目录 📖 前言1. 什么是信号1.1 认识信号:1.2 信号的产生:1.3 信号的异步:1.4 信号的处理: 2. 前后台进程3. 系统接口3.1 signal:3.1 - 1 不能被捕捉的信号 3.2 kill:3.2 - 1 killall 3.3 ra…...
JS弃之可惜食之无味的代码冷知识
JS代码冷知识大全 1. 变量提升与暂死 在JavaScript中,变量提升是一个有趣且容易让人误解的概念。在代码中,变量和函数声明会在其所在作用域的顶部被提升,但是初始化并不会被提升。这可能导致在声明之前就使用变量,结果为undefin…...

数据结构初阶--排序
目录 一.排序的基本概念 1.1.什么是排序 1.2.排序算法的评价指标 1.3.排序的分类 二.插入排序 2.1.直接插入排序 2.2.希尔排序 三.选择排序 3.1.直接选择排序 3.2.堆排序 重建堆 建堆 排序 四.交换排序 4.1.冒泡排序 4.2.快速排序 快速排序的递归实现 法一&a…...

赴日IT 如何提高去日本做程序员的几率?
其实想去日本做IT工作只要满足学历、日语、技术三个必要条件,具备这些条件应聘就好,不具备条件你就想办法具备这些条件,在不具备条件之前不要轻易到日本去,日本IT行业虽然要求技术没有国内那么高,但也不是随便好进入的…...
c# 使用了 await、asnync task.run 三者结合使用
在 C# 异步编程中,await 和 async 关键字结合使用可以让你更方便地编写异步代码,而无需直接使用 Task.Run。然而,有时候你可能仍然需要使用 Task.Run 来在后台线程上执行某些工作,这取决于你的代码逻辑和需求。 await 和 async 关…...

C#获取屏幕缩放比例
现在1920x1080以上分辨率的高分屏电脑渐渐普及了。我们会在Windows的显示设置里看到缩放比例的设置。在Windows桌面客户端的开发中,有时会想要精确计算窗口的面积或位置。然而在默认情况下,无论WinForms的Screen.Bounds.Width属性还是WPF中SystemParamet…...

Rn实现省市区三级联动
省市区三级联动选择是个很频繁的需求,但是查看了市面上很多插件不是太老不维护就是不满足需求,就试着实现一个 这个功能无任何依赖插件 功能略简单,但能实现需求 核心代码也尽力控制在了60行左右 pca-code.json树型数据来源 Administrative-d…...

SpringCloud学习笔记(十)_SpringCloud监控
今天我们来学习一下actuator这个组件,它不是SpringCloud之后才有的,而是SpringBoot的一个starter,Spring Boot Actuator。我们使用SpringCloud的时候需要使用这个组件对应用程序进行监控与管理 在SpringBoot2.0版本中,actuator可以…...
测试理论与方法----测试流程的第二个环节:测试计划
二、软件测试分类与测试计划 1、软件测试的分类(理解掌握) 根绝需求规格说明书,在设计阶段会产出的两个文档: 概要设计(HLD):设计软件的结构,包含软件的组成,模块之间的层次关系,模块与模块之间的调用关系…...

postgresql-子查询
postgresql-子查询 简介派生表IN 操作符ALL 操作符ANY 操作符关联子查询横向子查询EXISTS 操作符 简介 子查询(Subquery)是指嵌套在其他 SELECT、INSERT、UPDATE 以及 DELETE 语句中的 查询语句。 子查询的作用与多表连接查询有点类似,也是为…...

Linux 系统运维工具之 OpenLMI
一、前要 OpenLMI(全称 Open Linux Management Infrastructure)即开放式的 Linux 管理基础架构。OpenLMI 是一个开源项目,用于管理 Linux 系统管理的通用基础架构。它建立在现有工具基础上,充当抽象层,以便向系统管理…...

8天长假快来了,Python分析【去哪儿旅游攻略】数据,制作可视化图表
目录 前言环境使用模块使用数据来源分析 代码实现导入模块请求数据解析保存 数据可视化导入模块、数据年份分布情况月份分布情况出行时间情况费用分布情况人员分布情况 前言 2023年的中秋节和国庆节即将来临,好消息是,它们将连休8天!这个长假…...

【HSPCIE仿真】输入网表文件(5)基本仿真输出
仿真输出 1. 概述1.1 输出变量1.2 输出分析类型 2. 显示仿真结果2.1 .print语句基本语法示例 2.2 .probe 语句基本语法示例 2.3 子电路的输出2.4 打印控制选项.option probe.option post.option list.option ingold 2.5 .model_info打印模型参数 3. 仿真输出参数的选择3.1 直流…...

uni-app中使用iconfont彩色图标
uni-app中使用iconfont彩色图标 大家好,今天我们来学习一下uni-app中使用iconfont彩色图标,好好看,好好学,超详细的 第一步 首先,从iconfont官网(iconfont-阿里巴巴矢量图标库)选择自己需要的图…...

Hystrix: Dashboard流监控
接上两张服务熔断 开始搭建Dashboard流监控 pom依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocat…...

iconfont 图标在vue里的使用
刚好项目需要使用一个iconfont的图标,所以记录一下这个过程 1、iconfont-阿里巴巴矢量图标库 这个注册一个账号,以便后续使用下载代码时需要 2、寻找自己需要的图标 我主要是找两个图标 ,一个加号,一个减号,分别加入到…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...