一个简单的Netty例子

从今天开始,记录一下Netty学习的一些笔记和总结。下面从一个简单的Netty例子开始。

Netty客户端和服务端概览

从下图可以看出,我们要编写的是Echo的客户端和服务端。其中有多个客户端同时连接到同一台服务器。在客户端创建一个连接到服务端之后,可以向其发送一个或多个消息;之后,服务端又会将每个消息回传给客户端。


服务端实现

下面我们看一下精简后的服务端实现(下方代码参考Netty官方Demo,并做了简化)。服务端代码主要包括了2个类:EchoServerHandler和EchoServer,其中EchoServerHandler包含了我们在接收到客户端的请求后,写回给客户端的程序逻辑;EchoServer可以认为是一个粘合或引导,其组装了处理业务逻辑的EchoServerHandler,及相关的系统配置参数等。下面我们看下详细的代码实现:

EchoServerHandler

/**
 * Handler implementation for the echo server.
 */
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

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

上面的EchoServerHandler继承自ChannelInboundHandlerAdapter(表示入站处理器),在读取到客户端的消息之后,又将消息原样写回给了客户端。

EchoServer

/**
 * Echoes back any received data from a client.
 */
public final class EchoServer {

    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        final EchoServerHandler serverHandler = new EchoServerHandler();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
              //两种设置keepalive风格
             .childOption(ChannelOption.SO_KEEPALIVE, true)
             .childOption(NioChannelOption.SO_KEEPALIVE, true)

              //切换到unpooled的方式之一
             .childOption(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(serverHandler);
                 }
             });
            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();
            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

从上面代码可以看出,我们首先创建了2个EventLoopGroup:bossGroup和workerGroup。然后使用Netty服务端粘合组件:ServerBootstrap将bossGroup、workerGroup传入,然后设置相关其他的参数。设置的参数其中就包含了业务处理逻辑EchoServerHandler,其设置是在 .childHandler中设置的。因为ServerSocketChannel会创建子的SocketChannel,子channel会负责处理客户端的请求和发送应答数据。

ServerBootstrap的参数设置完成之后,就可以调用 b.bind(PORT).sync() 启动服务端程序了。

客户端实现

客户端实现主要包括了2个类:EchoClientHandler和EchoClient。其中EchoClientHandler包含了向Server端发送请求的逻辑,也就是在channelActive之后向Server端发送请求;EchoClient类似于EchoServer,是一个引导程序。

EchoClientHandler

/**
 * Handler implementation for the echo client.  It initiates the ping-pong
 * traffic between the echo client and server by sending the first message to
 * the server.
 */
public class EchoClientHandler extends ChannelInboundHandlerAdapter {

    private final ByteBuf firstMessage;

    /**
     * Creates a client-side handler.
     */
    public EchoClientHandler() {
        firstMessage = Unpooled.buffer(EchoClient.SIZE);
        for (int i = 0; i < firstMessage.capacity(); i ++) {
            firstMessage.writeByte((byte) i);
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ctx.writeAndFlush(firstMessage);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
       ctx.flush();
    }

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

上面的客户端业务代码,在创建Handler时初始化了ByteBuf,并且写入了数据。然后在channelActive的时候完成了向Server端发送消息。在客户端接收到服务端发送的数据后,又向服务端写入了数据,然后一直循环ping-pong。

EchoClient

/**
 * Sends one message when a connection is open and echoes back any received
 * data to the server.  Simply put, the echo client initiates the ping-pong
 * traffic between the echo client and server by sending the first message to
 * the server.
 */
public final class EchoClient0 {

    static final String HOST = System.getProperty("host", "127.0.0.1");
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure the client.
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .option(ChannelOption.TCP_NODELAY, true)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoClientHandler());
                 }
             });
            // Start the client.
            ChannelFuture f = b.connect(HOST, PORT).sync();
            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down the event loop to terminate all threads.
            group.shutdownGracefully();
        }
    }
}


参考:Netty源码