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实现心跳机制:

  1. 在服务端和客户端的ChannelInitializer中添加IdleStateHandler。这个处理程序将在特定时间间隔内未发生读取、写入或读取写入操作时触发事件。
pipeline.addLast(new IdleStateHandler(0, 0, 60));

在上面的示例中,IdleStateHandler将在60秒内未发生任何读取或写入操作时发送一个IdleStateEvent。0表示不设置读写超时。

  1. 添加针对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());
            }
        }
    }
}

在上面的示例中,如果连接处于读取空闲状态,则关闭连接;如果连接处于写入空闲状态,则向对方发送一个预定义的心跳消息。

  1. 在客户端中也可以使用类似的方法来处理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实现心跳机制,并不能应用于所有场景。在实际使用中,需要根据具体情况调整超时时间和心跳频率,并采取适当的措施来处理特定的空闲状态和网络异常。

 

posted @   开源遗迹  阅读(67)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
点击右上角即可分享
微信分享提示