本文参考

本篇文章是对《Netty In Action》一书第二章"你的第一款 Netty 应用程序"的学习摘记,主要内容为编写 Echo 服务器和客户端

第一款应用程序的功能

Echo 客户端和服务器之间的交互十分简单:在客户端建立一个连接之后,它会向服务器发送一个或多个消息,反过来,服务器又会将每个消息回送给客户端。虽然我们的主要关注点可能是编写基于 Web 的用于被浏览器访问的应用程序,但它也充分地体现了客户端/服务器系统中典型的请求-响应交互模式。

虽然这只是一个简单的应用程序,但是它可以伸缩到支持数千个并发连接——每秒可以比普通的基于套接字的 Java 应用程序处理多得多的消息

编写Echo服务器

ChannelHandler是一个接口族的父接口,它的许多实现可以负责接收并响应事件通知。 在 Netty 应用程序中,所有的数据处理逻辑都包含在这些核心抽象的实现中

因为我们的Echo 服务器会响应传入的消息,所以需要实现ChannelInboundHandler接口,用来定义响应入站事件的方法。这个简单应用程序只需要用到少量的这些方法,所以继承 ChannelInboundHandlerAdapter类也足够了,它提供了ChannelInboundHandler的默认实现

在这里我们用到了

channelRead() — Invoked when the current Channel has read a message from the peer

channelReadComplete() —— Invoked when the last message read by the current read operation has been consumed by channelRead(ChannelHandlerContext, Object).

exceptionCaught() —— Gets called if a Throwable class was thrown.

//标示一个ChannelHandler可以被多个 Channel 安全地共享
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

  @Override

  public void channelRead(ChannelHandlerContext ctx, Object msg) {

    ByteBuf in = (ByteBuf) msg;

    //将消息记录到控制台

    System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));

    //将接收到的消息写给发送者,而不冲刷出站消息

    ctx.write(in);
  }

  @Override

  public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

    //将未冲刷的消息冲刷到远程节点,并且关闭该 Channel

    ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
      .addListener(ChannelFutureListener.CLOSE);
  }

  @Override

  public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) {

    //打印异常栈跟踪

    cause.printStackTrace();

    //关闭该Channel

    ctx.close();
  }
}

入站异常捕获机制:

每个 Channel 都拥有一个与之相关联的 ChannelPipeline,其持有一个 ChannelHandler 的实例链。在默认的情况下,ChannelHandler 会把对它的方法的调用转发给链中的下一个 ChannelHandler。因此,对于入站事件,异常将会按照入站方向流动。如果exceptionCaught()方法没有被该链中的某处实现,那么所接收的异常将会被传递到 ChannelPipeline 的尾端并被记录,所以实现异常处理的ChannelInboundHandler通常位于ChannelPipeline的最后。应用程序应该提供至少有一个实现了 exceptionCaught()方法的ChannelHandler

若没有实现任何异常处理,Netty将会通过 Warning级别的日志记录该异常到达了ChannelPipeline的尾端,未被处理,并尝试释放该异常

因为还没有讲清ChannelPipeline等概念,对异常处理机制更深的理解会在之后的文章中涉及

@Sharable注解:

该注解也有涉及ChannelPipeline的概念,当前的ChannelHandler被注解为Sharable时,可以在不存在竞争的情况下添加到多个ChannelPipeline中,否则因成员变量无法共享,需要每次都新建一个ChannelHandler实例添加到ChannelPipeline

Indicates that the same instance of the annotated ChannelHandler can be added to one or more ChannelPipeline multiple times without a race condition.

If this annotation is not specified, you have to create a new handler instance every time you add it to a pipeline because it has unshared state such as member variables.

引导服务器

上面已经实现了核心的业务逻辑,接下来需要绑定到指定端口,启动服务

public class EchoServer {

  private final int port;

  public EchoServer(int port) {

    this.port = port;
  }

  public static void main(String[] args) throws Exception {

    if (args.length != 1) {

      System.err.println("Usage: " + EchoServer.class.getSimpleName() + " <port>");

      return;
    }

    //设置端口值(如果端口参数的格式不正确,则抛出一个NumberFormatException)

    int port = Integer.parseInt(args[0]);

    //调用服务器的 start()方法

    new EchoServer(port).start();
  }

  public void start() throws Exception {

    final EchoServerHandler serverHandler = new EchoServerHandler();

    //(1) 因为使用NIO传输,所以新建NioEventLoopGroup进行事件的处理,如接受新连接以及读/ 写数据
    EventLoopGroup group = new NioEventLoopGroup();

    try {

      //(2) 创建ServerBootstrap

      ServerBootstrap b = new ServerBootstrap();

      b.group(group)

       //(3) 指定所使用的 NIO 传输 Channel

       .channel(NioServerSocketChannel.class)

       //(4) 使用指定的本地端口设置套接字地址

       .localAddress(new InetSocketAddress(port))

       //(5) 添加一个EchoServerHandler到该Channel的 ChannelPipeline

       .childHandler(new ChannelInitializer<SocketChannel>() {

         @Override

         public void initChannel(SocketChannel ch) throws Exception {

           //这里对于所有的客户端连接来说,都会使用同一个 EchoServerHandler
           //用于初始化每一个新的Channel,因为其被标注为@Sharable


           ch.pipeline().addLast(serverHandler);
         }
       });

      //(6) 异步地绑定服务器,创建一个新的Channel;调用 sync()方法阻塞等待直到绑定完成

      ChannelFuture f = b.bind().sync();

      System.out.println(EchoServer.class.getName() + " started and listening for connections on " + f.channel().localAddress());

      //(7) 获取 Channel 的CloseFuture,并且阻塞当前线程直到它完成,即Channel关闭

      f.channel().closeFuture().sync();
    } finally {

      //(8) 关闭 EventLoopGroup,释放所有的资源

      group.shutdownGracefully().sync();
    }
  }
}

编写Echo客户端

在客户端,我们使用SimpleChannelInboundHandler 类处理所有必须的任务,它是对ChannelInboundHandlerAdapter的继承

在这里我们用到了

channelActive() —— The Channel of the ChannelHandlerContext is now active

channelRead0() —— Is called for each message of type,this method will be renamed to messageReceived() in 5.0.

exceptionCaught() —— Gets called if a Throwable class was thrown.

@Sharable
//标记该类的实例可以被多个 Channel 共享
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

  @Override

  public void channelActive(ChannelHandlerContext ctx) {

    //当被通知 Channel是活跃的时候,发送一条消息

    ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
  }

  // 由服务器发送的消息可能会被分块接收,channelRead0()方法可能会被调用多次

  // 但是作为一个面向流的协议,TCP 保证了字节数组将会按照服务器发送它们的顺序被接收


  @Override

  public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {

    //记录已接收消息的转储

    System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));
  }

  @Override

  //在发生异常时,记录错误并关闭Channel,即终止连接

  public void
exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {

    cause.printStackTrace();

    ctx.close();
  }
}

SimpleChannelInboundHandler 与 ChannelInboundHandler的区别:

在客户端,当channelRead0()方法完成时,你已经有了传入消息,并且已经处理完它了。当该方法返回时,SimpleChannelInboundHandler负责释放指向保存该消息的ByteBuf的内存引用,下面是SimpleChannelInboundHandler的channelRead()实现,可见我们不需要再去重载channelRead()方法,只需重载channelRead0()方法

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    boolean release = true;
    try {
        if (acceptInboundMessage(msg)) {
            @SuppressWarnings("unchecked")
            I imsg = (I) msg;
            channelRead0(ctx, imsg); //调用channelRead0() 
        } else {
            release = false;
            ctx.fireChannelRead(msg);
        }
    } finally {
        if (autoRelease && release) {
            ReferenceCountUtil.release(msg); //释放消息
        }
    }
}

在EchoServerHandler中,你仍然需要将传入消息回送给发送者,而write()操作是异步的,channelRead()方法返回后可能仍然没有完成。直到消息在 EchoServerHandler 的 channelReadComplete()方法中,当 writeAndFlush()方法被调用时被释放,下面是ChannelInboundHandler的类注释

Be aware that messages are not released after the channelRead(ChannelHandlerContext, Object) method returns automatically. If you are looking for a ChannelInboundHandler implementation that releases the received messages automatically, please see SimpleChannelInboundHandler.

引导客户端

引导客户端类似于引导服务器,不同的是,客户端是使用主机和端口参数来连接远程地址,也就是这里的 Echo 服务器的地址,而不是绑定到一个一直被监听的端口。

我们可以在客户端和服务器上分别使用不同的传输。 例如,在服务器端使用 NIO 传输,而在客户端使用 OIO 传输

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 {

    //(1) 创建EventLoopGroup

    EventLoopGroup group = new NioEventLoopGroup();

    try {

      //(2) 创建 Bootstrap

      Bootstrap b = new Bootstrap();

      //(3) 指定 EventLoopGroup 以处理客户端事件,其中事件处理包括创建新的连接以及处理入站和出站数据

      b.group(group)

       //(4) 适用于 NIO 传输的Channel 类型

       .channel(NioSocketChannel.class)

       //(5) 置服务器的InetSocketAddress

       .remoteAddress(new InetSocketAddress(host, port))

       //(6) 在创建Channel时,向 ChannelPipeline中添加一个 EchoClientHandler实例

       .handler(new ChannelInitializer<SocketChannel>() {

          @Override

          public void initChannel(SocketChannel ch) throws Exception {

            ch.pipeline().addLast(new EchoClientHandler());
          }
       });
      //(7) 连接到远程节点,阻塞等待直到连接完成

      ChannelFuture f = b.connect().sync();

      //(8) 阻塞,直到Channel 关闭

      f.channel().closeFuture().sync();
    } finally {

      //(9)关闭线程池并且释放所有的资源

      group.shutdownGracefully().sync();
    }
  }

  public static void main(String[] args) throws Exception {

    if (args.length != 2) {

      System.err.println("Usage: " + EchoClient.class.getSimpleName() + " <host> <port>" );

      return;
    }

    final String host = args[0];

    final int port = Integer.parseInt(args[1]);

    new EchoClient(host, port).start();
  }
}

Netty将会通过 Warning级别的日志记录该异常到达了ChannelPipeline的尾端,但没有被处理, 并尝试释放该异常

Netty学习摘记 —— Netty客户端 / 服务端概览的更多相关文章

  1. Netty学习笔记(二) 实现服务端和客户端

    在Netty学习笔记(一) 实现DISCARD服务中,我们使用Netty和Python实现了简单的丢弃DISCARD服务,这篇,我们使用Netty实现服务端和客户端交互的需求. 前置工作 开发环境 J ...

  2. Netty学习摘记 —— 再谈引导

    本文参考 本篇文章是对<Netty In Action>一书第八章"引导"的学习摘记,主要内容为引导客户端和服务端.从channel内引导客户端.添加ChannelHa ...

  3. Netty学习摘记 —— 再谈ChannelHandler和ChannelPipeline

    本文参考 本篇文章是对<Netty In Action>一书第六章"ChannelHandler和ChannelPipeline",主要内容为ChannelHandle ...

  4. Netty学习——基于netty实现简单的客户端聊天小程序

    Netty学习——基于netty实现简单的客户端聊天小程序 效果图,聊天程序展示 (TCP编程实现) 后端代码: package com.dawa.netty.chatexample; import ...

  5. Netty学习摘记 —— 初步认识Netty核心组件

    本文参考 我在博客内关于"Netty学习摘记"的系列文章主要是对<Netty in action>一书的学习摘记,文章中的代码也大多来自此书的github仓库,加上了一 ...

  6. [并发并行]_[线程模型]_[Pthread线程使用模型之三 客户端/服务端模型(Client/Server]

    Pthread线程使用模型之三 客户端/服务端模型(Client/Server) 场景 1.在客户端/服务端模型时,客户端向服务端请求一些数据集的操作. 服务端执行执行操作独立的(多进程或跨网络)– ...

  7. win10操作系统下oracle11g客户端/服务端的下载安装配置卸载总结

    win10操作系统下oracle11g客户端/服务端的下载安装配置卸载总结 一:前提 注意:现在有两种安装的方式 1. oracle11g服务端(64位)+oracle客户端(32位)+plsql(3 ...

  8. 《精通并发与Netty》学习笔记(02 - 服务端程序编写)

    上节我们介绍了开发netty项目所必需的开发环境及工具的使用,这节我们来写第一个netty项目 开发步骤 第一步:打开https://search.maven.org 找到netty依赖库 第二步:打 ...

  9. Netty学习摘记 —— 深入了解Netty核心组件

    本文参考 本篇文章是对<Netty In Action>一书第三章"Netty的组件和设计"的学习摘记,主要内容为Channel.EventLoop.ChannelFu ...

随机推荐

  1. C# Control.BeginInvoke、synchronizationcontext.post、delegate.BeginInvoke的运行原理

    背景 用到的知识点 1.windows消息机制 备注:鼠标点击.键盘等事件产生的消息要放入系统消息队列,然后再分配到应用程序线程消息队列.软件PostMessage的消息直接进入应用程序线程消息队列, ...

  2. C# InterFace 接口

    接口设计方式 自顶向下 (如图所示),自底向上. 接口成员: 事件 public interface IDrawingObject { event EventHandler ShapeChanged; ...

  3. Qt:QVector

    0.说明 template <typename T> class QVector QVector是存储同一个类型T数据的模板类,其功能是动态数组,数据在其中的存储是一系列连续的存储空间. ...

  4. 转 Linux下安装Tomcat服务器和部署Web应用

    转载声明: http://www.cnblogs.com/xdp-gacl/p/4097608.html 一.上传Tomcat服务器

  5. STC8单片机波特率115200时乱码解决

    最近开发一个STC8单片机(STC8H3K32S2)项目,需要通过传口与蓝牙模块通讯,波特率高于57600后STC接收时出现乱码,但发送时正常.当将stc8串口和蓝牙串口分别接USB转串口模块通讯正常 ...

  6. vue项目环境搭建(webpack4从零搭建)--仅个人记录

    一.nodejs环境搭建 首先需要下载node.js(推荐直接官网找最新的版本),下载完成后会自带npm包管理工具,打开cmd命令行界面,输入npm -v .node -v检查node.js与npm是 ...

  7. Azure KeyVault(四)另类在 .NET Core 上操作 Secrets 的类库方法-----Azure.Security.KeyVault.Secrets

    一,引言 上一篇文章我们在 .Net Core Web 项目中添加了 "Microsoft.Azure.KeyVault" 的 Nuget 包操作 Azure KeyVault 的 ...

  8. 微信小程序码生成及canvas绘制

    吐槽:某厂的开发文档写的跟屎一样 1.后台返回accessToken,小程序请求获取小程序码 uni.request({ url: 'https://api.weixin.qq.com/wxa/get ...

  9. 服务器安装centos8提示显示器不支持输出的分辨率

    今天机房老服务器安装CENTOS8时启到到引导界面,显示器就提输出分辩率为1920*1080 60MHZ ,这怎么能支持.本想装了显卡来装,拆机一看我去不支持. 后来在网上一查才知道有解决方案. 可以 ...

  10. 【一】工程配置与电机控制part1

    前言 学校发的无刷电机: 我们准备的有刷电机: 带霍尔编码器! 电机参数: 名称:驰名电机(直流减速电机) 型号:JGA25-370 电压:12V 转数:1360r/min 做云台,核心是使用PID控 ...