Netty框架学习(二) - 第一个简单的Netty程序

前言

上一篇文章简要地介绍了Netty框架能够做什么以及其中所涉及的一些技术细节。而本文主要是从一个最简单的Netty程序入手,这样我们才能更快地熟悉Netty这个框架。

我也在网上找过很多教程,但是很多教程基本都是基于Netty3的,Netty4的教程比较少,但是Netty3到Netty4,里面API的变化可以说是翻天覆地,而现在普遍都是用Netty4,所以大多数教程都不太适合新手学习了,包括我,在工作中也是用Netty4的,这也是没办法的事,没理由我让公司的项目转变成用Netty3吧。当然英文水平好的可以直接看Netty官网指南,或者看《Netty In Action 第五版》,可惜这么好的书没出中文版,但是这也是不能奢望太多的,就算有中文版,若翻译粗糙,那也是误人子弟的。

由于自己的英文水平也是马马虎虎,所以我也是硬着头皮看了一点点,现在我才感受到当初自己没有学好英文真是不应该啊,不过以后自己真心要恶补英语了。

从HelloWorld开始

不管学什么编程语言,框架技术,从编写HelloWorld开始无疑是最简单快捷地认识新事物的。下面是一个最简单的Netty程序,话不多说,直接上代码

HelloServer服务器代码

public class HelloServer {
    private int port;

    public HelloServer(int port) {
        this.port = port;
    }

    public void start() 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 p = ch.pipeline();
                            p.addLast(new HelloServerHandler());
                        }
                    }).option(ChannelOption.SO_KEEPALIVE, true);

            // 绑定端口, 并开始接受正到来的客户端连接
            ChannelFuture f = b.bind(port).sync();

            // 一直等待, 直到服务端socket已经关闭为止 
            f.channel().closeFuture().sync();
        } finally {
            // 释放所有的资源
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new HelloServer(5000).start();
    }
}

HelloServerHandler服务器处理程序或服务器处理器

// 这里一般写服务器端的业务逻辑代码
public class HelloServerHandler extends ChannelInboundHandlerAdapter {
    // channelActive 在与客户端连接建立并准备进行通信的时候被调用
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Hi, I'm HelloServer!");
    }

    // channelRead 在每当收到服务端发送的新数据时被调用
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

    }

    // exceptionCaught 在出现Throwable对象时, 即Netty由于IO出错或处理器处理事件抛出异常时被调用
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

HelloClient客户端代码

public class HelloClient {
    private String host;
    private int port;

    public HelloClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() 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 p = ch.pipeline();
                            p.addLast(new HelloClientHandler());
                        }
                    });

            // 绑定ip地址和端口, 并使其尝试连接服务端
            ChannelFuture f = b.connect(host, port).sync();

            // 一直等待, 直到该连接已经断开为止
            f.channel().closeFuture().sync();
        } finally {
            // 释放资源
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new HelloClient("127.0.0.1", 5000).start();
    }
}

HelloClientHandler服务器处理程序或服务器处理器

public class HelloClientHandler extends ChannelInboundHandlerAdapter {
    // channelActive 在与服务端连接建立并准备进行通信的时候被调用
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Hello, I'm HelloClient~");
    }

    // channelRead 在每当收到客户端发送的新数据时被调用
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {

    }

    // exceptionCaught 在出现Throwable对象时, 即Netty由于IO出错或处理器处理事件抛出异常时被调用
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

代码中的一些概念

这个服务端/客户端代码并没有进行互相通信,里面的所做的只是简单的打印,毕竟只是HelloWorld程序,但也有足够的东西让我们学习了。

1.EventLoopGroup

它是一个用于处理I/O操作的多线程事件循环线程池,服务端一般要用2个EventLoopGroup,boss线程池用于处理接收的连接,接收到连接后,它就会把连接信息注册到worker线程池上;而worker线程池用于处理该连接的各种事件和I/O操作。而Netty为各种传输方式提供了多种EventLoopGroup的实现,NioEventLoopGroup就是其中一种。

2.ServerBootstrap

这是一个简化建立Netty服务端操作的帮助类。

3.ChannelInitializer

它是一个特殊的处理类,专门用于配置新Channel;使用它来配置Channel就像是通过ChannelPipeline配置新Channel有一样的效果。

以上这些都是最基本的,代码中比较重要的方法它的作用就如注释所写。接下来,只需要先启动服务端,再启动客户端,你就能看到服务端和客户端都会打印相应的消息了。