Netty入门系列(1) --使用Netty搭建服务端和客户端

引言

前面咱们介绍了网络一些基本的概念,虽说这些很难吧,可是至少要作到理解吧。有了以前的基础,咱们来正式揭开Netty这神秘的面纱就会简单不少。编程

服务端

public class PrintServer {

    public void bind(int port) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();                     //1
        EventLoopGroup workerGroup = new NioEventLoopGroup();                   //2
        try {
            ServerBootstrap b = new ServerBootstrap();                          //3
            b.group(bossGroup, workerGroup)                                     //4                                         
                    .channel(NioServerSocketChannel.class)                      //5
                    .option(ChannelOption.SO_BACKLOG, 1024)                     //6
                    .childHandler(new ChannelInitializer<SocketChannel>() {     //7
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new PrintServerHandler());
                        }
                    });

            ChannelFuture f = b.bind(port).sync();              //8
            
            f.channel().closeFuture().sync();                   //9
        } finally {
            // 优雅退出,释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }


    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        int port = 8080;
        new TimeServer().bind(port);
    }
}

咱们来分析一下上面的这段代码(下面的每一点对应上面的注释)数组

1~2:首先咱们建立了两个NioEventLoopGroup实例,它是一个由Netty封装好的包含NIO的线程组。为何建立两个?我想通过前面的学习你们应该都清楚了。对,由于Netty的底层是IO多路复用,bossGroup 是用于接收客户端的链接,原理就是一个实现的Selector的Reactor线程。而workerGroup用于进行SocketChannel的网络读写。缓存

3:建立一个ServerBootstrap对象,能够把它想象成Netty的入口,经过这类来启动Netty,将所须要的参数传递到该类当中,大大下降了的开发难度。网络

4:将两个NioEventLoopGroup实例绑定到ServerBootstrap对象中。异步

5:建立Channel(典型的channel有NioSocketChannel,NioServerSocketChannel,OioSocketChannel,OioServerSocketChannel,EpollSocketChannel,EpollServerSocketChannel),这里建立的是NIOserverSocketChannel,它的功能能够理解为当接受到客户端的链接请求的时候,完成TCP三次握手,TCP物理链路创建成功。并将该“通道”与workerGroup线程组的某个线程相关联。ide

6:设置参数,这里设置的SO_BACKLOG,意思是客户端链接等待队列的长度为1024.oop

7:创建链接后的具体Handler。就是咱们接受数据后的具体操做,例如:记录日志,对信息解码编码等。学习

8:绑定端口,同步等待成功编码

9:等待服务端监听端口关闭线程

绑定该服务端的Handler

public class PrintServerHandler extends ChannelHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
        throws Exception {
    ByteBuf buf = (ByteBuf) msg;                                        //1
    byte[] req = new byte[buf.readableBytes()]; 
    buf.readBytes(req); //将缓存区的字节数组复制到新建的req数组中
    String body = new String(req, "UTF-8");
    System.out.println(body);
    String response= "打印成功";
    ByteBuf resp = Unpooled.copiedBuffer(response.getBytes());                      
    ctx.write(resp);                                                    //2
    }   

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    ctx.flush();                                                        //3
    }


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

PrintServerHandler 继承 ChannelHandlerAdapter ,在这里它的功能为 打印客户端发来的数据而且返回客户端打印成功。

咱们只须要实现channelRead,exceptionCaught,前一个为接受消息具体逻辑的实现,后一个为发生异常后的具体逻辑实现。

1:咱们能够看到,接受的消息被封装为了Object ,咱们将其转换为ByteBuf ,前一章的讲解中也说明了该类的做用。咱们须要读取的数据就在该缓存类中。

2~3:咱们将写好的数据封装到ByteBuf中,而后经过write方法写回到客户端,这里的3调用flush方法的做用为,防止频繁的发送数据,write方法并不直接将数据写入SocketChannel中,而是把待发送的数据放到发送缓存数组中,再调用flush方法发送数据。

客户端

public class PrintClient {

    public void connect(int port, String host) throws Exception {
    EventLoopGroup group = new NioEventLoopGroup();                 //1
    try {
        Bootstrap b = new Bootstrap();                              //2
         b.group(group)                                             //3
            .channel(NioSocketChannel.class)                        //4
            .option(ChannelOption.TCP_NODELAY, true)                //5
            .handler(new ChannelInitializer<SocketChannel>() {      //6
            @Override
            public void initChannel(SocketChannel ch)               
                throws Exception {
                ch.pipeline().addLast(new PrintClientHandler());
            }
            });

        ChannelFuture f = b.connect(host, port).sync();             //7
        f.channel().closeFuture().sync();                           //8
    } finally {
        // 优雅退出,释放NIO线程组
        group.shutdownGracefully();
    }
    }

    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
    int port = 8080;
    new TimeClient().connect(port, "127.0.0.1");
    }
}

咱们继续来分析一下上面的这段代码(下面的每一点对应上面的注释)

1:区别于服务端,咱们在客户端只建立了一个NioEventLoopGroup实例,由于客户端你并不须要使用I/O多路复用模型,须要有一个Reactor来接受请求。只须要单纯的读写数据便可

2:区别于服务端,咱们在客户端只须要建立一个Bootstrap对象,它是客户端辅助启动类,功能相似于ServerBootstrap。

3:将NioEventLoopGroup实例绑定到Bootstrap对象中。

4:建立Channel(典型的channel有NioSocketChannel,NioServerSocketChannel,OioSocketChannel,OioServerSocketChannel,EpollSocketChannel,EpollServerSocketChannel),区别与服务端,这里建立的是NIOSocketChannel.

5:设置参数,这里设置的TCP_NODELAY为true,意思是关闭延迟发送,一有消息就当即发送,默认为false。

6:创建链接后的具体Handler。注意这里区别与服务端,使用的是handler()而不是childHandler()。handler和childHandler的区别在于,handler是接受或发送以前的执行器;childHandler为创建链接以后的执行器。

7:发起异步链接操做

8:当代客户端链路关闭

绑定该客户端的Handler

public class PrintClientHandler extends ChannelHandlerAdapter {

    private static final Logger logger = Logger
        .getLogger(TimeClientHandler.class.getName());

    private final ByteBuf firstMessage;

    /**
     * Creates a client-side handler.
     */
    public TimeClientHandler() {
    byte[] req = "你好服务端".getBytes();
    firstMessage = Unpooled.buffer(req.length);                                 //1
    firstMessage.writeBytes(req);

    }

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

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)              //3
        throws Exception {
    ByteBuf buf = (ByteBuf) msg;    
    byte[] req = new byte[buf.readableBytes()];
    buf.readBytes(req);
    String body = new String(req, "UTF-8");
    System.out.println("服务端回应消息 : " + body);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {   //4
    // 释放资源
    System.out.println("Unexpected exception from downstream : "
        + cause.getMessage());
    ctx.close();
    }
}

PrintClientHandler 继承 ChannelHandlerAdapter ,在这里它的功能为 发送数据并打印服务端发来的数据。

咱们只须要实现channelActive,channelRead,exceptionCaught,第一个为创建链接后当即执行,后两个与一个为接受消息具体逻辑的实现,另外一个为发生异常后的具体逻辑实现。

1:将发送的信息封装到ByteBuf中。

2:发送消息。

3:接受客户端的消息并打印

4:发生异常时,打印异常信息,释放客户端资源

总结

这是一个入门程序,对应前面所讲的I/O多路复用模型以及NIO的特性,能颇有效的理解该模式的编程方式。若是这几段代码看着很费劲,那么能够看看以前博主的Netty基础系列。

若是博主哪里说得有问题,但愿你们提出来,一块儿进步~

相关文章
相关标签/搜索