《Netty 实战》Netty In Action中文版 第2章——你的第一款Netty应用程序(二)

简介: 2.3.2 引导服务器 在讨论过由EchoServerHandler实现的核心业务逻辑之后,我们现在可以探讨引导服务器本身的过程了,具体涉及以下内容: 绑定到服务器将在其上监听并接受传入连接请求的端口; 配置Channel,以将有关的入站消息通知给EchoServerHandler实例。

2.3.2 引导服务器

在讨论过由EchoServerHandler实现的核心业务逻辑之后,我们现在可以探讨引导服务器本身的过程了,具体涉及以下内容:

  • 绑定到服务器将在其上监听并接受传入连接请求的端口;
  • 配置Channel,以将有关的入站消息通知给EchoServerHandler实例。

传输

 

在这一节中,你将遇到术语传输。在网络协议的标准多层视图中,传输层提供了端到端的或者主机到主机的通信服务。

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

我们将在第4章对传输进行详细的讨论。

代码清单2-2展示了EchoServer类的完整代码。

代码清单2-2 EchoServer

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() +
                " ");
        }
        int port = Integer.parseInt(args[0]);   ⇽--- 设置端口值(如果端口参数的格式不正确,则抛出一个NumberFormatException
        new EchoServer(port).start();    ⇽---  调用服务器的start()方法
    }
    public void start() throws Exception {
        final EchoServerHandler serverHandler = new EchoServerHandler();
        EventLoopGroup group = new NioEventLoopGroup();    ⇽---   创建Event-LoopGroup
        try {
             ServerBootstrap b = new ServerBootstrap();    ⇽---    创建Server-Bootstrap
             b.group(group)
                 .channel(NioServerSocketChannel.class)   ⇽---   指定所使用的NIO传输Channel
                 .localAddress(new InetSocketAddress(port))   ⇽---   使用指定的端口设置套接字地址
                .childHandler(new ChannelInitializer(){    ⇽---   添加一个EchoServer-
Handler到子ChannelChannelPipeline
                 @Override
                public void initChannel(SocketChannel ch)
                    throws Exception {
                         ch.pipeline().addLast(serverHandler);[4]   ⇽---  EchoServerHandler被标注为@Shareable,所以我们可以总是使用同样的实例
                    }
                 });
            ChannelFuture f = b.bind().sync();    ⇽---    异步地绑定服务器;调用sync()方法阻塞等待直到绑定完成
            f.channel().closeFuture().sync();  ⇽---   获取ChannelCloseFuture,并且阻塞当前线程直到它完成
        } finally {
            group.shutdownGracefully().sync();    ⇽---    关闭EventLoopGroup,释放所有的资源
        }
    }
}

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

在处,你使用了一个特殊的类——ChannelInitializer。这是关键。当一个新的连接被接受时,一个新的子Channel将会被创建,而ChannelInitializer将会把一个你的EchoServerHandler的实例添加到该ChannelChannelPipeline中。正如我们之前所解释的,这个ChannelHandler将会收到有关入站消息的通知。

虽然NIO是可伸缩的,但是其适当的尤其是关于多线程处理的配置并不简单。Netty的设计封装了大部分的复杂性,而且我们将在第3章中对相关的抽象(EventLoopGroupSocket-ChannelChannelInitializer)进行详细的讨论。

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

这个示例使用了NIO,因为得益于它的可扩展性和彻底的异步性,它是目前使用最广泛的传输。但是也可以使用一个不同的传输实现。如果你想要在自己的服务器中使用OIO传输,将需要指定OioServerSocketChannelOioEventLoopGroup。我们将在第4章中对传输进行更加详细的探讨。

与此同时,让我们回顾一下你刚完成的服务器实现中的重要步骤。下面这些是服务器的主要代码组件:

  • EchoServerHandler实现了业务逻辑;
  • main()方法引导了服务器;

引导过程中所需要的步骤如下:

  • 创建一个ServerBootstrap的实例以引导和绑定服务器;
  • 创建并分配一个NioEventLoopGroup实例以进行事件的处理,如接受新连接以及读/写数据;
  • 指定服务器绑定的本地的InetSocketAddress
  • 使用一个EchoServerHandler的实例初始化每一个新的Channel
  • 调用ServerBootstrap.bind()方法以绑定服务器。

在这个时候,服务器已经初始化,并且已经就绪能被使用了。在下一节中,我们将探讨对应的客户端应用程序的代码。

2.4 编写Echo客户端

Echo客户端将会:

(1)连接到服务器;

(2)发送一个或者多个消息;

(3)对于每个消息,等待并接收从服务器发回的相同的消息;

(4)关闭连接。

编写客户端所涉及的两个主要代码部分也是业务逻辑和引导,和你在服务器中看到的一样。

2.4.1 通过ChannelHandler实现客户端逻辑

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

  • channelActive()——在到服务器的连接已经建立之后将被调用;
  • channelRead0()[5]——当从服务器接收到一条消息时被调用;
  • exceptionCaught()——在处理过程中引发异常时被调用。

代码清单2-3 客户端的ChannelHandler

@Sharable     ⇽---  标记该类的实例可以被多个Channel共享
public class EchoClientHandler extends
    SimpleChannelInboundHandler<ByteBuf> {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!",     ⇽---  当被通知Channel是活跃的时候,发送一条消息
        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,     ⇽---  在发生异常时,记录错误并关闭Channel 
        Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

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

接下来,你重写了channelRead0()方法。每当接收数据时,都会调用这个方法。需要注意的是,由服务器发送的消息可能会被分块接收。也就是说,如果服务器发送了5字节,那么不能保证这5字节会被一次性接收。即使是对于这么少量的数据,channelRead0()方法也可能会被调用两次,第一次使用一个持有3字节的ByteBuf(Netty的字节容器),第二次使用一个持有2字节的ByteBuf。作为一个面向流的协议,TCP保证了字节数组将会按照服务器发送它们的顺序被接收。

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

SimpleChannelInboundHandler与ChannelInboundHandler

 

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

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

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

消息在EchoServerHandlerchannelReadComplete()方法中,当writeAndFlush()方法被调用时被释放(见代码清单2-1)。

第5章和第6章将对消息的资源管理进行详细的介绍。

2.4.2 引导客户端

如同将在代码清单2-4中所看到的,引导客户端类似于引导服务器,不同的是,客户端是使用主机和端口参数来连接远程地址,也就是这里的Echo服务器的地址,而不是绑定到一个一直被监听的端口。

代码清单2-4 客户端的主类

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
            Bootstrap b = new Bootstrap();     ⇽---  指定EventLoopGroup以处理客户端事件;需要适用于NIO的实现
            b.group(group)    
                 .channel(NioSocketChannel.class)     ⇽---  适用于NIO传输的Channel类型
                 .remoteAddress(new InetSocketAddress(host, port))     ⇽---  设置服务器的InetSocketAddr-ess
![](/api/storage/getbykey/screenshow?key=17043add7e9c14a5d3f7)                .handler(new ChannelInitializer<SocketChannel>() {    ⇽---  在创建Channel时,向ChannelPipeline中添加一个Echo-ClientHandler实例
                 @Override
                public void initChannel(SocketChannel ch)
                    throws Exception {
                   ch.pipeline().addLast(
                        new EchoClientHandler());
                    }
                });
            ChannelFuture f = b.connect().sync();     ⇽---  连接到远程节点,阻塞等待直到连接完成
            f.channel().closeFuture().sync();      ⇽---  阻塞,直到Channel关闭
        } 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();
    }
}

和之前一样,使用了NIO传输。注意,你可以在客户端和服务器上分别使用不同的传输。例如,在服务器端使用NIO传输,而在客户端使用OIO传输。在第4章,我们将探讨影响你选择适用于特定用例的特定传输的各种因素和场景。

让我们回顾一下这一节中所介绍的要点:

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

完成了客户端,你便可以着手构建并测试该系统了。

转载自 并发编程网 - ifeve.comhttp://ifeve.com/

相关文章
|
7月前
Netty实战: HTTP文件列表服务器
Netty实战: HTTP文件列表服务器
75 0
|
7月前
|
缓存 网络协议 算法
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析
《跟闪电侠学Netty》阅读笔记 - Netty入门程序解析
217 0
|
7月前
|
Java Unix Linux
【Netty技术专题】「原理分析系列」Netty强大特性之Native transports扩展开发实战
当涉及到网络通信和高性能的Java应用程序时,Netty是一个强大的框架。它提供了许多功能和组件,其中之一是JNI传输。JNI传输是Netty的一个特性,它为特定平台提供了高效的网络传输。 在本文中,我们将深入探讨Netty提供的特定平台的JNI传输功能,分析其优势和适用场景。我们将介绍每个特定平台的JNI传输,并讨论其性能、可靠性和可扩展性。通过了解这些特定平台的JNI传输,您将能够更好地选择和配置适合您应用程序需求的网络传输方式,以实现最佳的性能和可靠性。
147 7
【Netty技术专题】「原理分析系列」Netty强大特性之Native transports扩展开发实战
|
7月前
|
网络协议 Java 测试技术
阿里内部Netty实战小册,值得拥有
Netty 是一个高性能的 Java 网络通信框架,简化了网络编程并涵盖了最新的Web技术。它提供了一种抽象,降低了底层复杂性,使得更多开发者能接触网络编程。Netty 因其易用性、高效性和广泛的应用场景受到推崇,适合互联网行业从业者学习,有助于理解和开发基于Netty的系统。免费的《Netty实战小册》详细介绍了Netty的各个方面,包括概念、架构、编解码器、网络协议和实际案例,帮助读者深入理解和应用Netty。如需完整版小册,可点击链接获取。
阿里内部Netty实战小册,值得拥有
|
4月前
|
编解码 NoSQL Redis
(十一)Netty实战篇:基于Netty框架打造一款高性能的IM即时通讯程序
关于Netty网络框架的内容,前面已经讲了两个章节,但总归来说难以真正掌握,毕竟只是对其中一个个组件进行讲解,很难让诸位将其串起来形成一条线,所以本章中则会结合实战案例,对Netty进行更深层次的学习与掌握,实战案例也并不难,一个非常朴素的IM聊天程序。
|
4月前
|
前端开发 网络协议
Netty实战巅峰:从零构建高性能IM即时通讯系统,解锁并发通信新境界
【8月更文挑战第3天】Netty是一款高性能、异步事件驱动的网络框架,适用于开发高并发网络应用,如即时通讯(IM)系统。本文将指导你利用Netty从零构建高性能IM程序,介绍Netty基础及服务器/客户端设计。服务器端使用`ServerBootstrap`启动,客户端通过`Bootstrap`连接服务器。示例展示了简单的服务器启动过程。通过深入学习,可进一步实现用户认证等功能,打造出更完善的IM系统。
183 1
|
4月前
|
移动开发 网络协议 算法
(十)Netty进阶篇:漫谈网络粘包、半包问题、解码器与长连接、心跳机制实战
在前面关于《Netty入门篇》的文章中,咱们已经初步对Netty这个著名的网络框架有了认知,本章的目的则是承接上文,再对Netty中的一些进阶知识进行阐述,毕竟前面的内容中,仅阐述了一些Netty的核心组件,想要真正掌握Netty框架,对于它我们应该具备更为全面的认知。
236 2
|
5月前
|
前端开发 Java 数据处理
使用Netty构建高性能的网络应用
使用Netty构建高性能的网络应用
|
5月前
|
前端开发 Java 数据处理
使用Netty构建高性能的网络应用
使用Netty构建高性能的网络应用
|
7月前
|
NoSQL Redis
Netty实战:模拟Redis的客户端
Netty实战:模拟Redis的客户端
96 0