Netty实战-第一个Netty

      本部分将简单介绍Netty的核心概念,核心概念就是学习Netty是如何拦截和处理异常。

1 设置开发环境

   设置开发环境的步骤包括如下三个部分:

  • 安装jdk
  • 下载netty包
  • 安装Ecplise

2 Netty客户端和服务器概述

    下面将构建一个完整的Netty服务器和客户端。一般情况下,你可能只关心编写服务器,如一个http服务器的客户端是浏览器。下面的例子中,同时实现了服务器和客户端,将会对原理更加清晰。一个Netty程序的工作图如下:

Netty实战-第一个Netty_第1张图片

  1. 客户端连接到服务器
  2. 建立连接后,发送或接收数据
  3. 服务器处理所有的客户端连接

从上图中可以看出,服务器会写数据到客户端并且处理多个客户端的并发连接。从理论上来说,限制程序性能的因素只有系统资源和JVM。

3 编写一个应答服务器

   写一个Netty服务器主要由两部分组成:

  •    配置服务器功能,如线程、接口;
  •    实现服务器处理程序,它包含业务逻辑,决定当有一个请求连接或接受数据时该做什么

3.1 启动服务器

    通过创建ServerBootstrap对象来启动服务器,然后配置这个对象的相关选项,如端口、线程模式、事件循环,并且添加逻辑处理程序用来处理业务逻辑。

package netty.in.action.p2;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class EchoServer {
    private final int port;
    public EchoServer(int port)
    {
        this.port=port;
    }

    public void start() throws Exception
    {
        EventLoopGroup group=new NioEventLoopGroup();
        try
        {
            //create ServerBootstrap instance
            ServerBootstrap b=new ServerBootstrap();
            //Specifies NIO transport,local socket address
            //Adds handler to channel pipeline
            b.group(group).channel(NioServerSocketChannel.class).localAddress(port)
                    .childHandler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(Channel ch) throws Exception
                        {
                            ch.pipeline().addLast(new EchoServerHandler());
                        }
                    });
            //Binds server,waits for server to close,and release resources
            ChannelFuture f=b.bind().sync();
            System.out.println(EchoServer.class.getName()+"started and listen on "+f.channel().localAddress());
            f.channel().closeFuture().sync();
        }
        finally {
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] agrs)
    {
        new EchoServer(65535).start();
    }
}

   从上面这个简单的服务器例子可以看出,启动服务器首先创建一个ServerBootstrap对象,因为使用NIO,所以指定NioEventLoopGroup来接受和处理新连接,指定通道类型为NioServerSocketChannel,设置InetSocketAddress让服务器监听某个端口以等待用户端连接。接下来, 当接受了一个新的连接后就需要实例化一个ChannelHandler供后续调用,同时也会创建一个子通道。这里会使用ChannelInitializer。尽管NIO是稳定的,但是还是会导致一些其它的问题,比如线程就不容易让他正确运行,通过使用EventLoopGroup,SocketChannel和ChannelInitializer,Netty设计而且抽象封装了大部分你需要做的线程工作。最后绑定服务器并且等待知道绑定完成,调用sync()方法会阻塞直到服务器完成绑定,然后服务器等待通道关闭,因为使用sync方法,所以关闭操作也会被阻塞。

  •      创建ServerBootstrap实例来引导绑定和启动服务器;
  •      创建NioEventLoopGroup对象来处理事件,如接受新连接、接受数据、写数据等等;
  •      指定InetSocketAddress,服务器监听此端口;
  •      设置childHandler执行所有的连接请求;
  •       都设置完毕了,最后低啊用ServerBootstrap。bind()方法来绑定服务器。

3.2 实现服务器业务逻辑

    

package netty.in.action.p2;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class EchoServerHandler extends ChannelInboundHandlerAdapter{
    @Override
    public void channelRead(ChannelHandlerContext ctx,Object msg) throws Exception
    {
        System.out.println("Server received: "+msg);
        ctx.write(msg);
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception
    {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) throws Exception
    {
        cause.printStackTrace();
        ctx.close();
    }
}

   Netty使用多个Channel Handler来达到对事件处理的分离,因为可以很容易添加、更新、删除业务逻辑处理handler。Handler很简单,它的每个方法都可以被重写,它的所有的方法中之后channelRead方法时必须要重写的。

3.3 捕获异常

     重写ChannelHandler中的exceptionCaught方法可以捕获服务器的异常,比如客户端连接服务器后强制关闭,服务器会抛出客户端主机强制关闭错误。通过重写该方法可以处理异常,比如发生异常后关闭ChannelHandlerContext。

4. 编写应答程序客户端

     服务器写好了,现在来写一个客户端连接服务器,应答程序的客户端包括以下几步:

  • 连接服务器
  • 写数据到服务器
  • 等待接受服务器返回相同的数据
  • 关闭连接

4.1 引导客户端

package netty.in.action.p2;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import netty.in.action.p2.EchoClientHandler;
import java.net.InetSocketAddress;
public class EchoClient {
    private final String host;
    private final int port;
    public EchoClient(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).remoteAddress(new InetSocketAddress(host,port))
                    .handler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception
                        {
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            ChannelFuture f=b.connect().sync();
            f.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] agrs) throws Exception
    {
        new EchoClient("localhost",20000).start();
    }

}

创建启动一个客户端包含以下几步:

  • 创建Bootstrap对象用来引导启动客户端
  • 创建EventLoopGroup对象并设置到Bootstrap中,EventLoopGroup可以理解为是一个线程池,这个线程池用来处理连接、接受数据、发送数据
  • 创建InetSocketAddress并设置到Bootstrap中,InetSocketAddress是指定连接的服务器地址
  • 添加一个ChannelHandler,客户端成功连接服务器后就会被执行
  • 调用Bootstrap.connect()来连接服务器
  • 最后关闭EventLoopGroup来释放资源

 4.2 实现客户端逻辑

      客户端的业务逻辑的实现依然很简单,更复杂的用法将在后面章节介绍。在编写服务器的ChannelHandler一样,在这里将自定义一个继承SimpleChannelInboundHandler的ChannelHandler来处理业务,通过重写父类的三个方法来处理感兴趣的事件:

  • channelActive():客户端连接服务器后被调用
  • channelRead0():从服务器接收到数据后调用
  • exceptionCaught():发生异常时被调用

实现代码如下:

package netty.in.action.p2;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

public class EchoClientHandler extends SimpleChannelInboundHandler{
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception
    {
        ctx.write(Unpooled.copiedBuffer("Netty Workd",CharsetUtil.UTF_8));
    }
    @Override
    protected  void channelRead0(ChannelHandlerContext ctx,ByteBuf msg) throws Exception
    {
        System.out.println("Client Received: "+ByteBufUtil.hexDump(msg.readBytes(msg.readableBytes())));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) throws Exception
    {
        cause.printStackTrace();
        ctx.close();
    }
}

    可能你会问为什么这边使用的是SimpleChannelInboundHandler而不使用ChannelInboundHandlerAdapter,主要原因是ChannelInboundHandlerAdapter在处理完消息后需要负责释放资源。在这里将调用ByteBuf.release()来释放资源。SimpleChannelInboundHandler会在完成channelRead0后释放消息,这是Netty中用于处理所有消息的ChannelHandler实现了ReferenceCounted接口达到的。

     为什么服务器中不使用SimpleChannelInboundHandler呢?因为服务器要返回相同的消息给客户端,在服务器执行完成写操作之前不能释放调用读取到的消息,因为写操作是异步的,一旦写操作完成后,Netty中会自动释放消息。

5. 编译运行echo程序客户端和服务器端



你可能感兴趣的:(Netty)