客户端将要做的事情

  • 连接到服务器
  • 发送一个或者多个消息
  • 对于每个消息,等待并接收从服务器发回的相同的消息
  • 关闭连接

    通过ChannelHandler实现客户端逻辑

    跟服务器一样,客户端也需要一个用来处理数据的ChannelInboundHandler。在这个场景下需要扩展SimpleChannelInboundHandler类以处理所有必须的任务
    主要重写的方法:

  • channelActive()—在到服务器的连接已经建立之后将被调用

  • channelRead0()—当从服务器接收到一条消息时被调用
  • exceptionCaught()—在处理过程中引发异常时被调用 ```java package cn.linguo.netty.handler;

import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil;

/**

  • @apiNote 标识该类的实例可以被多个Channel安全地共享
  • @apiNote 客户端处理
  • @author linguo / @Sharable public class EchoClientHandler extends SimpleChannelInboundHandler {

    /**

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

      @Override public void channelActive(ChannelHandlerContext ctx) throws Exception {

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

      }

      /**

    • 记录已接受消息的转储 */ @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println(“客户端接收:” + msg.toString(CharsetUtil.UTF_8)); }

      /**

    • 在发生异常时,记录错误并关闭Channel */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
  1. ChannelActive()方法将在一个连接建立时被调用,确保了数据将会被尽可能快地写入服务器,在这个场景下是一个变了字符串“Netty rocks!”的字节缓冲区<br />ChannelRead0()方法会在每次接收数据时被调用,需要注意的是,由服务器发送的消息可能会被分块接收。也就是说,如果服务器发送了5个字节,那么不能保证这5字节被一次性接收。即使对于这么少量的数据,channelRead0()方法也可能被调用多次,第一次使用持有3字节的ByteBufNetty的字节容器),第二次使用一个持有2字节的ByteBuf。作为一个面向流的协议,TCP保证了字节数组将会按照服务器发送它们的顺序被接收。
  2. <a name="Q13ql"></a>
  3. ### 思考
  4. 为什么客户端使用的是SimpleChannelInboundHandler,而不是EchoServerHandler中所使用的ChannelInboundHandlerAdapter?<br />和两个因素的相互左右有关:业务逻辑如何处理消息以及Netty如何管理资源<br />在客户端当channelRead0()方法完成时,已经有了传入的消息并且已经处理完它了。当该方法返回时,SimpleChannelInboundHandler负责释放指向保存该消息的ByteBuf的内存引用<br />在EchoServerHandler中,仍然需要将传入消息回送给发送者,而write()是异步的,直到channelRead()方法返回后可能仍然没有完成,为此EchoServerHandler扩展了ChannelInboundHandlerAdapter,其在这个时间点上不会释放消息。<br />--这个思考内容可能会比较模糊,不着急
  5. <a name="wUwfk"></a>
  6. ### 引导客户端
  7. 引导客户端类似于引导服务器,不同的是,客户端使用主机和端口参数来连接远程地址,也就是这里的Echo服务器的地址,而不是绑定到一个一直被监听的端口。
  8. ```java
  9. package cn.linguo.netty.client;
  10. import java.net.InetSocketAddress;
  11. import cn.linguo.netty.handler.EchoClientHandler;
  12. import io.netty.bootstrap.Bootstrap;
  13. import io.netty.channel.ChannelFuture;
  14. import io.netty.channel.ChannelInitializer;
  15. import io.netty.channel.EventLoopGroup;
  16. import io.netty.channel.nio.NioEventLoopGroup;
  17. import io.netty.channel.socket.SocketChannel;
  18. import io.netty.channel.socket.nio.NioSocketChannel;
  19. /**
  20. * @apiNote 引导客户端
  21. *
  22. * @author linguo
  23. *
  24. */
  25. public class EchoClient {
  26. private final String host;
  27. private final int port;
  28. public EchoClient(String host, int port) {
  29. this.host = host;
  30. this.port = port;
  31. }
  32. public void start() throws InterruptedException {
  33. EventLoopGroup group = new NioEventLoopGroup();
  34. try {
  35. // 创建BootStrap
  36. Bootstrap b = new Bootstrap();
  37. b.group(group) // 指定EventLoopGroup以处理客户端事件,需要适用于NIO的实现
  38. .channel(NioSocketChannel.class) // 适用于NIO传输的Channel类型
  39. .remoteAddress(new InetSocketAddress(host, port)) // 设置服务器的InetSockerAddress
  40. .handler(new ChannelInitializer<SocketChannel>() { // 在创建Channel时,向ChannelPipeline中添加一个EchoClientHandler实例
  41. @Override
  42. protected void initChannel(SocketChannel ch) throws Exception {
  43. ch.pipeline().addLast(new EchoClientHandler());
  44. }
  45. });
  46. // 连接到远程节点,阻塞等待直到连接完成
  47. ChannelFuture f = b.connect().sync();
  48. // 阻塞,直到Channel关闭
  49. f.channel().closeFuture().sync();
  50. } finally {
  51. // 关闭线程池并且释放所有的资源
  52. group.shutdownGracefully().sync();
  53. }
  54. }
  55. public static void main(String[] args) throws InterruptedException {
  56. if (args.length != 2) {
  57. System.err.println("Usage:" + EchoClient.class.getSimpleName() + "<host><port>");
  58. return;
  59. }
  60. String host = args[0];
  61. int port = Integer.parseInt(args[1]);
  62. new EchoClient(host, port).start();
  63. }
  64. }

主要步骤和关注要点

  • 为初始化客户端,创建了一个Bootstrap实例
  • 为进行事件处理分配了一个NioEventLoopGroup实例,其中事件处理包括创建新的连接以及处理入站和出站数据
  • 为服务器连接创建了一个InetSocketAddress实例
  • 当连接被建立时,一个EchoClientHandler实例会被安装到该Channel的ChannelPipeline中
  • 在一切都设置完成后,调用Bootstrap.connect()方法连接到远程节点

主要测试流程

根据自己项目的类型打包运行,maven直接jar包运行就好,注意port和localhost两个参数别缺失了。
依次启动Server\Client观察控制台打印的内容是否符合设计的要求