一,首先引入依赖

<dependencies>
         <!-- Netty依赖包
         https://mvnrepository.com/artifact/io.netty/netty-all -->
         <dependency>
             <groupId>io.netty</groupId>
             <artifactId>netty-all</artifactId>
             <version>4.1.25.Final</version>
         </dependency>        <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <version>1.16.22</version>
         </dependency>
     </dependencies>    <build>
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
                     <source>1.8</source>
                     <target>1.8</target>
                 </configuration>
             </plugin>
             <plugin>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
                     <useSystemClassLoader>false</useSystemClassLoader>
                 </configuration>
             </plugin>
         </plugins>
     </build>

二,编写服务器端

/**
  * 配置服务器的启动代码。最少需要设置服务器绑定的端口,用来监听连接请求。
  *
  */
 public class LiuServer {    private final int port;
    public LiuServer(int port) {
         this.port = port;
     }    public static void main(String[] args) throws Exception {
         //设置端口值
         int port = 8888;
         //呼叫服务器的 start() 方法
         new LiuServer(port).start();
     }    public void start() throws Exception {
         //Netty内部都是通过线程在处理各种数据,EventLoopGroup就是用来管理调度他们的,注册Channel,管理他们的生命周期。
         //NioEventLoopGroup是一个处理I/O操作的多线程事件循环
         //bossGroup作为boss,接收传入连接
         //因为bossGroup仅接收客户端连接,不做复杂的逻辑处理,为了尽可能减少资源的占用,取值越小越好.
         NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
         //workerGroup作为worker,处理boss接收的连接的流量和将接收的连接注册进入这个worke
         NioEventLoopGroup workerGroup = new NioEventLoopGroup();
         try {
             //ServerBootstrap负责建立服务端
             //你可以直接使用Channel去建立服务端,但是大多数情况下你无需做这种乏味的事情
             ServerBootstrap b = new ServerBootstrap();
             b.group(bossGroup, workerGroup)
                     //指定使用NioServerSocketChannel产生一个Channel用来接收连接  指定NIO的模式 NioServerSocketChannel对应TCP, NioDatagramChannel对应UDP
                     .channel(NioServerSocketChannel.class)
                     //设置 socket 地址使用所选的端口
                     .localAddress(new InetSocketAddress(port))
                     //ChannelInitializer用于配置一个新的Channel
                     //用于向你的Channel当中添加ChannelInboundHandler的实现
                     //添加 EchoServerHandler 到 Channel 的 ChannelPipeline
                     .childHandler(new ChannelInitializer<SocketChannel>() {
                         @Override
                         public void initChannel(SocketChannel ch) {
                             //ChannelPipeline用于存放管理ChannelHandel
                             //ChannelHandler用于处理请求响应的业务逻辑相关代码
                             // //配置通信数据的处理逻辑, 可以addLast多个
                             ch.pipeline().addLast(
                                     new LiuServerHandler());
                         }
                     })//对Channel进行一些配置
                     //注意以下是socket的标准参数
                     //BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。
                     //Option是为了NioServerSocketChannel设置的,用来接收传入连接的  设置TCP缓冲区
                     .option(ChannelOption.SO_BACKLOG, 128)
                     //保持连接
                     //是否启用心跳保活机制。在双方TCP套接字建立连接后(即都进入ESTABLISHED状态)并且在两个小时左右上层没有任何数据传输的情况下,这套机制才会被激活。
                     //childOption是用来给父级ServerChannel之下的Channels设置参数的
                     .childOption(ChannelOption.SO_KEEPALIVE, true);
             //绑定的服务器;sync 等待服务器关闭 ,也可以在该处再绑定端口。 bind返回future(异步), 加上sync阻塞在获取连接处
             ChannelFuture f = b.bind().sync();
             System.out.println(LiuServer.class.getName() + " 服务启动开始监听端口:" + f.channel().localAddress());
             //sync()会同步等待连接操作结果,用户线程将在此wait(),直到连接操作完成之后,线程被notify(),用户代码继续执行
             //closeFuture()当Channel关闭时返回一个ChannelFuture,用于链路检测
             f.channel().closeFuture().sync();
         } finally {
             //释放 channel 和 块,直到它被关闭
             bossGroup.shutdownGracefully();
             workerGroup.shutdownGracefully();
         }
     }}

 

编写服务器端处理类

/**
  * 处理服务器端通道
  * ChannelInboundHandlerAdapter继承自ChannelHandlerAdapter,实现了ChannelInboundHandler接口
  * ChannelInboundHandler接口提供了不同的事件处理方法,可进行重写
  * 实现了服务器的业务逻辑,决定了连接创建后和接收到信息后该如何处理
  *
  * @author chenhx
  * @version DiscardServerHandler.java, v 0.1 2018-07-13 下午 4:05
  *///Sharable注解 标识这类的实例之间可以在 channel 里面共享
 @ChannelHandler.Sharable
 public class LiuServerHandler extends ChannelInboundHandlerAdapter {
     /**
      * 每个信息入站都会调用
      * 接收数据进行处理
      * 事件处理程序方法。每当从客户端接收到新数据时,使用该方法来接收客户端的消息。 在此示例中,接收到的消息的类型为ByteBuf。
      *
      * @param ctx
      * @param msg
      * @throws Exception
      */
     @Override
     public void channelRead(ChannelHandlerContext ctx, Object msg) {
         ByteBuf in = (ByteBuf) msg;
         System.out.println("客户端发来消息: " + in.toString(CharsetUtil.UTF_8));
         //ChannelHandlerContext提供各种不同的操作用于触发不同的I/O时间和操作
         //调用write方法来逐字返回接收到的信息
         //这里我们不需要调用释放,因为Netty会在写的时候自动释放
         //只调用write是不会释放的,它会缓存,直到调用flush
         //将所接收的消息返回给发送者。注意,这还没有冲刷数据
         ctx.write(in);
     }    /**
      *  当前读操作读取的最后一个消息被channelRead()方法消费时调用. 如果ChannelOption.AUTO_READ 属性被设置为off,
      *  不会再尝试从当前channel中读取inbound数据, 直到ChannelHandlerContext.read()方法被调用.
      * @param ctx
      * @throws Exception
      */
     @Override
     public void channelReadComplete(ChannelHandlerContext ctx) {
         System.out.println("channel 通道读取完成");
         //冲刷所有待审消息到远程节点。关闭通道后,操作完成
         //第一种写法 写一个空的buf,并刷新写出区域。完成后关闭sock channel连接。
         ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                 .addListener(ChannelFutureListener.CLOSE);
         //ctx.flush(); // 第二种方法:在client端关闭channel连接,这样的话,会触发两次channelReadComplete方法。
         //ctx.flush().close().sync(); // 第三种:改成这种写法也可以,但是这中写法,没有第一种方法的好。
     }    /**
      * 读操作时捕获到异常时调用
      *
      * @param ctx
      * @param cause
      */
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx,
                                 Throwable cause) {
         //打印异常堆栈跟踪
         cause.printStackTrace();
         //关闭通道
         ctx.close();
     } }

三,编写客户端类

/**
  * 通过host和port连接服务器。
  *
  */
 public class LiuClient {    private final String host;
     private final int port;    public LiuClient(String host, int port) {
         this.host = host;
         this.port = port;
     }    public static void main(String[] args) throws Exception {
         final String host = "127.0.0.1";
         //端口与服务器端匹配
         final int port = 8888;        new LiuClient(host, port).start();
     }    public void start() throws Exception {
         EventLoopGroup group = new NioEventLoopGroup();
         try {
             Bootstrap b = new Bootstrap();
             //指定 EventLoopGroup 来处理客户端事件。由于我们使用 NIO 传输,所以用到了 NioEventLoopGroup 的实现
             b.group(group)
                     .channel(NioSocketChannel.class)
                     //设置服务器的地址和端口
                     .remoteAddress(new InetSocketAddress(host, port))
                     //当建立一个连接和一个新的通道时,创建添加到 EchoClientHandler 实例 到 channel pipeline
                     .handler(new ChannelInitializer<SocketChannel>() {
                         @Override
                         public void initChannel(SocketChannel ch)
                                 throws Exception {
                             ch.pipeline().addLast(
                                     new LiuClientHandler());
                         }
                     });            //连接到远程;等待连接完成 也可以在这里设置服务器地址和端口
             ChannelFuture f = b.connect().sync();
             //阻塞直到 Channel 关闭
             f.channel().closeFuture().sync();
         } finally {
             //调用 shutdownGracefully() 来关闭线程池和释放所有资源
             group.shutdownGracefully().sync();
         }
     }
 }

客户端处理类

@ChannelHandler.Sharable
 public class LiuClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
     /**
      * 服务器的连接被建立后调用
      * 建立连接后该 channelActive() 方法被调用一次
      *
      * @param ctx
      */
     @Override
     public void channelActive(ChannelHandlerContext ctx) {
         //当被通知该 channel 是活动的时候就发送信息
         ctx.writeAndFlush(Unpooled.copiedBuffer("Hello Netty! " + Long.toString(System.currentTimeMillis()),
                 CharsetUtil.UTF_8));
     }    /**
      * 从服务器接收到数据调用
      *
      * @param ctx
      * @param in
      */
     @Override
     public void channelRead0(ChannelHandlerContext ctx,
                              ByteBuf in) {
         System.out.println("服务器发来消息: " + in.toString(CharsetUtil.UTF_8));
     }    /**
      * 捕获异常时调用
      *
      * @param ctx
      * @param cause
      */
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx,
                                 Throwable cause) {
         //记录错误日志并关闭 channel
         cause.printStackTrace();
         ctx.close();
     }
 }