Netty 入门示例详解

本文导读

开发包获取

二进制 jar 包

  • Netty 本身就是人家写好的一个 Java 的 Jar 包(库),所以开发的第一步便是要下载它,Maven 方式后面讲解。
  • 如下所示,进入 Netty 官网,然后鼠标悬停在 "Downloads" 上,点击下载对应的版本,本文以最新的 4.1.30.Final 为例。

  • 下载后压缩包大小为 20.1M,可以使用 "WinRar" 等压缩工具进行解压。

  • jar 开发包目录中提供了各个模块单独的开发包,同时也提供了对应的源码,其中有一个 netty-example-4.1.30.Final.jar 、netty-example-4.1.30.Final-sources.jar 提供了大量的示例,非常适合学习。

  • 以导入二进制类库的方式开发 Netty 时,则只需要导入如下所示的整合包即可,3.7M 包含了所有的模块,同时也提供了源码。

Maven 依赖

  • 如果使用 Maven 进行项目开发管理,则 Netty 也提供了 Maven 依赖。
  • Maven 依赖可以从 Netty 官网下载页中获取:https://netty.io/downloads.html,如下所示:

<dependencies>
  ...
  <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty</artifactId> <!-- Use 'netty-all' for 4.0 or above -->
    <version>X.Y.Z.Q</version>
    <scope>compile</scope>
  </dependency>
  ...
</dependencies>

<artifactId>netty</artifactId>:如果 Netty 是 4.0 以下版本,则 artifactId值写 netty,如果 Netty 是 4.0 及以上版本,则 写 netty-all。

<version>X.Y.Z.Q</version>:netty 版本号自己填写具体版本即可。

  • 这里同样可以选择上面最新版的 4.1.30.Final ,点击进入即可获取依赖:
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.30.Final</version>
</dependency>

Hello World

  • 出于入门阶段学习的目的,本文将新建 Java SE 项目,采用导入二进制开发包的方式,暂时不使用 Maven 管理。
  • 环境:IDEA 14 + JDK 8 + Netty4.1.30。

  • 项目建好之后,新建 lib 目录用于存放第三方开发包,注意要添加到类路径中,同时新建 classes 目录作为项目统一的编译输出目录。
  • 以一个简单的例子来对 Netty 网络编程有一个初步的了解,其中的细节可以以后慢慢消化:先开启服务器等待客户端连接,然后开启客户端,同时给服务器发送一条消息,服务器接收到消息后,回发一条消息。

服务端

·TimeServerHandler·

package com.lct.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.Date;

/**
 * Created by Administrator on 2017/5/16.
 * 用于对网络事件进行读写操作
 */
public class TimeServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        /**获取缓冲区可读字节数,并创建数组*/
        byte[] reg = new byte[buf.readableBytes()];
        /**将缓冲区字节数组复制到新建的byte数组中*/
        buf.readBytes(reg);
        /**获取请求消息*/
        String body = new String(reg, "UTF-8");
        System.out.println("The server receive  order : " + body);
        String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
        /**给客户端发送应答消息,实际是将消息放到发送缓冲数组中*/
        ctx.write(resp);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        /**将发送缓冲区中的消息全部写到SocketChannel中*/
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        /**当发生异常时,关闭ChannelHandlerContext,释放和它相关联的句柄等资源*/
        ctx.close();
    }
}

·TimeServer·

package com.lct.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.beans.beancontext.BeanContextChildComponentProxy;

/**
 * Created by Administrator on 2017/5/16.
 */
public class TimeServer {

    public void bind(int port){
        /**配置服务端的NIO线程组,专门用于网络事件处理
         * 一个用于服务端结束客户端连接
         * 一个用于用于进行SocketChannel读写*/
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            /**ServerBootstrap是Netty用于启动NIO服务端的辅助启动类*/
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,1024)
                    .childHandler(new ChildChannelHandler());
            /**绑定端口,同步等待成功*/
            ChannelFuture f = b.bind(port).sync();
            /**等待服务器监听端口关闭*/
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            /**优雅退出,释放线程池资源*/
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
        @Override
        protected void initChannel(SocketChannel arg0) throws Exception {
            arg0.pipeline().addLast(new TimeServerHandler());
        }
    }
    public static void main(String[] args) {
        int port = 8080;
        if (args!=null && args.length>0){
            try {
                port = Integer.valueOf(args[0]);
            }catch (NumberFormatException e){
                /**采用默认值*/
            }
        }
        new TimeServer().bind(port);
    }
}

客户端

·TimeClientHandler·

package com.lct.client;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.logging.Logger;

/**
 * Created by Administrator on 2017/5/17.
 * 用于对网络事件进行读写操作
 */
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
    private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName());
    private  final ByteBuf firstMessage;

    public TimeClientHandler() {
        byte[] req = "QUERY TIME ORDER".getBytes();
        firstMessage = Unpooled.buffer(req.length);
        firstMessage.writeBytes(req);
    }

    /**当客户端和服务端TCP链路建立成功之后,Netty的NIO线程会调用channelActive方法
     * 同时发送查询时间的指令给服务端,调用ChannelHandlerContext的writeAndFlush方法
     * 将 请求消息发送给服务端*/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(firstMessage);
    }

    @Override
    /**当服务端返回应答消息时,channelRead方法被调用,从Netty的ByteBuf中读取并打印应答消息*/
    public void channelRead(ChannelHandlerContext ctx, Object msg) 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("Server return Message:"+body);
    }
    /**当发生异常时,打印异常 日志,释放客户端资源*/
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        /**释放资源*/
        logger.warning("Unexpected exception from downstream : "+cause.getMessage());
        ctx.close();
    }
}

·TimeClient·

package com.lct.client;

import io.netty.bootstrap.Bootstrap;
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;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * Created by Administrator on 2017/5/16.
 */
public class TimeClient {
    public void connect(int port,String host){
        /**配置客户端NIO线程组*/
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            /**创建客户端辅助启动类,并对其配置
             * 与服务器稍微不同,这里的Channel设置为NioSocketChannel
             * 然后为其添加Handler,这里直接使用匿名内部类,实现initChannel方法
             * 作用是当创建NioSocketChannel成功后,在进行初始化时
             * 将它的ChannelHandler设置到ChannelPipeline中,用于处理网络I/O事件*/
            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 {
                            ch.pipeline().addLast(new TimeClientHandler());
                        }
                    });
            /**发起异步连接操作*/
            ChannelFuture f = b.connect(host, port);
            /**等待客户端链路关闭*/
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            /**优雅退出,释放NIO线程组*/
            group.shutdownGracefully();
        }
    }
    public static void main(String[] args) {
        int port = 8080;
        if (args!=null && args.length>0){
            try {
                port = Integer.valueOf(args[0]);
            }catch (NumberFormatException e){
                /**采用默认值*/
            }
        }
        new TimeClient().connect(port,"127.0.0.1");
    }
}

测试运行

  • 先运行服务端,再运行客户端:

自学建议

  • 对于和我一样自学的兄弟姐妹来说,除了百度以外,还可以参考下载包中更多官方示例,如下所示:

············下一篇《传统 BIO 编程

猜你喜欢

转载自blog.csdn.net/wangmx1993328/article/details/83036285