Spring Boot 整合 Netty
1. Netty 服务器实现
1.1 NettyServer.java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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 org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
/**
* Netty 服务器组件
* 使用 @Component 注解让 Spring 管理生命周期
*/
@Component
public class NettyServer {
// 从配置文件中读取端口号,默认8888
@Value("${netty.port:8888}")
private int port;
// 主线程组,用于接收客户端连接
private EventLoopGroup bossGroup;
// 工作线程组,用于处理网络读写
private EventLoopGroup workerGroup;
/**
* 启动Netty服务器
* 使用@PostConstruct注解在Spring Bean初始化后执行
*/
@PostConstruct
public void start() throws InterruptedException {
// 创建主线程组,接收客户端连接
bossGroup = new NioEventLoopGroup(1); // 1个线程足够
// 创建工作线程组,处理网络读写
workerGroup = new NioEventLoopGroup(); // 默认CPU核心数*2
// 创建服务器启动对象
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
// 使用NioServerSocketChannel作为服务器通道实现
.channel(NioServerSocketChannel.class)
// 设置服务器监听端口
.localAddress(new InetSocketAddress(port))
// 初始化通道,设置处理器
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 添加自定义处理器
ch.pipeline().addLast(new NettyServerHandler());
}
})
// 服务器等待队列大小
.option(ChannelOption.SO_BACKLOG, 128)
// 保持长连接
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 绑定端口启动服务器
ChannelFuture future = bootstrap.bind().sync();
if (future.isSuccess()) {
System.out.println("Netty server started on port: " + port);
}
}
/**
* 关闭Netty服务器
* 使用@PreDestroy注解在Spring Bean销毁前执行
*/
@PreDestroy
public void stop() {
// 优雅关闭线程组
if (bossGroup != null) {
bossGroup.shutdownGracefully();
}
if (workerGroup != null) {
workerGroup.shutdownGracefully();
}
System.out.println("Netty server stopped");
}
}
1.2 NettyServerHandler.java
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.springframework.stereotype.Component;
/**
* Netty服务器处理器
* 处理客户端消息和事件
*/
@Component
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 读取客户端发送的消息
* @param ctx 通道上下文
* @param msg 接收到的消息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 1. 处理接收到的消息
String message = (String) msg;
System.out.println("Server received: " + message);
// 2. 返回响应给客户端
String response = "Server response: " + message.toUpperCase();
ctx.writeAndFlush(response);
// 3. 打印处理线程信息(演示Netty的异步特性)
System.out.println("Processed by thread: " + Thread.currentThread().getName());
}
/**
* 处理异常事件
* @param ctx 通道上下文
* @param cause 异常对象
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 打印异常堆栈
cause.printStackTrace();
// 关闭通道
ctx.close();
System.out.println("Client disconnected due to error");
}
/**
* 客户端连接建立时触发
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("New client connected: " + ctx.channel().remoteAddress());
}
/**
* 客户端断开连接时触发
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Client disconnected: " + ctx.channel().remoteAddress());
}
}
2. Netty 客户端实现(可选)
2.1 NettyClient.java
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
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.NioSocketChannel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* Netty客户端组件
* 用于连接Netty服务器并发送消息
*/
@Component
public class NettyClient {
// 服务器地址,从配置读取,默认localhost
@Value("${netty.server.host:localhost}")
private String host;
// 服务器端口,从配置读取,默认8888
@Value("${netty.server.port:8888}")
private int port;
// 客户端线程组
private EventLoopGroup group;
// 客户端通道
private Channel channel;
/**
* 启动Netty客户端并连接服务器
*/
public void start() throws InterruptedException {
// 创建客户端线程组
group = new NioEventLoopGroup();
// 创建客户端启动对象
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
// 使用NioSocketChannel作为客户端通道实现
.channel(NioSocketChannel.class)
// 设置处理器
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 添加自定义处理器
ch.pipeline().addLast(new NettyClientHandler());
}
});
// 连接服务器
ChannelFuture future = bootstrap.connect(host, port).sync();
channel = future.channel();
System.out.println("Netty client connected to " + host + ":" + port);
}
/**
* 向服务器发送消息
* @param message 要发送的消息
*/
public void sendMessage(String message) {
if (channel != null && channel.isActive()) {
System.out.println("Sending message: " + message);
channel.writeAndFlush(message);
} else {
System.err.println("Channel is not active, message not sent");
}
}
/**
* 关闭客户端
*/
public void stop() {
if (group != null) {
group.shutdownGracefully();
}
System.out.println("Netty client stopped");
}
}
2.2 NettyClientHandler.java
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.springframework.stereotype.Component;
/**
* Netty客户端处理器
* 处理服务器返回的消息
*/
@Component
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 读取服务器返回的消息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Client received: " + msg);
}
/**
* 处理异常事件
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
System.out.println("Disconnected from server due to error");
}
/**
* 连接建立时触发
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Connected to server: " + ctx.channel().remoteAddress());
}
/**
* 连接断开时触发
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Disconnected from server: " + ctx.channel().remoteAddress());
}
}
3. 测试控制器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试控制器
* 提供HTTP接口测试Netty功能
*/
@RestController
public class TestController {
@Autowired
private NettyClient nettyClient;
/**
* 发送消息到Netty服务器
* @param message 要发送的消息
* @return 操作结果
*/
@GetMapping("/send")
public String sendMessage(@RequestParam String message) {
try {
nettyClient.sendMessage(message);
return "Message sent: " + message;
} catch (Exception e) {
return "Failed to send message: " + e.getMessage();
}
}
}
4. 配置文件
application.properties
:
# Netty 服务器配置
netty.port=8888
# Netty 客户端配置 (可选)
netty.server.host=localhost
netty.server.port=8888
5. 自定义编解码器(带注释)
5.1 CustomEncoder.java
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* 自定义编码器
* 将String消息编码为字节流
*/
public class CustomEncoder extends MessageToByteEncoder<String> {
@Override
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
System.out.println("Encoding message: " + msg);
// 将字符串转换为字节数组并写入ByteBuf
out.writeBytes(msg.getBytes("UTF-8"));
}
}
5.2 CustomDecoder.java
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
* 自定义解码器
* 将字节流解码为String消息
*/
public class CustomDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 检查是否有足够的数据可读
if (in.readableBytes() < 4) { // 假设最小消息长度为4字节
return;
}
// 标记读取位置
in.markReaderIndex();
// 读取数据
byte[] bytes = new byte[in.readableBytes()];
in.readBytes(bytes);
String message = new String(bytes, "UTF-8");
System.out.println("Decoded message: " + message);
// 添加到输出列表,传递给下一个处理器
out.add(message);
}
}