netty网络框架三
一、scheduledTaskQueue
在 Netty 中,定时任务使用 ScheduledFuture 和 ScheduledExecutorService 接口进行操作,其中 ScheduledExecutorService 负责管理定时任务队列。
在 ScheduledExecutorService 中,ScheduledTaskQueue 是用来存储定时任务的队列。它是一个优先级队列,按照任务的执行时间排序。当定时任务被添加到 ScheduledExecutorService 中后,它会被添加到 ScheduledTaskQueue 中,并根据其执行时间排列在合适的位置。定时任务队列中的任务包含了执行时间、任务本身等信息。在每个时间点,ScheduledExecutorService 会检查定时任务队列中是否有需要执行的任务,如果有,则取出最先到期的任务并执行。
ScheduledTaskQueue 还支持删除和修改已经添加的任务,以及计算还有多少时间才能执行下一个任务等操作。这使得 Netty 可以方便地实现各种复杂的业务场景,例如定时任务、心跳检测等。
二、FutureListener机制
Netty中的FutureListener机制是一种异步编程模型,用于监听Netty中的Future对象的状态变化。当Future对象的状态发生变化时,将会触发监听器的回调方法,从而让开发者能够在合适的时机处理Future对象的结果或异常。
通过添加FutureListener,我们可以在I/O操作完成后执行特定的操作,例如发送另一个请求、更新UI界面或记录日志。此外,Netty还提供了ChannelFutureListener,以便在操作完成时关闭通道或者进行其他与通道相关的操作。
三、http服务过滤资源
Netty中可以使用ChannelInboundHandlerAdapter类的channelRead方法来过滤HTTP请求的资源。 在该方法中,我们可以检查URI和HTTP方法,并根据需要拒绝或接受请求。
以下是一个简单的示例代码,用于仅允许GET方法并且URI为“/hello”的HTTP请求通过:
public class HttpServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequest) {
HttpRequest request = (HttpRequest) msg;
HttpMethod method = request.method();
String uri = request.uri();
if (method.equals(HttpMethod.GET) && uri.equals("/hello")) {
// 此处可以继续处理请求
super.channelRead(ctx, msg);
} else {
// 拒绝请求
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN,
Unpooled.copiedBuffer("Access Denied", CharsetUtil.UTF_8));
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
} else {
// 不是HTTP请求,直接传递给下一个处理器
super.channelRead(ctx, msg);
}
}
}
在该示例中,如果HTTP请求的方法不是GET或URI不是“/hello”,则会返回HTTP 403(禁止访问)响应。否则,将继续处理请求并将其传递给管道中的下一个处理程序。
四、pipeline组件
在Netty中,Pipeline是一组ChannelHandler的有序集合,用于处理传入和传出的数据。每个Channel都有自己的Pipeline,当数据通过Channel时,将沿着Pipeline流动,并按顺序经过每个ChannelHandler。
Pipeline通常由开发者根据需要进行配置,例如添加编码器、解码器、业务逻辑处理器等来实现所需的协议或功能。可以使用ChannelPipeline接口提供的方法来添加或删除处理程序,以及在管道中移动处理程序位置。
以下是一个简单的例子,演示如何配置Pipeline:
ChannelInitializer<SocketChannel> initializer = new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 添加HTTP请求解码器
pipeline.addLast("decoder", new HttpRequestDecoder());
// 添加HTTP响应编码器
pipeline.addLast("encoder", new HttpResponseEncoder());
// 添加业务逻辑处理器
pipeline.addLast("handler", new HttpServerHandler());
}
};
在该示例中,我们可以看到Pipeline中添加了HttpRequestDecoder和HttpResponseEncoder来对HTTP请求和响应进行编解码,然后添加了HttpServerHandler来处理业务逻辑。这是一个非常简单的例子,Pipeline的配置可以根据实际需求进行适当调整。
五、EventLoop组件
在Netty中,EventLoop是一个关键组件,用于处理所有的I/O操作,例如连接、读取和写入。每个EventLoop都运行在一个单独的线程中,并且处理一个或多个Channel的所有事件。EventLoop的主要作用是将I/O事件分派给ChannelHandler进行处理。 在Netty中,每个Channel都会被分配到一个EventLoop上,并且在整个生命周期内都由该EventLoop处理。这种设计可以极大地减少线程上下文切换的开销,并且提高了系统的性能。此外,Netty还支持更高级别的编程模型,例如EventLoopGroup和多线程模型,以进一步优化系统性能。 总之,EventLoop是Netty中非常重要的一个组件,它提供了高效的I/O操作和更高级别的编程模型,使得Netty能够处理高并发的网络应用程序。
六、Unpooled
在Netty中,Unpooled是一个用于创建ByteBuf实例的工具类。它提供了一些静态方法来创建不同类型的ByteBuf,例如使用堆、直接内存或组合缓冲区。
Unpooled类还提供了许多实用方法,例如将字节数组转换为ByteBuf、复制ByteBuf以及对ByteBuf进行切片等。这些方法可以方便地操作ByteBuf,从而简化了网络编程中的数据处理和传输。
七、netty群聊系统服务端
以下是一个简单的Netty群聊系统服务端实现,用于演示如何使用Netty编写网络应用程序。
public class ChatServer {
private final int port;
public ChatServer(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("frameDecoder", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast("stringDecoder", new StringDecoder());
pipeline.addLast("stringEncoder", new StringEncoder());
pipeline.addLast("handler", new ChatServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
System.out.println("ChatServer started on port " + port);
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new ChatServer(8080).run();
}
}
该服务端使用了Netty的DelimitedBasedFrameDecoder来解码客户端发送的消息,并使用StringDecoder和StringEncoder将ByteBuf转换为字符串和反向转换。在处理完消息后,它将消息广播给所有已连接的客户端。要实现群聊系统的完整功能,还需要实现一个ChatServerHandler类来处理不同类型的消息和管理客户端列表。
八、netty群聊系统客户端
以下是一个简单的Netty群聊系统客户端实现,用于演示如何使用Netty编写网络应用程序。
public class ChatClient {
private final String host;
private final int port;
public ChatClient(String host, int port) {
this.host = host;
this.port = port;
}
public void run() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("frameDecoder", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast("stringDecoder", new StringDecoder());
pipeline.addLast("stringEncoder", new StringEncoder());
pipeline.addLast("handler", new ChatClientHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
System.out.println("ChatClient connected to " + host + ":" + port);
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
if ("quit".equalsIgnoreCase(line.trim())) {
break;
}
f.channel().writeAndFlush(line + "\r\n");
}
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new ChatClient("localhost", 8080).run();
}
}
该客户端与服务端类似,使用了Netty的DelimitedBasedFrameDecoder来解码服务端发送的消息,并使用StringDecoder和StringEncoder将ByteBuf转换为字符串和反向转换。在收到消息后,它将消息打印到控制台。该客户端还支持从控制台输入消息并发送它们给服务端。要实现群聊系统的完整功能,还需要实现一个ChatClientHandler类来处理不同类型的消息和管理客户端列表。
九、netty私聊机制
为了实现Netty私聊机制,需要对服务端和客户端进行修改以支持单独的消息传递。
在服务端,可以为每个连接创建一个Map来存储客户端的ChannelHandlerContext和昵称之间的映射。然后,在处理收到的消息时,如果收到的消息包含“@昵称”格式的前缀,则从Map中查找目标客户端的ChannelHandlerContext,并将消息发送给该客户端。
以下是服务端的代码示例:
public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
private static final Map<String, ChannelHandlerContext> clients = new ConcurrentHashMap<>();
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Client " + ctx.channel().remoteAddress() + " connected");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String nickname = getNickname(ctx);
if (nickname != null) {
clients.remove(nickname);
broadcastMessage("[Server] " + nickname + " left the chat");
}
System.out.println("Client " + ctx.channel().remoteAddress() + " disconnected");
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
String nickname = getNickname(ctx);
if (msg.startsWith("@")) {
int index = msg.indexOf(' ');
if (index >= 0) {
String target = msg.substring(1, index);
String message = msg.substring(index + 1);
ChannelHandlerContext targetCtx = clients.get(target);
if (targetCtx != null) {
targetCtx.writeAndFlush("@" + nickname + ": " + message + "\n");
ctx.writeAndFlush("Sent message to " + target + ": " + message + "\n");
} else {
ctx.writeAndFlush("User " + target + " not found\n");
}
} else {
ctx.writeAndFlush("Invalid command format: " + msg + "\n");
}
} else {
broadcastMessage(nickname + ": " + msg);
}
}
private void broadcastMessage(String message) {
for (ChannelHandlerContext ctx : clients.values()) {
ctx.writeAndFlush(message + "\n");
}
}
private String getNickname(ChannelHandlerContext ctx) {
for (Map.Entry<String, ChannelHandlerContext> entry : clients.entrySet()) {
if (entry.getValue() == ctx) {
return entry.getKey();
}
}
return null;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("Exception caught on " + ctx.channel().remoteAddress());
cause.printStackTrace();
ctx.close();
}
}
在客户端,可以修改发送消息的代码以支持私聊。如果用户在消息文本中输入“@昵称”,则将消息发送给指定昵称的客户端,否则将其广播给所有连接的客户端。
以下是客户端的代码示例:
public class ChatClientHandler extends SimpleChannelInboundHandler<String> {
private final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Connected to server");
System.out.print("Enter nickname: ");
String nickname = reader.readLine();
ctx.writeAndFlush(nickname + "\n");
super.channelActive(ctx);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("Exception caught on " + ctx.channel().remoteAddress());
cause.printStackTrace();
ctx.close();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Disconnected from server");
super.channelInactive(ctx);
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
reader.close();
super.channelUnregistered(ctx);
}
public void run() throws Exception {
while (true) {
String line = reader.readLine();
if (line == null || "quit".equalsIgnoreCase(line.trim())) {
break;
}
if (line.startsWith("@")) {
int index = line.indexOf(' ');
if (index >= 0) {
String target = line.substring(1, index);
ctx.writeAndFlush(line + "\n");
System.out.print("Enter message: ");
} else System.out.println("Invalid command format: " + line);
} else {
ctx.writeAndFlush(line + "\n");
}
}
}
}
注意,这只是一个简单的示例,实际使用中可能需要更复杂的逻辑来处理私聊和广播消息。另外,还需要考虑昵称的唯一性,例如当两个客户端使用相同的昵称时应该如何处理。
十、netty的心跳机制
Netty提供了内置的心跳机制,用于检测连接是否处于活动状态并维持长时间运行的连接。以下是如何使用Netty实现心跳机制:
- 在服务端和客户端的ChannelInitializer中添加IdleStateHandler。这个处理程序将在特定时间间隔内未发生读取、写入或读取写入操作时触发事件。
pipeline.addLast(new IdleStateHandler(0, 0, 60));
在上面的示例中,IdleStateHandler将在60秒内未发生任何读取或写入操作时发送一个IdleStateEvent。0表示不设置读写超时。
- 添加针对IdleStateEvent的处理程序,通常用于关闭空闲连接或发送心跳消息。例如,在服务端的ChannelInboundHandlerAdapter中,可以使用userEventTriggered方法来检测IdleStateEvent并采取相应措施:
public class HeartbeatServerHandler extends ChannelInboundHandlerAdapter {
static final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("HEARTBEAT", CharsetUtil.UTF_8));
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent e = (IdleStateEvent) evt;
if (e.state() == IdleState.READER_IDLE) {
// 关闭空闲连接
ctx.close();
} else if (e.state() == IdleState.WRITER_IDLE) {
// 发送心跳消息
ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate());
}
}
}
}
在上面的示例中,如果连接处于读取空闲状态,则关闭连接;如果连接处于写入空闲状态,则向对方发送一个预定义的心跳消息。
- 在客户端中也可以使用类似的方法来处理IdleStateEvent。例如,在客户端的ChannelInboundHandlerAdapter中,可以使用userEventTriggered方法来检测IdleStateEvent并向服务端发送心跳消息:
public class HeartbeatClientHandler extends ChannelInboundHandlerAdapter {
static final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("HEARTBEAT", CharsetUtil.UTF_8));
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate());
} else {
super.userEventTriggered(ctx, evt);
}
}
}
在上面的示例中,如果连接处于任何一种空闲状态,都会向服务端发送一个预定义的心跳消息。
请注意,这些示例只是演示如何使用Netty实现心跳机制,并不能应用于所有场景。在实际使用中,需要根据具体情况调整超时时间和心跳频率,并采取适当的措施来处理特定的空闲状态和网络异常。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报