小白也能看懂的 Netty 框架——实战(二)

小白也能看懂的 Netty 框架——实战(二)

一、任务(Task)介绍

在处理高并发网络请求时,Netty 提供了一种高效的任务调度机制,避免了阻塞主线程或导致性能瓶颈。对于一些耗时任务,Netty 使用了任务队列(task queue)来异步处理,确保系统能够继续处理其他请求。

1. 常见的任务类型

Netty 支持三种常见的任务类型:普通任务、定时任务以及跨线程调用任务。接下来,我们会详细介绍这三种任务类型及其实现方式。


2. 用户自定义普通任务

普通任务通常用于处理不需要定时触发的耗时操作。例如,我们可能会在收到客户端请求后,执行一些后台任务(如数据处理、文件操作等),然后向客户端返回结果。此类任务会被提交到 taskQueue 中异步执行。

ctx.channel().eventLoop().execute(new Runnable() {
    @Override
    public void run() {
        try {
            // 模拟耗时操作
            Thread.sleep(10 * 1000);  
            ctx.writeAndFlush(Unpooled.copiedBuffer("hello client : miaowu2", CharsetUtil.UTF_8));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
});

在上面的代码中,我们将任务提交给 eventLoop,该任务会被放入 taskQueue 中异步执行,避免了阻塞当前线程。


3. 用户自定义定时任务

定时任务用于处理那些需要延迟执行的操作。例如,定时推送消息、定时清理缓存等任务。这类任务会被提交到 scheduleTaskQueue 中,由 Netty 的事件循环线程池按时间顺序调度执行。

ctx.channel().eventLoop().schedule(new Runnable() {
    @Override
    public void run() {
        try {
            // 模拟耗时操作
            Thread.sleep(10 * 1000);
            ctx.writeAndFlush(Unpooled.copiedBuffer("hello client : miaowu2", CharsetUtil.UTF_8));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}, 5, TimeUnit.SECONDS);

在这个例子中,我们使用 schedule 方法定义了一个 5 秒后执行的任务,任务会被放入 scheduleTaskQueue,然后由 eventLoop 异步执行。


4. 跨线程调用 Channel 的方法

在一些复杂的应用场景中,可能需要在非当前 Reactor 线程中调用 Channel 的各种方法。例如,在一个推送系统中,服务器可能需要根据用户标识推送消息,而这些推送操作通常是由非 Reactor 线程发起的。

解决方案

我们可以将每个用户的 Channel 存储到一个集合中,然后在推送消息时将任务提交到该用户对应的 eventLooptaskQueuescheduleTaskQueue 中。

@Override
protected void initChannel(SocketChannel ch) throws Exception {
    ch.pipeline().addLast(new NettyServerHandler());
}

通过 NettyServerHandler 处理器,Netty 会管理每个连接的 Channel,并确保推送消息时不会发生阻塞。


二、Netty 异步模型

1. Netty 的异步 IO 操作

Netty 使用异步非阻塞 IO 操作来提高性能和吞吐量。常见的操作,如 bindwriteconnect 等,都会异步执行。调用者无法立即获得执行结果,而是通过 future-listener 机制来获取操作的结果。

image-20241205050955463

2. FutureChannelFuture

在 Netty 中,Future 是一个接口,表示某个任务的执行结果。通过 ChannelFuture,用户可以在任务完成时被通知,避免了阻塞调用。

示例:绑定端口并监听结果
ChannelFuture cf = bootstrap.bind(6668).sync();  // 绑定端口并同步等待

// 添加监听器,异步处理结果
cf.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
        if (cf.isSuccess()) {
            System.out.println("监听端口6668绑定成功");
        } else {
            System.out.println("监听失败");
        }
    }
});

// 监听通道关闭事件
cf.channel().closeFuture().sync();

在上面的代码中,ChannelFuture 通过监听器回调来处理端口绑定是否成功。通过异步模型,Netty 可以避免阻塞,允许程序继续执行其他任务。


3. 异步模型的优势

使用 Netty 的异步模型,能够避免阻塞操作,提高系统的并发处理能力。通过 future 机制,调用者不必等待结果的返回,而是可以在结果准备好时通知回调。这使得 Netty 能够高效地处理大量并发请求。


三、每个客户端独立的 Pipeline 和 Handler

在 Netty 中,每个客户端连接都会有独立的 PipelineHandler,这使得每个连接的处理逻辑相互独立,互不干扰。这样设计的好处是可以高效地处理大量并发连接,而不会导致线程安全问题。

  • Pipeline:每个 Channel 会有一个 Pipeline,它是一个责任链模式,用来处理数据的传输和处理。
  • HandlerHandler 负责执行具体的业务逻辑处理。

Netty 会根据每个 Channel 独立管理其数据流,确保不同连接之间的处理互不干扰,从而提升系统的扩展性和稳定性。

四、实战:编写一个简单的 HTTP 服务

在本节中,我们将通过一个简单的 HTTP 服务器实例,演示如何使用 Netty 框架构建一个支持基本 HTTP 请求和响应的服务。我们将实现三部分代码:HTTP 请求的处理器(TestHttpServerHandler)、服务器启动类(TestServer)和服务器的初始化类(TestserverInitializer)。

1. HTTP 请求处理器 (TestHttpServerHandler)

该处理器用于接收和处理来自客户端的 HTTP 请求。我们将处理两类请求:

  • 预检请求(OPTIONS):用于处理跨域请求。
  • 其他 HTTP 请求(如 GET 请求):用于处理常规的 GET 请求,并返回响应。
package netty.http;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;

import java.net.URI;

/**
 * HTTP 请求处理器,用于处理客户端发来的 HTTP 请求。
 */
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        // 处理预检请求 (OPTIONS),用于跨域资源共享(CORS)
        if (msg instanceof HttpRequest && ((HttpRequest) msg).method() == HttpMethod.OPTIONS) {
            // 构建一个空的响应,表示允许跨域请求
            FullHttpResponse response = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1, HttpResponseStatus.OK);

            // 设置 CORS 响应头
            response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
            response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS");
            response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type, Authorization");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);

            ctx.writeAndFlush(response);
            return;
        }

        // 处理其他 HTTP 请求(如 GET 请求)
        if (msg instanceof HttpRequest) {
            System.out.println(msg.getClass());
            System.out.println(ctx.channel().remoteAddress());

            HttpRequest httpRequest = (HttpRequest) msg;
            URI uri = new URI(httpRequest.uri());

            // 忽略 favicon.ico 请求
            if ("/favicon.ico".equals(uri.getPath())) {
                System.out.println("请求了 favicon.ico");
                return;
            }

            // 构建响应内容
            ByteBuf byteBuf = Unpooled.copiedBuffer("hello, I'm server", CharsetUtil.UTF_8);

            // 构建 HTTP 响应
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);

            // 设置响应头
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());

            // 发送响应到客户端
            ctx.writeAndFlush(response);
        }
    }
}

2. 服务器启动类 (TestServer)

该类负责启动 HTTP 服务器,并绑定到指定端口(在此示例中为 8084)。它配置了 EventLoopGroup 来处理所有的 IO 操作,并设置 ServerBootstrap 来启动服务器。

package netty.http;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * 启动 HTTP 服务器
 */
public class TestServer {
    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 处理连接请求
        EventLoopGroup workergroup = new NioEventLoopGroup(); // 处理连接之后的任务

        try {
            // 创建并配置 ServerBootstrap
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workergroup)
                    .channel(NioServerSocketChannel.class) // 设置使用 NioServerSocketChannel 作为通信方式
                    .childHandler(new TestserverInitializer()); // 初始化服务器处理逻辑

            // 绑定端口并启动服务器
            ChannelFuture sync = bootstrap.bind(8084).sync();
            sync.channel().closeFuture().sync(); // 等待服务器关闭
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            bossGroup.shutdownGracefully(); // 优雅地关闭 EventLoopGroup
            workergroup.shutdownGracefully();
        }
    }
}

3. 服务器初始化类 (TestserverInitializer)

此类用于设置服务器的 Pipeline,并添加必要的编解码器和自定义处理器。我们使用 Netty 提供的 HttpServerCodec 来处理 HTTP 编解码,并将自定义的 TestHttpServerHandler 添加到 Pipeline 中。

package netty.http;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;

/**
 * 初始化服务器的处理管道
 */
public class TestserverInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        // 获取管道
        ChannelPipeline pipeline = ch.pipeline();

        // 添加 HTTP 编解码器(HttpServerCodec 会自动处理 HTTP 的解码和编码)
        pipeline.addLast("MyHttpServerCodec", new HttpServerCodec());

        // 添加自定义的 HTTP 请求处理器
        pipeline.addLast("MyHttpServerHandler", new TestHttpServerHandler());
    }
}

4. 测试与运行

image-20241205051326671

image-20241205051336945

5. 说明

  1. CORS(跨域资源共享)处理: 服务器支持处理 OPTIONS 请求,这是跨域请求时浏览器发送的预检请求。服务器会返回合适的 CORS 响应头,允许跨域访问。
  2. 简单的 HTTP 处理: 服务器会对其他 HTTP 请求(如 GET 请求)做出响应,返回一条简单的消息 hello, I'm server
  3. Netty 的事件驱动模型: 通过 ChannelPipelineChannelHandler,Netty 可以高效地处理 HTTP 请求和响应。这种设计允许 Netty 在不阻塞的情况下,快速处理大量并发连接。

总结

  1. 任务队列:Netty 提供了三种常见的任务类型:普通任务、定时任务和跨线程任务。这些任务通过 taskQueuescheduleTaskQueue 异步执行,避免了线程阻塞。
  2. 异步 IO:Netty 的异步模型使得 IO 操作不会阻塞调用线程。通过 future-listener 机制,用户可以获取操作结果并进一步处理。
  3. 独立的 Pipeline 和 Handler:每个客户端的连接都会有独立的 PipelineHandler,从而避免了不同连接之间的干扰,提升了系统的并发处理能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值