接下来我们将展示如何构建一个基于Netty的客户端和服务器,程序很简单:客户端将消息发送给服务器,而服务器再将消息回送给客户端,这将是一个对你而言很重要的第一个netty的实践经验。

1、设置开发环境

编译和运行,我们需要准备JDK和Apache Maven工具,这里建议大家使用Java的集成开发环境(IDE)。

如果你已经安装了JDK,那么可以略过此步。

否则,请从http://java.com/en/download/manual.jsp 处获取JDK第8版,请下载JDK,而不是Java运行环境(JRE),其仅仅可以运行Java应用程序,但不够编译它们。

有关安装说明:

——将环境变量JAVA_HOME设置为你的JDK安装位置

——将%JAVA_HOME%\bin添加到你的执行路径

下面是使用最广泛的Java IDE,都可以免费获取

——Eclipse——www.eclipse.org

——NetBeans——https://netbeans.org

——Intellij IDEA Community Edition——www.jetbrains.com

有关MAVEN的安装也与Java JDK安装类似

2、Netty客户端/服务器概览

图2-1展示了我们将要编写的Echo客户端和服务器应用程序,即使可能我们要编写基于Web的用于被浏览器访问的应用程序,但是通过同时实现客户端和服务器,你一定能更加全面地理解Netty的API。

虽然图中也展示了我们一开始所说的多个客户端,所能够支持的客户端数量,在理论上,仅受限于系统的可用资源(以及所使用的JDK版本可能会施加的限制)。

Echo客户端和服务器之间的交互非常简单,其本身也充分体现了客户端/服务器系统中典型的请求-响应交互模式。

3、编写Echo服务器

所有的Netty服务器都需要以下两个部分:

——至少一个ChannelHandler——该组件实现了服务器对从客户端接收的数据的处理,即它的业务逻辑。

——引导——配置服务器的启动代码,将服务器绑定到它要监听连接请求的端口上。

我们的服务器会响应传入的消息,需要实现ChannelInboundHandler接口,用来定义响应入站事件的方法,对于此应用而言只需要用到少量的这些方法,所以继承ChannelInboundHandlerAdapter类就足够了,它提供了ChannelInboundHandler的默认实现。

——channelRead():对于每个传入的消息都要调用

——channelReadComplete():通知ChannelInboundHandler最后一次对channelRead()的调用时当前批量读取中的最后一条消息

——exceptionCaught():在读取操作期间,有异常跑出会调用

代码清单2-1,展示Echo服务器的ChannelHandler实现EchoServerHandler。

  1. @ChannelHandler.Sharable //标示一个ChannelHandler可以被多个Channel安全地共享
  2. public class EchoServerHandler extends ChannelInboundHandlerAdapter{
  3. @Override
  4. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  5. ByteBuf in = (ByteBuf)msg;
  6. System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));//将消息记录到控制台
  7. ctx.write(in);//将接受到的消息写给发送者,而不冲刷出站消息·
  8. }
  9. @Override
  10. public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
  11. //将未决消息冲刷到远程节点,并且关闭该Channel
  12. ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
  13. }
  14. @Override
  15. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  16. cause.printStackTrace();//打印异常栈跟踪
  17. ctx.close();//关闭该Channel
  18. }
  19. }

ChannelInboundHandlerAdapter有一个直观的API,并且它的每个方法都可以被重写以挂钩到事件生命周期的恰当点上。因为需要处理所有接收到的数据,所以重写channelRead()方法

重写exceptionCaught()方法允许你对Throwable的任何子类做出反应,我们代码中记录了异常并关闭了连接。

如果不捕获异常,会发生什么呢?

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

除了ChannelInboundHandlerAdapter之外,还有很多需要学习的ChannelHandler的子类型和实现。

——针对不同类型的事件调用ChannelHandler

——应用程序通过实现或者扩展ChannelHandler来挂钩到事件的生命周期,并且提供自定义的应用程序逻辑

——在架构上,ChannelHandler有助于保持业务逻辑与网络处理代码的分离,这简化了开发过程,因为代码必须不断地演化以响应不断变化的需求

在讨论过EchoServerHandler实现的核心业务逻辑之后,我们现在可以讨论引导服务器本身的过程:

——绑定到服务器将在其上监听并接受请求的端口

——配置Channel,以将有关的入站消息通知给EchoServerHandler实例

传输:

在网络协议的标准多层视图中,传输层提供了端到端的或者主机到主机的通信服务。

因特网通信是建立在TCP传输之上的,除了一些由Java NIO实现提供的服务器端性能增强之外,NIO传输大多数时候指的就是TCP传输。

  1. public class EchoServer {
  2. private final int port;
  3. public EchoServer(int port) {
  4. this.port = port;
  5. }
  6. public static void main(String[] args) throws Exception{
  7. if (args.length != 1){
  8. System.out.println("Usage: " + EchoServer.class.getSimpleName() + " <port>");
  9. return;
  10. }
  11. int port = Integer.parseInt(args[0]);//设置端口值(如果端口参数的格式不正确,则抛出一个NumberFormatException)
  12. new EchoServer(port).start();//调用服务器的start()方法
  13. }
  14. public void start() throws Exception{
  15. final EchoServerHandler serverHandler = new EchoServerHandler();
  16. EventLoopGroup group = new NioEventLoopGroup();//创建EventLoopGroup
  17. try {
  18. ServerBootstrap b = new ServerBootstrap();//创建ServerBootstrap
  19. b.group(group)
  20. .channel(NioServerSocketChannel.class)//指定所使用的NIO传输Channel
  21. .localAddress(new InetSocketAddress(port))//使用指定的端口设置套接字地址
  22. .childHandler(new ChannelInitializer<SocketChannel>() {//添加一个EchoServerHandler到子Channel的ChannelPipeline
  23. @Override
  24. protected void initChannel(SocketChannel socketChannel) throws Exception {
  25. //EchoServerHandler被标注为@Shareable,所以我们可以总是使用同样的实例
  26. socketChannel.pipeline().addLast(serverHandler);
  27. }
  28. });
  29. ChannelFuture f = b.bind().sync();//异步地绑定服务器,调用sync()方法阻塞等待直到绑定完成
  30. f.channel().closeFuture().sync();//获取Channel的closeFuture,并且阻塞当前线程直到它完成
  31. }finally {
  32. group.shutdownGracefully().sync();//关闭EventLoopGroup释放所有的资源
  33. }
  34. }
  35. }

我们创建了一个ServerBootstrap实例,因为正在使用NIO传输,指定NioEventLoopGroup来接收和处理新的连接,并且将Channel的类型指定为NioServerSocketChannel。在此之后,将本地地址设置为一个具有选定端口的InetSocketAddress,服务器将绑定到这个地址以监听新的连接请求。

使用一个特殊的类——ChannelInitializer。当一个新的连接被接受时,一个新的子Channel将会被创建,而ChannelInitializer将会把一个你的EchoServerHandler的实例添加到该Channel的ChannelPipeline中,即这个ChannelHandler将会收到有关入站消息的通知。

虽然NIO是可伸缩的,但是其关于多线程处理的配置并不简单。Netty的设计封装了大部分的复杂性。

绑定服务器,并等待绑定完成。(对sync()方法的调用将导致当前Thread阻塞,一直到绑定操作完成为止)该应用程序将会阻塞等待直到服务器的Channel关闭(因为我的Channel的CloseFuture上调用sync()方法),之后我们可以关闭EventLoopGroup,并释放所有的资源,包括所有被创建的线程。

使用了NIO,因为得益于它的可扩展性和彻底的异步性,它是目前使用最广泛的传输,可以使用一个不同的传输实现,当然如果你想要在自己的服务器中使用OIO传输,将需要指定OioServerSocketChanne和OioEventLoopGroup。

让我们回顾一下服务器中的重要步骤:

——EchoServerHandler实现了业务逻辑

——main()方法引导了服务器

引导过程中所需的步骤:

——创建一个ServerBootstrap的实例以引导和绑定服务器

——创建并分配一个NioEventLoopGroup实例以进行事件的处理,如接受新连接以及读/写数据

——指定服务器绑定的本地的InetSocketAddress

——使用一个EchoServerHandler的实例初始化每一个新的Channel

——调用ServerBootstrap.bing()方法以绑定服务器

4、编写Echo客户端

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

编写客户端所涉及的两个主要代码部分也是业务逻辑和引导

客户端将拥有一个用来处理数据的ChannelInboundHandler,在这个场景下,将扩展SimpleChannelInboundHandler类以处理所有必须的任务。如代码清单2-3,要求重写下面的方法:

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

——channelRead():当从服务器接收到一条消息时被调用

——exceptionCaught():在处理过程中引发异常时被调用

  1. @ChannelHandler.Sharable //标记该类的实例可以被多个Channel共享
  2. public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf>{
  3. @Override
  4. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  5. //当被通知Channel是活跃的时候,发送一条消息
  6. ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
  7. }
  8. @Override
  9. protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
  10. //记录已接收消息的转储
  11. System.out.println("Client received: " + byteBuf.toString(CharsetUtil.UTF_8));
  12. }
  13. /**
  14. * 在发生异常时,记录错误并关闭Channel
  15. * @param ctx
  16. * @param cause
  17. * @throws Exception
  18. */
  19. @Override
  20. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  21. cause.printStackTrace();
  22. ctx.close();
  23. }
  24. }

重写了channelActive()方法,其将在一个连接建立时被调用,这确保了数据将会被尽可能快地写入服务器,其在这个场景下是一个编码了字符串“Netty rocks!”的字符串缓存区。

重写了channelRead0()方法,每当接收数据时,都会调用这个方法。需要注意的是,由服务器发送的消息可能会被分块接收。如果服务器发送了5字节,那么不能保证这5字节会被一次性接收。

即使是对于这么少量的数据,channelRead0()方法也可能会被调用两次。作为一个面向流的协议,TCP保证了字节数组将会按照服务器发送它们的顺序被接收

重写了exceptionCaught()。如同在EchoServerHandler(见代码清单2-2)中所示,记录Throwable,关闭Channel,在这个场景下,终止到服务器的连接。

SimpleChannelInboundHandler与ChannelLnboundHandler

为什么我们在客户端使用的是SimpleChannelInboundHandler,而不是在EchoServerHandler中所使用的ChannelInboundHandlerAdapter呢?这两个因素的相互作用有关:业务逻辑如何处理消息以及Netty如何管理资源

在客户端,当channelRead()方法完成时,你已经有了传入消息,并且已经处理完它了。当该方法返回时,SimpleChannelInboundHandler负责释放指向保存该消息的ByteBuf的内存引用。

在EchoServerHandler中,你仍然需要将传入消息回送给发送者,而write()操作时异步的,直到channelRead()方法返回后可能仍然没有完成,为此,EchoServerHandler扩展了ChannelInboundHandlerAdapter,其在这个时间点上不会释放消息。

消息在EchoServerHandler的channelReadComplete()方法中,当writeAndFlush()方法被调用时被释放。

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

  1. public class EchoClient {
  2. private final String host;
  3. private final int port;
  4. public EchoClient(String host, int port) {
  5. this.host = host;
  6. this.port = port;
  7. }
  8. public void start() throws Exception{
  9. EventLoopGroup group = new NioEventLoopGroup();
  10. try {
  11. Bootstrap b = new Bootstrap();//创建Bootstrap
  12. b.group(group)//指定EventLoopGroup以处理客户端事件,需要适用于NIO的实现
  13. .channel(NioSocketChannel.class)//适用于NIO传输的Channel类型
  14. .remoteAddress(new InetSocketAddress(host,port))//设置服务器的InetSocketAddress
  15. .handler(new ChannelInitializer<SocketChannel>() {//在创建Channel时,向ChannelPipeline中添加一个EchoClientHandler实例
  16. @Override
  17. protected void initChannel(SocketChannel socketChannel) throws Exception {
  18. socketChannel.pipeline().addLast(new EchoClientHandler());
  19. }
  20. });
  21. ChannelFuture f = b.connect().sync();//连接到远程节点,阻塞等待直到连接完成
  22. f.channel().closeFuture().sync();//阻塞,直到Channel关闭
  23. }finally {
  24. group.shutdownGracefully().sync();//关闭线程池并且释放所有的资源
  25. }
  26. }
  27. public static void main(String[] args) throws Exception{
  28. if (args.length != 2){
  29. System.out.println("Usage: " + EchoClient.class.getSimpleName() + " <host> <port>");
  30. return;
  31. }
  32. String host = args[0];
  33. int port = Integer.parseInt(args[1]);
  34. new EchoClient(host,port).start();
  35. }
  36. }

注意,你可以在客户端和服务器上分别使用不同的传输。在服务器端使用NIO传输,而在客户端使用OIO传输。

——为初始化客户端,创建了一个Bootstrap实例

——为进行事件处理分配了一个NioEventLoopGroup实例,其中事件处理包括创建新的连接以及处理入站和出站数据

——为服务器连接创建了一个InetSocketAddress实例

——当连接被建立时,一个EchoClientHandler实例会被安装到(该Channel的)ChannelPipeline中

——在一切都设置完成后,调用Bootstrap.connect()方法连接到远程节点

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

深入地了解Netty对于关注点分离的架构原则的支持,通过提供正确的抽象来解耦业务逻辑和网络编程逻辑。

 

Netty实战二之自己的Netty应用程序的更多相关文章

  1. Netty 仿QQ聊天室 (实战二)

    Netty 聊天器(百万级流量实战二):仿QQ客户端 疯狂创客圈 Java 分布式聊天室[ 亿级流量]实战系列之15 [博客园 总入口 ] 源码IDEA工程获取链接:Java 聊天室 实战 源码 写在 ...

  2. 1、Netty 实战入门详解

    一.Netty 简介 Netty 是基于 Java NIO 的异步事件驱动的网络应用框架,使用 Netty 可以快速开发网络应用,Netty 提供了高层次的抽象来简化 TCP 和 UDP 服务器的编程 ...

  3. Netty实战入门详解——让你彻底记住什么是Netty(看不懂你来找我)

    一.Netty 简介 Netty 是基于 Java NIO 的异步事件驱动的网络应用框架,使用 Netty 可以快速开发网络应用,Netty 提供了高层次的抽象来简化 TCP 和 UDP 服务器的编程 ...

  4. Netty(二):Netty为啥去掉支持AIO?

    匠心零度 转载请注明原创出处,谢谢! 疑惑 我们都知道bio nio 以及nio2(也就是aio),如果不是特别熟悉可以看看我之前写的网络 I/O模型,那么netty为什么还经常看到类似下面的这段代码 ...

  5. Netty入门二:开发第一个Netty应用程序

    Netty入门二:开发第一个Netty应用程序 时间 2014-05-07 18:25:43  CSDN博客 原文  http://blog.csdn.net/suifeng3051/article/ ...

  6. Netty实战

    一.Netty异步和事件驱动1.Java网络编程回顾socket.accept 阻塞socket.setsockopt /非阻塞2.NIO异步非阻塞a).nio 非阻塞的关键时使用选择器(java.n ...

  7. Netty学习(二)-Helloworld Netty

    这一节我们来讲解Netty,使用Netty之前我们先了解一下Netty能做什么,无为而学,岂不是白费力气! 1.使用Netty能够做什么 开发异步.非阻塞的TCP网络应用程序: 开发异步.非阻塞的UD ...

  8. 《Netty实战》源码运行及本地环境搭建

     1.源码路径: GitHub - zzzvvvxxxd/netty-in-action-cn: Netty In Action 中文版 ,中文唯一正版<Netty实战>的代码清单 下载后 ...

  9. 1.Netty 实战前言

    1.参考文档:Netty实战精髓篇 2.Netty介绍:     Netty是基于Java NIO的网络应用框架. Netty是一个NIO client-server(客户端服务器)框架,使用Nett ...

随机推荐

  1. 玩转Kafka的生产者——分区器与多线程

    上篇文章学习kafka的基本安装和基础概念,本文主要是学习kafka的常用API.其中包括生产者和消费者, 多线程生产者,多线程消费者,自定义分区等,当然还包括一些避坑指南. 首发于个人网站:链接地址 ...

  2. eclipse的这几个小玩意

    scroll lock   滚动锁定 word wrap  自动换行 show console  when standard  out changes   标准输出更改时显示控制台 show cons ...

  3. Java面试题总结(附答案)

    1.什么是B/S架构?C/S架构? B/S(Browser/Server),浏览器/服务器程序: C/S(Client/Server),客户端/服务端,桌面应用程序. 2.网络协议有哪些? HTTP: ...

  4. 吴恩达机器学习笔记42-大边界的直观理解(Large Margin Intuition)

    这是我的支持向量机模型的代价函数,在左边这里我画出了关于

  5. Kali学习笔记36:AVWS10的使用

    AVWS是一款商业Web扫描工具 适用于Windows操作系统 功能强大,必须掌握 AVWS11以上是Web形式,AVWS10是桌面应用形式 下载安装破解这些基本操作就不说了,百度即可 从安装好开始: ...

  6. Metasploit Framework(2)Exploit模块、Payload使用

    文章的格式也许不是很好看,也没有什么合理的顺序 完全是想到什么写一些什么,但各个方面都涵盖到了 能耐下心看的朋友欢迎一起学习,大牛和杠精们请绕道 Exploit模块分为主动和被动(Active.Pas ...

  7. Kali学习笔记16:Nmap详细讲解

    在前面十五篇博客中,几乎每一篇都有介绍Nmap,这里系统地介绍下每个参数: 注意:区分每个参数的大小写 -iL:列好的IP放在一个文本里面,可以直接扫描这个文本 用法:namp -iL <文本名 ...

  8. postgresql 日常sql

    查看服务进程: select pid,usename,client_addr,client_port from pg_stat_activity;   查看当前数据库实例的版本:  select ve ...

  9. [译]ASP.NET Core中使用MediatR实现命令和中介者模式

    作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9866068.html 在本文中,我将解释命令模式,以及如何利用基于命令模式的第三方库来实现它们,以及如何 ...

  10. mysql 开发进阶篇系列 44 物理备份与恢复( 热备份xtrabackup 工具介绍)

    一.概述 物理备份和恢复又分为冷备份和热备份.与逻辑备份相比,它最大优点是备份和恢复的速度更快.因为物理备份的原理都是基于文件的cp. 1.1 冷备份 冷备份就是停掉数据库服务.这种物理备份一般很少使 ...