diff --git a/src/main/java/com/guwan/backend/netty/chat/ChatMessage.java b/src/main/java/com/guwan/backend/netty/chat/ChatMessage.java new file mode 100644 index 0000000..a9bea22 --- /dev/null +++ b/src/main/java/com/guwan/backend/netty/chat/ChatMessage.java @@ -0,0 +1,11 @@ +package com.guwan.backend.netty.chat; + +import lombok.Data; + +@Data +public class ChatMessage { + private String type; // 消息类型: CONNECT, CHAT, DISCONNECT + private String from; // 发送者 + private String content; // 消息内容 + private Long timestamp; // 时间戳 +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/netty/chat/ChatServer.java b/src/main/java/com/guwan/backend/netty/chat/ChatServer.java new file mode 100644 index 0000000..a47c256 --- /dev/null +++ b/src/main/java/com/guwan/backend/netty/chat/ChatServer.java @@ -0,0 +1,70 @@ +package com.guwan.backend.netty.chat; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ChatServer { + + @Value("${netty.chat.port}") + private int port; + + private final ChatServerHandler chatServerHandler; + private EventLoopGroup bossGroup; + private EventLoopGroup workerGroup; + + @PostConstruct + public void start() throws Exception { + bossGroup = new NioEventLoopGroup(1); + workerGroup = new NioEventLoopGroup(); + + try { + ServerBootstrap bootstrap = new ServerBootstrap() + .group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ch.pipeline() + .addLast(new HttpServerCodec()) + .addLast(new HttpObjectAggregator(65536)) + .addLast(new WebSocketServerProtocolHandler("/ws/chat")) + .addLast(chatServerHandler); + } + }); + + ChannelFuture future = bootstrap.bind(port).sync(); + log.info("聊天服务器启动成功,WebSocket端口: {}", port); + } catch (Exception e) { + log.error("聊天服务器启动失败", e); + throw e; + } + } + + @PreDestroy + public void stop() { + if (bossGroup != null) { + bossGroup.shutdownGracefully(); + } + if (workerGroup != null) { + workerGroup.shutdownGracefully(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/netty/chat/ChatServerHandler.java b/src/main/java/com/guwan/backend/netty/chat/ChatServerHandler.java new file mode 100644 index 0000000..67b5c99 --- /dev/null +++ b/src/main/java/com/guwan/backend/netty/chat/ChatServerHandler.java @@ -0,0 +1,92 @@ +package com.guwan.backend.netty.chat; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +@ChannelHandler.Sharable +public class ChatServerHandler extends SimpleChannelInboundHandler { + + private static final Map clients = new ConcurrentHashMap<>(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) throws Exception { + // 解析消息 + ChatMessage message = objectMapper.readValue(frame.text(), ChatMessage.class); + + // 处理不同类型的消息 + switch (message.getType()) { + case "CONNECT": + handleConnect(ctx, message); + break; + case "CHAT": + handleChat(message); + break; + case "DISCONNECT": + handleDisconnect(message); + break; + } + } + + private void handleConnect(ChannelHandlerContext ctx, ChatMessage message) { + // 保存用户Channel + clients.put(message.getFrom(), ctx.channel()); + + // 广播用户上线消息 + broadcastMessage(new ChatMessage() {{ + setType("CHAT"); + setFrom("System"); + setContent(message.getFrom() + " joined the chat"); + setTimestamp(System.currentTimeMillis()); + }}); + } + + private void handleChat(ChatMessage message) { + // 广播聊天消息 + broadcastMessage(message); + } + + private void handleDisconnect(ChatMessage message) { + // 移除用户Channel + clients.remove(message.getFrom()); + + // 广播用户下线消息 + broadcastMessage(new ChatMessage() {{ + setType("CHAT"); + setFrom("System"); + setContent(message.getFrom() + " left the chat"); + setTimestamp(System.currentTimeMillis()); + }}); + } + + private void broadcastMessage(ChatMessage message) { + try { + String messageJson = objectMapper.writeValueAsString(message); + TextWebSocketFrame frame = new TextWebSocketFrame(messageJson); + + // 广播给所有在线用户 + clients.values().forEach(channel -> + channel.writeAndFlush(frame.retain()) + ); + } catch (Exception e) { + log.error("广播消息失败", e); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + log.error("WebSocket error", cause); + ctx.close(); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bd7027d..095cce9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -140,4 +140,16 @@ easy-es: process-index-mode: manual print-dsl: true distributed: false - response-log: true \ No newline at end of file + response-log: true + +netty: + danmaku: + port: 8085 + chat: + port: 8086 + stream: + port: 8087 + heartbeat: + interval: 30 + cluster: + nodes: localhost:8088,localhost:8089 \ No newline at end of file diff --git a/src/main/resources/static/chat.html b/src/main/resources/static/chat.html new file mode 100644 index 0000000..2c93e4f --- /dev/null +++ b/src/main/resources/static/chat.html @@ -0,0 +1,68 @@ + + + + WebSocket Chat + + + +
+ + + + + + + \ No newline at end of file