netty实战 第二章

编写Echo服务器

一个netty服务器至少需要两个部分:

  1. 至少一个ChannelHander,实现了服务器对接收到客户端数据的处理,即业务逻辑。
  2. 引导,配置服务器的启动代码,比如配置监听端口等。

ChannelHander和业务逻辑

  1. ChannerHander是一个父接口,负责接收并响应事件通知。数据处理逻辑都包含在这些核心接口中。
  2. ChannelInboundHandler定义了响应入站事件的方法。其中ChannelInboundHandlerAdapter提供了ChannelInboundHandler的默认实现。

只需重写ChannelInboundHandlerAdapter几个方法就可实现服务端接收数据的业务逻辑

  1. channelRead() 每次传入消息时,都会调用该方法。
  2. channelReadComplete() 当消息读取到末尾时,调用该方法
  3. exceptionCaught() 发生异常时,会调用该方法
@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) {
       ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
      cause.printStackTrace();
      ctx.close();
    }
}

channelRead表示接收消息,可以看到msg转换成了ByteBuf,然后打印.每次打印完后,channelReadComplete也会调用,如果传一个超长的字符串过来,超过1024个字母长度,channelRead会调用多次,而channelReadComplete只调用一次。

启动类代码

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>");
  }
  int port = Integer.parseInt(args[0]);
  new EchoServer(port).start();
}
public void start() throws Exception {
  final EchoServerHandler serverHandler = new EchoServerHandler();
  EventLoopGroup group = new NioEventLoopGroup(); // 使用nio传输,指定NioEventLoopGroup来接受和处理新链接
  try {
    ServerBootstrap b = new ServerBootstrap();
    b.group(group)
     .channel(NioServerSocketChannel.class)  // 将channel指定为NioServerSocketChannel
     .localAddress(new InetSocketAddress(port)) 
     .childHandler(new ChannelInitializer<SocketChannel>(){
        @Override
        public void initChannel(SocketChannel ch)throws Exception {
          // 当一个新的连接产生时,一个新的子 Channel 将会被创建,而 ChannelInitializer 将会把EchoServerHandler添加到该 Channel的ChannelPipeline中
          ch.pipeline().addLast(serverHandler); 
          // 对 sync()方法的调用将导致当前Thread阻塞,一直到绑定操作完成为止
          ChannelFuture f = b.bind().sync();
          // 阻塞等待直到服务器的Channel关闭
          f.channel().closeFuture().sync();
        }finally {
          // 关闭EventLoopGroup,并释放所有的资源
          group.shutdownGracefully().sync();
        }
});

实现Echo服务器的过程

  1. EchoServerHandler 实现了业务逻辑;
  2. main()方法引导了服务器;
  3. 导过程中所需要的步骤如下:
  4. 创建一个 ServerBootstrap 的实例以引导和绑定服务器;
  5. 创建并分配一个 NioEventLoopGroup 实例以进行事件的处理,如接受新连接以及读/写数据;
  6. 指定服务器绑定的本地的 InetSocketAddress;
  7. 使用一个 EchoServerHandler 的实例初始化每一个新的 Channel;
  8. 调用 ServerBootstrap.bind()方法以绑定服务器

编写Echo客户端

与服务的一样,客户端还需实现自己的ChannelInboundHandler。通过重写SimpleChannelInboundHandler几个方法实现客户端的业务逻辑

  1. channelActive() 与服务端建立连接后调用
  2. channelRead0() 接收到服务器信息后调用
  3. exceptionCaught() 发生异常时调用
@Sharable
public class EchoClientHandler extendsSimpleChannelInboundHandler<ByteBuf> {
   @Override
   public void channelActive(ChannelHandlerContext ctx) {
     ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!",CharsetUtil.UTF_8));
   }

  @Override
  public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
    System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) {
    cause.printStackTrace();
    ctx.close();
  }
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<SocketChannel>() {
          @Override
          public 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[] args) throws Exception {
    if (args.length != 2) {
    System.err.println("Usage: " + EchoClient.class.getSimpleName() +" <host> <port>");
    return;
    }
    String host = args[0];
    int port = Integer.parseInt(args[1]);
    new EchoClient(host, port).start();
  }
}

实现Echo客户端的过程

  1. 为进行事件处理分配了一个 NioEventLoopGroup 实例,其中事件处理包括创建新的连接以及处理入站和出站数据;
  2. 为服务器连接创建了一个 InetSocketAddress 实例;
  3. 当连接被建立时,一个 EchoClientHandler 实例会被安装到(该 Channel 的)ChannelPipeline 中;
  4. 在一切都设置完成后,调用 Bootstrap.connect()方法连接到远程节点;
posted @ 2021-06-03 21:15  dxyoung  阅读(80)  评论(0)    收藏  举报