pom.xml | ●●●●● patch | view | raw | blame | history | |
src/main/java/cc/mrbird/febs/common/configure/NettyWebSocketConfig.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/cc/mrbird/febs/common/websocket/WebSocketServer.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/cc/mrbird/febs/common/websocket/WebSocketServerHandler.java | ●●●●● patch | view | raw | blame | history | |
src/main/resources/application.yml | ●●●●● patch | view | raw | blame | history | |
src/main/resources/static/febs/pages/websocket/chat.html | ●●●●● patch | view | raw | blame | history |
pom.xml
@@ -29,6 +29,14 @@ <dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.92.Final</version> <!-- 示例版本 --> </dependency> <dependency> <groupId>com.volcengine</groupId> <artifactId>volcengine-java-sdk-ark-runtime</artifactId> src/main/java/cc/mrbird/febs/common/configure/NettyWebSocketConfig.java
New file @@ -0,0 +1,54 @@ package cc.mrbird.febs.common.configure; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.stream.ChunkedWriteHandler; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class NettyWebSocketConfig { @Value("${netty.websocket.port:8090}") private int port; @Bean(name = "bossGroup", destroyMethod = "shutdownGracefully") public EventLoopGroup bossGroup() { return new NioEventLoopGroup(1); } @Bean(name = "workerGroup", destroyMethod = "shutdownGracefully") public EventLoopGroup workerGroup() { return new NioEventLoopGroup(); } @Bean public ServerBootstrap serverBootstrap(EventLoopGroup bossGroup, EventLoopGroup workerGroup) { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new io.netty.channel.ChannelInitializer<io.netty.channel.socket.SocketChannel>() { @Override protected void initChannel(io.netty.channel.socket.SocketChannel ch) throws Exception { ch.pipeline().addLast(new HttpServerCodec()); ch.pipeline().addLast(new ChunkedWriteHandler()); ch.pipeline().addLast(new HttpObjectAggregator(65536)); ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws")); ch.pipeline().addLast(new cc.mrbird.febs.common.websocket.WebSocketServerHandler()); } }); return bootstrap; } } src/main/java/cc/mrbird/febs/common/websocket/WebSocketServer.java
New file @@ -0,0 +1,45 @@ package cc.mrbird.febs.common.websocket; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Component public class WebSocketServer { @Value("${netty.websocket.port:8090}") private int port; @Autowired private ServerBootstrap serverBootstrap; @Autowired private EventLoopGroup bossGroup; @Autowired private EventLoopGroup workerGroup; private ChannelFuture channelFuture; @PostConstruct public void start() throws InterruptedException { channelFuture = serverBootstrap.bind(port).sync(); System.out.println("Netty WebSocket服务器启动在端口:" + port); } @PreDestroy public void stop() { if (channelFuture != null) { channelFuture.channel().close(); } bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); System.out.println("Netty WebSocket服务器已停止"); } } src/main/java/cc/mrbird/febs/common/websocket/WebSocketServerHandler.java
New file @@ -0,0 +1,48 @@ package cc.mrbird.febs.common.websocket; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.util.concurrent.GlobalEventExecutor; public class WebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { channels.add(ctx.channel()); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { channels.remove(ctx.channel()); } @Override public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { String request = msg.text(); System.out.println("服务端收到:" + request); // 广播消息给其他连接的客户端,不包括发送者自己 for (io.netty.channel.Channel channel : channels) { if (channel != ctx.channel()) { channel.writeAndFlush(new TextWebSocketFrame("[用户] " + request)); } } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } src/main/resources/application.yml
@@ -41,3 +41,7 @@ metadata: TableInfoHelper: error cc.mrbird.febs: info netty: websocket: port: 8090 src/main/resources/static/febs/pages/websocket/chat.html
New file @@ -0,0 +1,134 @@ <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>WebSocket聊天室</title> <link rel="stylesheet" href="/layui/css/layui.css" media="all"> <style> .chat-container { width: 100%; max-width: 800px; margin: 20px auto; border: 1px solid #e6e6e6; border-radius: 4px; overflow: hidden; } .chat-messages { height: 400px; padding: 15px; overflow-y: auto; background-color: #f8f8f8; } .chat-input { padding: 15px; background-color: #fff; } .message { margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background-color: #fff; box-shadow: 0 1px 2px rgba(0,0,0,0.1); } .message.me { background-color: #1E9FFF; color: white; text-align: right; } </style> </head> <body> <div class="layui-container"> <div class="chat-container"> <div class="chat-messages" id="chatMessages"> <!-- 消息将在这里显示 --> </div> <div class="chat-input"> <div class="layui-form"> <div class="layui-form-item"> <div class="layui-input-block"> <input type="text" id="messageInput" placeholder="请输入消息..." class="layui-input"> </div> </div> <div class="layui-form-item"> <div class="layui-input-block"> <button class="layui-btn" id="sendBtn">发送消息</button> <button class="layui-btn layui-btn-primary" id="connectBtn">连接</button> <button class="layui-btn layui-btn-danger" id="disconnectBtn">断开</button> </div> </div> </div> </div> </div> </div> <script src="/layui/layui.js" charset="utf-8"></script> <script> layui.use(['jquery'], function() { var $ = layui.jquery; var websocket; function connect() { if ('WebSocket' in window) { // 连接到Netty WebSocket服务器 websocket = new WebSocket("ws://localhost:8090/ws"); websocket.onopen = function() { appendMessage("[系统] 连接成功", "system"); }; websocket.onmessage = function(event) { appendMessage(event.data, "other"); }; websocket.onclose = function() { appendMessage("[系统] 连接已关闭", "system"); }; websocket.onerror = function() { appendMessage("[系统] 连接发生错误", "system"); }; } else { alert('您的浏览器不支持WebSocket'); } } function disconnect() { if (websocket) { websocket.close(); } } function sendMessage() { var message = $('#messageInput').val().trim(); if (message && websocket) { websocket.send(message); appendMessage(message, "me"); $('#messageInput').val(''); } } function appendMessage(message, type) { var messageClass = "message"; if (type === "me") { messageClass += " me"; } $('#chatMessages').append('<div class="' + messageClass + '">' + message + '</div>'); $('#chatMessages').scrollTop($('#chatMessages')[0].scrollHeight); } // 绑定事件 $('#connectBtn').on('click', connect); $('#disconnectBtn').on('click', disconnect); $('#sendBtn').on('click', sendMessage); // 回车发送消息 $('#messageInput').on('keypress', function(e) { if (e.which === 13) { sendMessage(); } }); }); </script> </body> </html>