前后端分离的Netty + WebSocket实现聊天室

1. 前端

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>WebSocket Chat</title>
</head>
<body><script type="text/javascript">var socket;if (!window.WebSocket) {window.WebSocket = window.MozWebSocket;}if (window.WebSocket) {socket = new WebSocket("ws://localhost:8088/ws");socket.onmessage = function(event) {console.log("receive message:" + event.data)var ele = document.getElementById('respText');ele.value = ele.value + "\n" + event.data;};socket.onopen = function(event) {var ele = document.getElementById('respText');ele.value = "连接开启";}socket.onclose = function(event) {var ele = document.getElementById('respText');ele.value = ele.value + "连接被关闭";}} else {alert("Your browser does not support WebSocket!");}function sendMessage(message) {if (!window.WebSocket) {alert("Your browser does not support WebSocket!")return;}if (socket.readyState == WebSocket.OPEN) {socket.send(message);console.log("send message:" + message)} else {alert("The connection is not open.");}}</script><form onsubmit="return false"><h3>WebSocket Chat</h3><textarea name="" id= "respText" style="width: 500px;height: 300px"></textarea><input type="text" name="message" style="width: 300px" value="Welcome to chat.marding.cn"><input type="button" value="发送" onclick="sendMessage(this.form.message.value)"></input>
</form>
</body>
</html>

2. 后端

@Component
public class WebSocketChatServer {private int port;public WebSocketChatServer(@Value("${websocket.port:8088}") int port) {this.port = port;}@PostConstructpublic void init() {try {this.run();} catch (Exception e) {throw new RuntimeException(e);}}public static void main(String[] args) throws Exception {//new WebSocketChatServer(8088).run();}public void run() throws Exception {NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);NioEventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(bossGroup,workerGroup)// 非阻塞式IO.channel(NioServerSocketChannel.class)// 在每次有新的客户端连接时被调用,用来初始化这个新连接的 ChannelPipeline.childHandler(new WebSocketChatServerInitializer())// 服务器端点的最大排队连接数.option(ChannelOption.SO_BACKLOG,128).childOption(ChannelOption.SO_KEEPALIVE,true);ChannelFuture channelFuture = serverBootstrap.bind(port).sync();System.out.println("bind port success");// 在Netty中用于阻塞当前线程,直到与通道(Channel)关联的连接被关闭channelFuture.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();bossGroup.shutdownGracefully();System.out.println("server stop");}}}
public class WebSocketChatServerInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new HttpServerCodec()).addLast(new HttpObjectAggregator(64 * 1024)).addLast(new ChunkedWriteHandler()).addLast(new HttpRequestHandler("/ws")).addLast(new WebSocketServerProtocolHandler("/ws")).addLast(new TextWebSocketFrameHandler());}
}
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {Channel channel = ctx.channel();channels.add(channel);// 广播channels.writeAndFlush(new TextWebSocketFrame(channel.remoteAddress() + "加入"));System.out.println(channel.remoteAddress() + "加入");}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {Channel channel = ctx.channel();channels.writeAndFlush(new TextWebSocketFrame(channel.remoteAddress() + "离开"));System.out.println(channel.remoteAddress() + "离开");}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {Channel channel = ctx.channel();System.out.println(channel.remoteAddress() + "在线");}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {Channel channel = ctx.channel();System.out.println(channel.remoteAddress() + "掉线");}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {Channel channel = ctx.channel();System.out.println(channel.remoteAddress() + " 异常");cause.printStackTrace();ctx.close();}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {String text = msg.text();System.out.println("收到消息:" + text);Channel channel = ctx.channel();text = WordsFilter.filter(text);for (Channel ch : channels){if (channel != ch){ch.writeAndFlush(new TextWebSocketFrame(channel.remoteAddress() + ":" + text));}else {ch.writeAndFlush(new TextWebSocketFrame("[you]" + text));}}}}
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {private final String wsUri;private static final File PAGE_FILE;static {URL location = HttpRequestHandler.class.getProtectionDomain().getCodeSource().getLocation();String path = null;try {path = location.toURI() + "wsChatClient.html";path = path.contains("file:") ? path.substring(5):path;PAGE_FILE = new File("\"C:\\Jlearning\\wsChatClient.html\"");} catch (URISyntaxException e) {throw new RuntimeException(e);}}/*** see {@link #SimpleChannelInboundHandler(boolean)} with {@code true} as boolean parameter.*/public HttpRequestHandler(String wsUri) {this.wsUri = wsUri;}/*** Is called for each message of type {@link I}.** @param ctx the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler}*            belongs to* @param msg the message to handle* @throws Exception is thrown if an error occurred*/@Overrideprotected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {if (wsUri.equalsIgnoreCase(request.uri())) {// 交给下一个InboudHandler 处理ctx.fireChannelRead(request.retain());return;}if (HttpUtil.is100ContinueExpected(request)) {// 发送 100 continuesend100Continue(ctx);}// 读取文件内容到字节数组byte[] fileContent;try (RandomAccessFile file = new RandomAccessFile(PAGE_FILE, "r")) {fileContent = new byte[(int) file.length()];file.readFully(fileContent);} catch (IOException e) {// 处理文件读取错误DefaultFullHttpResponse errorResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR);ctx.writeAndFlush(errorResponse);return;}DefaultFullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);resp.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");boolean keepAlive = HttpUtil.isKeepAlive(request);if (keepAlive) {resp.headers().set(HttpHeaderNames.CONTENT_LENGTH, fileContent.length);resp.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);}// 将文件内容设置为响应内容resp.content().writeBytes(fileContent);ChannelFuture channelFuture = ctx.writeAndFlush(resp);if (!keepAlive) {channelFuture.addListener(ChannelFutureListener.CLOSE);}channelFuture.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {System.out.println("response empty last content success !");}});}private void send100Continue(ChannelHandlerContext ctx) {DefaultFullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);ctx.writeAndFlush(resp);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {Channel channel = ctx.channel();System.out.println("client " + channel.remoteAddress() + "异常");cause.printStackTrace();ctx.close();}
}
public class WordsFilter {public static Map<String, Object> dictionaryMap = new HashMap<>();/*** 生成关键词字典库* @param words* @return*/public static void initMap(Collection<String> words) {if (words == null) {System.out.println("敏感词列表不能为空");return ;}// map初始长度words.size(),整个字典库的入口字数(小于words.size(),因为不同的词可能会有相同的首字)Map<String, Object> map = new HashMap<>(words.size());// 遍历过程中当前层次的数据Map<String, Object> curMap = null;Iterator<String> iterator = words.iterator();while (iterator.hasNext()) {String word = iterator.next();curMap = map;int len = word.length();for (int i =0; i < len; i++) {// 遍历每个词的字String key = String.valueOf(word.charAt(i));// 当前字在当前层是否存在, 不存在则新建, 当前层数据指向下一个节点, 继续判断是否存在数据Map<String, Object> wordMap = (Map<String, Object>) curMap.get(key);if (wordMap == null) {// 每个节点存在两个数据: 下一个节点和isEnd(是否结束标志)wordMap = new HashMap<>(2);wordMap.put("isEnd", "0");curMap.put(key, wordMap);}curMap = wordMap;// 如果当前字是词的最后一个字,则将isEnd标志置1if (i == len -1) {curMap.put("isEnd", "1");}}}dictionaryMap = map;}static {List<String> list = new ArrayList<>();list.add("坏蛋");list.add("笨蛋");initMap(list);}/*** 搜索文本中某个文字是否匹配关键词* @param text* @param beginIndex* @return*/private static int checkWord(String text, int beginIndex) {if (dictionaryMap == null) {throw new RuntimeException("字典不能为空");}boolean isEnd = false;int wordLength = 0;Map<String, Object> curMap = dictionaryMap;int len = text.length();// 从文本的第beginIndex开始匹配for (int i = beginIndex; i < len; i++) {String key = String.valueOf(text.charAt(i));// 获取当前key的下一个节点curMap = (Map<String, Object>) curMap.get(key);if (curMap == null) {break;} else {wordLength ++;if ("1".equals(curMap.get("isEnd"))) {isEnd = true;}}}if (!isEnd) {wordLength = 0;}return wordLength;}public static String filter(String text) {StringBuilder result = new StringBuilder();int length = text.length();int position = 0;while (position < length) {int matchLength = checkWord(text, position);if (matchLength > 0) {// 发现敏感词,用**替换result.append("**");position += matchLength;} else {result.append(text.charAt(position));position++;}}return result.toString();}/*** 获取匹配的关键词和命中次数* @param text* @return*/public static Map<String, Integer> matchWords(String text) {Map<String, Integer> wordMap = new HashMap<>();int len = text.length();for (int i = 0; i < len; i++) {int wordLength = checkWord(text, i);if (wordLength > 0) {String word = text.substring(i, i + wordLength);// 添加关键词匹配次数if (wordMap.containsKey(word)) {wordMap.put(word, wordMap.get(word) + 1);} else {wordMap.put(word, 1);}i += wordLength - 1;}}return wordMap;}
}

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

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

相关文章

计算机网络安全之一:网络安全概述

1.1 网络安全的内涵 随着计算机和网络技术的迅猛发展和广泛普及&#xff0c;越来越多的企业将经营的各种业务建立在Internet/Intranet环境中。于是&#xff0c;支持E-mail、文件共享、即时消息传送的消息和协作服务器成为当今商业社会中的极重要的IT基础设施。然而&#xff0…

程函方程的详细推导

以下是基于非均匀介质弹性波方程&#xff08;无纵波假设&#xff09;推导程函方程的详细过程&#xff0c;完整考虑纵波&#xff08;P 波&#xff09;和横波&#xff08;S 波&#xff09;的耦合效应&#xff1a;

【JavaEE进阶】MyBatis通过注解实现增删改查

目录 &#x1f343;前言 &#x1f340;打印日志 &#x1f334;传递参数 &#x1f38b;增(Insert) &#x1f6a9;返回主键 &#x1f384;删(Delete) &#x1f332;改(Update) &#x1f333;查(Select) &#x1f6a9;起别名 &#x1f6a9;结果映射 &#x1f6a9;开启驼…

[AHOI2018初中组] 分组---贪心算法

贪心没套路果真如此。 题目描述 小可可的学校信息组总共有 n 个队员&#xff0c;每个人都有一个实力值 ai​。现在&#xff0c;一年一度的编程大赛就要到了&#xff0c;小可可的学校获得了若干个参赛名额&#xff0c;教练决定把学校信息组的 n 个队员分成若干个小组去参加这场…

DeepSeek动画视频全攻略:从架构到本地部署

DeepSeek 本身并不直接生成动画视频,而是通过与一系列先进的 AI 工具和传统软件协作,完成动画视频的制作任务。这一独特的架构模式,使得 DeepSeek 在动画视频创作领域发挥着不可或缺的辅助作用。其核心流程主要包括脚本生成、画面设计、视频合成与后期处理这几个关键环节。 …

用deepseek学大模型08-长短时记忆网络 (LSTM)

deepseek.com 从入门到精通长短时记忆网络(LSTM),着重介绍的目标函数&#xff0c;损失函数&#xff0c;梯度下降 标量和矩阵形式的数学推导&#xff0c;pytorch真实能跑的代码案例以及模型,数据&#xff0c; 模型应用场景和优缺点&#xff0c;及如何改进解决及改进方法数据推导…

以ChatGPT为例解析大模型背后的技术

目录 1、大模型分类 2、为什么自然语言处理可计算&#xff1f; 2.1、One-hot分类编码&#xff08;传统词表示方法&#xff09; 2.2、词向量 3、Transformer架构 3.1、何为注意力机制&#xff1f; 3.2、注意力机制在 Transformer 模型中有何意义&#xff1f; 3.3、位置编…

鸿道Intewell操作系统:赋能高端装备制造,引领国产数控系统迈向新高度

在当今全球制造业竞争日益激烈的时代&#xff0c;高端装备制造作为国家核心竞争力的重要组成部分&#xff0c;其发展水平直接影响着一个国家的综合实力。而CNC数控系统&#xff0c;作为高端装备制造的“大脑”&#xff0c;对于提升装备的精度、效率和智能化水平起着关键作用。鸿…

mac开发环境配置笔记

1. 终端配置 参考&#xff1a; Mac终端配置笔记-CSDN博客 2. 下载JDK 到 oracle官网 下载jdk: oracle官网 :Java Downloads | Oraclemac的芯片为Intel系列下载 x64版本的jdk&#xff1b;为Apple Mx系列使用 Arm64版本&#xff1b;oracle官网下载时报错&#xff1a;400 Bad R…

【Python爬虫(29)】爬虫数据生命线:质量评估与监控全解

【Python爬虫】专栏简介&#xff1a;本专栏是 Python 爬虫领域的集大成之作&#xff0c;共 100 章节。从 Python 基础语法、爬虫入门知识讲起&#xff0c;深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑&#xff0c;覆盖网页、图片、音频等各类数据爬取&#xff…

大模型工具大比拼:SGLang、Ollama、VLLM、LLaMA.cpp 如何选择?

简介&#xff1a;在人工智能飞速发展的今天&#xff0c;大模型已经成为推动技术革新的核心力量。无论是智能客服、内容创作&#xff0c;还是科研辅助、代码生成&#xff0c;大模型的身影无处不在。然而&#xff0c;面对市场上琳琅满目的工具&#xff0c;如何挑选最适合自己的那…

测评雷龙出品的CS SD NAND贴片式TF卡

一、前言 在现代科技飞速发展的背景下&#xff0c;存储解决方案的创新与进步成为了推动各行各业发展的重要力量。这篇文章讲解雷龙公司出品的CS SD NAND贴片式TF卡的深度测评。这款产品不仅以其小巧精致的设计脱颖而出&#xff0c;更凭借其卓越的性能和可靠性&#xff0c;在众…

Hadoop一 HDFS分布式文件系统

一 分布式文件存储 了解为什么海量数据需要使用分布式存储技术 100T数据太大&#xff0c;单台服务器无法承担。于是&#xff1a; 分布式服务器集群 靠数量取胜&#xff0c;多台服务器组合&#xff0c;才能Hold住&#xff0c;如下 分布式不仅仅是解决了能存的问题&#xff…

windows下docker使用笔记

目录 镜像的配置 镜像的拉取 推荐镜像源列表&#xff08;截至2025年2月测试有效&#xff09; 配置方法 修改容器名字 如何使用卷 创建不同的容器&#xff0c;每个容器中有不同的mysql和java版本&#xff08;不推荐&#xff09; 1. 安装 Docker Desktop&#xff08;Win…

1005 K 次取反后最大化的数组和(贪心)

文章目录 题目[](https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/)算法原理源码总结 题目 如上图&#xff0c;k是取反的次数&#xff0c;在数组【4&#xff0c;-1,3】中&#xff0c;当k 1&#xff0c;把-2取反为2&#xff0c;和为9&#xff1b;在数组…

java毕业设计之医院门诊挂号系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的医院门诊挂号系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 医院门诊挂号系统的主要使用者…

深入学习解析:183页可编辑PPT华为市场营销MPR+LTC流程规划方案

华为终端正面临销售模式转型的关键时刻&#xff0c;旨在通过构建MPRLTC项目&#xff0c;以规避对运营商定制的过度依赖&#xff0c;并探索新的增长路径。项目核心在于建设一套全新的销售流程与IT系统&#xff0c;支撑双品牌及自有品牌的战略发展。 项目总体方案聚焦于四大关键议…

JUC并发—8.并发安全集合一

大纲 1.JDK 1.7的HashMap的死循环与数据丢失 2.ConcurrentHashMap的并发安全 3.ConcurrentHashMap的设计介绍 4.ConcurrentHashMap的put操作流程 5.ConcurrentHashMap的Node数组初始化 6.ConcurrentHashMap对Hash冲突的处理 7.ConcurrentHashMap的并发扩容机制 8.Concu…

Cython学习笔记1:利用Cython加速Python运行速度

Cython学习笔记1&#xff1a;利用Cython加速Python运行速度 CythonCython 的核心特点&#xff1a;利用Cython加速Python运行速度1. Cython加速Python运行速度原理2. 不使用Cython3. 使用Cython加速&#xff08;1&#xff09;使用pip安装 cython 和 setuptools 库&#xff08;2&…

DApp 开发入门指南

DApp 开发入门指南 &#x1f528; 1. DApp 基础概念 1.1 什么是 DApp&#xff1f; 去中心化应用&#xff08;DApp&#xff09;是基于区块链的应用程序&#xff0c;特点是&#xff1a; 后端运行在区块链网络前端可以是任何框架使用智能合约处理业务逻辑数据存储在区块链上 1…