目录
2.7 HTML5客户端 (src/main/resources/static/chat.html)
1、开发流程
-
创建Spring Boot项目
-
添加Netty依赖
-
配置Netty端口和路径
-
实现Netty服务器
-
编写WebSocket处理器
-
创建HTML5客户端
-
启动和测试
2、具体代码实现
2.1 添加依赖 (pom.xml)
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.68.Final</version>
</dependency>
<!-- 配置处理器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2.2 配置文件(application.yml)
netty:
websocket:
port: 8081 # Netty服务器端口
path: /chat # WebSocket路径
2.3 配置类读取设置
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "netty.websocket")
public class NettyConfig {
private int port;
private String path;
// Getters and Setters
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
2.4 Netty服务器实现
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class NettyServer {
@Autowired
private NettyConfig nettyConfig;
@Autowired
private WebSocketInitializer webSocketInitializer;
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
@PostConstruct
public void start() throws InterruptedException {
bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(webSocketInitializer)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(nettyConfig.getPort()).sync();
System.out.println("Netty WebSocket服务器启动,端口:" + nettyConfig.getPort());
}
@PreDestroy
public void shutdown() {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
System.out.println("Netty服务器已关闭");
}
}
2.5 WebSocket初始化器和处理器
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
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.stream.ChunkedWriteHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class WebSocketInitializer extends ChannelInitializer<SocketChannel> {
@Autowired
private NettyConfig nettyConfig;
@Autowired
WebSocketHandler webSocketHandler;
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
// HTTP编解码器
pipeline.addLast(new HttpServerCodec());
// 支持大数据流
pipeline.addLast(new ChunkedWriteHandler());
// HTTP聚合器
//post请求分三部分. request line / request header / message body
// HttpObjectAggregator将多个信息转化成单一的request或者response对象
pipeline.addLast(new HttpObjectAggregator(65536));
// WebSocket协议处理器,将http协议升级为ws协议
pipeline.addLast(new WebSocketServerProtocolHandler(nettyConfig.getPath(), null, true));
// 自定义消息处理器
pipeline.addLast(webSocketHandler);
}
}
import io.netty.channel.Channel;
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.util.concurrent.GlobalEventExecutor;
@Component
@ChannelHandler.Sharable //设置通道共享
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
// 管理所有连接的Channel
private static final ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
clients.add(ctx.channel());
System.out.println("客户端连接: " + ctx.channel().id().asShortText());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
System.out.println("客户端断开: " + ctx.channel().id().asShortText());
}
//TextWebSocketFrame: websocket数据是帧的形式处理
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
// 广播收到的消息
String text = msg.text();
System.out.println("收到消息: " + text);
for (Channel channel : clients) {
if (channel != ctx.channel()) {
channel.writeAndFlush(new TextWebSocketFrame("用户" + ctx.channel().id().asShortText() + ": " + text));
} else {
channel.writeAndFlush(new TextWebSocketFrame("我: " + text));
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
2.6 Spring Boot启动类
2.7 HTML5客户端 (src/main/resources/static/chat.html)
<!DOCTYPE html>
<html>
<head>
<title>WebSocket聊天室</title>
<style>
body { font-family: Arial, sans-serif; }
#chat-box { height: 400px; border: 1px solid #ccc; padding: 10px; overflow-y: scroll; }
.message { margin: 5px 0; }
.self { color: blue; text-align: right; }
.remote { color: green; }
</style>
</head>
<body>
<h1>WebSocket聊天室</h1>
<div id="chat-box"></div>
<input type="text" id="message-input" placeholder="输入消息...">
<button onclick="sendMessage()">发送</button>
<script>
// 从配置中获取Netty端口和路径
const nettyPort = 8081; // 实际项目中应从后端获取
const wsPath = "/chat";
// 创建WebSocket连接
const socket = new WebSocket(`ws://${window.location.hostname}:${nettyPort}${wsPath}`);
// 连接建立
socket.onopen = () => {
addMessage("系统", "连接服务器成功", "system");
};
// 接收消息
socket.onmessage = (event) => {
addMessage("远程", event.data, "remote");
};
// 错误处理
socket.onerror = (error) => {
console.error("WebSocket错误:", error);
addMessage("系统", "连接发生错误", "system");
};
// 连接关闭
socket.onclose = () => {
addMessage("系统", "连接已关闭", "system");
};
// 发送消息
function sendMessage() {
const input = document.getElementById("message-input");
const message = input.value.trim();
if (message) {
socket.send(message);
addMessage("我", message, "self");
input.value = "";
}
}
// 添加消息到界面
function addMessage(sender, content, type) {
const chatBox = document.getElementById("chat-box");
const msgElement = document.createElement("div");
msgElement.className = `message ${type}`;
msgElement.innerHTML = `<strong>${sender}:</strong> ${content}`;
chatBox.appendChild(msgElement);
chatBox.scrollTop = chatBox.scrollHeight;
}
// 回车发送消息
document.getElementById("message-input").addEventListener("keypress", (e) => {
if (e.key === "Enter") sendMessage();
});
</script>
</body>
</html>
2.8 启动与测试
启动Spring Boot应用:
mvn spring-boot:run
访问客户端页面:http://localhost:8080/chat.html
打开多个浏览器窗口测试聊天功能