Files
rikako-note/netty/netty-j.md
2024-08-31 17:07:16 +08:00

7.3 KiB
Raw Blame History

netty

netty解决的问题

当前http协议被广泛使用于client和server之间的通信。但是部分场景下http协议并不适用例如传输大文件、e-mail消息、事实的经济或游戏数据。对于该类场景http协议并无法很好的满足此时需要一个高度优化的自定义协议实现而通过netty可以及进行快速的协议实现。

netty提供了一个异步的事件驱动的网络应用框架用于快速开发一个拥有高性能、高可拓展性的协议的server和client。

netty example

Discard Server Example

如下是一个Discard Protocol的样例其会对所有接收到的数据进行丢弃并且不返回任何结果。handler方法会处理由netty产生的io事件

package io.netty.example.discard;

import io.netty.buffer.ByteBuf;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * Handles a server-side channel.
 */
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
        // Discard the received data silently.
        ((ByteBuf) msg).release(); // (3)
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

DiscardServerHandler继承了ChannelInboundHandlerAdapter,而ChannelInboundHandlerAdapter则是实现了ChannelInboundHandlerChannelInboundHandler提供了各种不同的io事件处理方法可以对这些方法进行覆盖。

在上述示例中对chnnelRead方法进行了重写channelRead方法将会在从客户端接收到数据时被调用被调用时会把从客户端接收到的消息作为参数。在上述示例中接收到的消息是ByteBuf类型。

ByteBuf是一个引用计数对象必须要通过调用release方法来显式释放。并且,handler method有责任对传入的引用计数对象进行释放操作

通常channelRead方法的重写按照如下形式

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    try {
        // Do something with msg
    } finally {
        // handler makes sure that msg passed has been released
        ReferenceCountUtil.release(msg);
    }
}

上述示例中除了重写channelRead方法外还重写了exceptionCaught方法。exceptionCaught方法会在 netty由于io error抛出异常 或是 handler method实现在处理事件时抛出异常 的场景下被调用。

在通常情况下被exceptionCaught方法捕获的异常其异常信息应该被打印到日志中且异常关联的channel应该被关闭。通过重写caughtException也可以自定义异常捕获后的实现例如在关闭channel之前向client发送error code.

netty应用程序实现示例

如下是netty实现的Discard Server示例

package io.netty.example.discard;
    
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;
    
/**
 * Discards any incoming data.
 */
public class DiscardServer {
    
    private int port;
    
    public DiscardServer(int port) {
        this.port = port;
    }
    
    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class) // (3)
             .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ch.pipeline().addLast(new DiscardServerHandler());
                 }
             })
             .option(ChannelOption.SO_BACKLOG, 128)          // (5)
             .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
    
            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(port).sync(); // (7)
    
            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
    
    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        }

        new DiscardServer(port).run();
    }
}

核心组件解析

NioEventLoopGroup

NioEventLoopGroup是一个多线程的event loop用于处理io操作。netty提供了不同的EventLoopGroup实现用于不同种类的传输。

在上述示例中使用了两个NioEventLoopGroup

  • bossboss event loop用于接收incoming connections
  • workerworker event loop用于处理accepted connection的通信

每当boss event loop接收到connection之后其会将新的连接注册到worker event loop。

event group loop中拥有的线程数以及线程如何与channel相关联可能是每个线程对应一个channel、或是一个线程管理多个channel由EventLoopGroup的实现来决定并且可以通过构造器来进行配置。

ServerBootstrap

ServerBoostrap是一个helper类用于方便的构建一个netty server。

NioServerSocketChannel

在上述使用中使用了NioServerSocketChannel来实例化一个channel改channel用于接收incoming connection。

handler注册

在上述示例中为accepted connection注册handler的示例如下所示

.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ch.pipeline().addLast(new DiscardServerHandler());
                 }
             })

在上述注册示例中每个newly accepted channel都会调用DiscardServerHandler。

ChannelInitializer是一个特殊的handler用于帮助用户为新channel配置ahndler。其initChannel为新channel的ChannelPipeline配置添加自定义的handler从而实现自定义的消息处理逻辑。

为Channel实现设置参数

通过option和childOption方法可以为channel设置参数。由于netty server基于TCP/IP故而可以指定socket option例如tcpNoDelaykeepAlive参数。

  • optionoption设置的是NioServerSocketCahnel该channel用于接收incoming connection
  • childOptionchildOption用于设置被server channel接收的channel被接收的channel为SocketChannel

为Channel绑定port

在经历完上述配置之后就可以为server绑定监听端口并启动了。