zoukankan      html  css  js  c++  java
  • Netty4.x中文教程系列(二) Hello World !

      在中国程序界。我们都是学着Hello World !慢慢成长起来的。逐渐从一无所知到熟悉精通的。

      第二章就从Hello World 开始讲述Netty的中文教程。

      首先创建一个Java项目。引入一个Netty 框架的包。这个步骤我在本系列教程的后面就不在重复了。

      先上一张我示例的项目工程图给大家看一下:

    1.下载并为项目添加Netty框架

      Netty的包大家可以从Netty官网:http://netty.io/downloads.html 下载

      

    如图所示: Netty提供了三个主要版本的框架包给大家下载。

    3.9版本Final 说明这个版本是3.x版本中最新的版本。final意味着功能不再继续添加更新。仅为修改bug等提供继续的更新。

    5.x版本由于是开始。不能排除是否稳定运行等问题。加上5.x在4.x的版本上略微修改的。在5.x稳定之前。不推荐大家学习使用。

    本教程是基于Netty4.x版本的。

      笔者也是从3.6版本,经过了相当痛苦的一段时间才算是真正的过度到4.x版本。

      下载之后解压缩。大家可以看到这样一个目录结构。非常的清晰。

      第一个文件夹jar是jar包的文件夹。第二个javadoc是API文档。第三个license文件夹是开源的授权文件(可以直接无视)。

      javadoc文件夹下面是一个jar包。可以直接解压缩出来。解压缩之后的文件夹就是api文档(以网页的形势展现)。

      jar文件夹里面有很多的jar包和一个all-in-one文件夹。都是Netty框架的组成部分。all-in-one里面有两个文件一个是jar包,另一个是对应的source源代码包。这样做的目的是为了给程序员有选择的添加自己所需要的包。

      假如读者是初学者的话。推荐直接套用all-in-one里面的jar包。假如你熟悉Netty的话可以根据自己的项目需求添加不同的jar包。

    2.创建Server 服务端

      Netty创建全部都是实现自AbstractBootstrap。客户端的是Bootstrap,服务端的则是ServerBootstrap。

      

    2.1创建一个 HelloServer

    package org.example.hello;
    
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    
    public class HelloServer {
        
        /**
         * 服务端监听的端口地址
         */
        private static final int portNumber = 7878;
        
        public static void main(String[] args) throws InterruptedException {
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap b = new ServerBootstrap();
                b.group(bossGroup, workerGroup);
                b.channel(NioServerSocketChannel.class);
                b.childHandler(new HelloServerInitializer());
    
                // 服务器绑定端口监听
                ChannelFuture f = b.bind(portNumber).sync();
                // 监听服务器关闭监听
                f.channel().closeFuture().sync();
    
                // 可以简写为
                /* b.bind(portNumber).sync().channel().closeFuture().sync(); */
            } finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }

      EventLoopGroup 是在4.x版本中提出来的一个新概念。用于channel的管理。服务端需要两个。和3.x版本一样,一个是boss线程一个是worker线程。

      b.childHandler(new HelloServerInitializer());    //用于添加相关的Handler

      服务端简单的代码,真的没有办法在精简了感觉。就是一个绑定端口操作。

    2.2创建和实现HelloServerInitializer

      在HelloServer中的HelloServerInitializer在这里实现。

      首先我们需要明确我们到底是要做什么的。很简单。HelloWorld!。我们希望实现一个能够像服务端发送文字的功能。服务端假如可以最好还能返回点消息给客户端,然客户端去显示。

      需求简单。那我们下面就准备开始实现。

      DelimiterBasedFrameDecoder Netty在官方网站上提供的示例显示 有这么一个解码器可以简单的消息分割。

      其次 在decoder里面我们找到了String解码编码器。着都是官网提供给我们的。

     1 package org.example.hello;
     2 
     3 import io.netty.channel.ChannelInitializer;
     4 import io.netty.channel.ChannelPipeline;
     5 import io.netty.channel.socket.SocketChannel;
     6 import io.netty.handler.codec.DelimiterBasedFrameDecoder;
     7 import io.netty.handler.codec.Delimiters;
     8 import io.netty.handler.codec.string.StringDecoder;
     9 import io.netty.handler.codec.string.StringEncoder;
    10 
    11 public class HelloServerInitializer extends ChannelInitializer<SocketChannel> {
    12 
    13     @Override
    14     protected void initChannel(SocketChannel ch) throws Exception {
    15         ChannelPipeline pipeline = ch.pipeline();
    16 
    17         // 以("
    ")为结尾分割的 解码器
    18         pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
    19 
    20         // 字符串解码 和 编码
    21         pipeline.addLast("decoder", new StringDecoder());
    22         pipeline.addLast("encoder", new StringEncoder());
    23 
    24         // 自己的逻辑Handler
    25         pipeline.addLast("handler", new HelloServerHandler());
    26     }
    27 }

      上面的三个解码和编码都是系统。

      另外我们自己的Handler怎么办呢。在最后我们添加一个自己的Handler用于写自己的处理逻辑。

    2.3 增加自己的逻辑HelloServerHandler

      自己的Handler我们这里先去继承extends官网推荐的SimpleChannelInboundHandler<C> 。在这里C,由于我们需求里面发送的是字符串。这里的C改写为String。

      

     1 package org.example.hello;
     2 
     3 import java.net.InetAddress;
     4 
     5 import io.netty.channel.ChannelHandlerContext;
     6 import io.netty.channel.SimpleChannelInboundHandler;
     7 
     8 public class HelloServerHandler extends SimpleChannelInboundHandler<String> {
     9     
    10     @Override
    11     protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
    12         // 收到消息直接打印输出
    13         System.out.println(ctx.channel().remoteAddress() + " Say : " + msg);
    14         
    15         // 返回客户端消息 - 我已经接收到了你的消息
    16         ctx.writeAndFlush("Received your message !
    ");
    17     }
    18     
    19     /*
    20      * 
    21      * 覆盖 channelActive 方法 在channel被启用的时候触发 (在建立连接的时候)
    22      * 
    23      * channelActive 和 channelInActive 在后面的内容中讲述,这里先不做详细的描述
    24      * */
    25     @Override
    26     public void channelActive(ChannelHandlerContext ctx) throws Exception {
    27         
    28         System.out.println("RamoteAddress : " + ctx.channel().remoteAddress() + " active !");
    29         
    30         ctx.writeAndFlush( "Welcome to " + InetAddress.getLocalHost().getHostName() + " service!
    ");
    31         
    32         super.channelActive(ctx);
    33     }
    34 }

      在channelHandlerContent自带一个writeAndFlush方法。方法的作用是写入Buffer并刷入。

      注意:在3.x版本中此处有很大区别。在3.x版本中write()方法是自动flush的。在4.x版本的前面几个版本也是一样的。但是在4.0.9之后修改为WriteAndFlush。普通的write方法将不会发送消息。需要手动在write之后flush()一次

      这里channeActive的意思是当连接活跃(建立)的时候触发.输出消息源的远程地址。并返回欢迎消息。

      channelRead0 在这里的作用是类似于3.x版本的messageReceived()。可以当做是每一次收到消息是触发。

      我们在这里的代码是返回客户端一个字符串"Received your message !".

      注意:字符串最后面的" "是必须的。因为我们在前面的解码器DelimiterBasedFrameDecoder是一个根据字符串结尾为“ ”来结尾的。假如没有这个字符的话。解码会出现问题。

    2.Client客户端

      类似于服务端的代码。我们不做特别详细的解释。

      直接上示例代码:

      

     1 package org.example.hello;
     2 
     3 import io.netty.bootstrap.Bootstrap;
     4 import io.netty.channel.Channel;
     5 import io.netty.channel.EventLoopGroup;
     6 import io.netty.channel.nio.NioEventLoopGroup;
     7 import io.netty.channel.socket.nio.NioSocketChannel;
     8 
     9 import java.io.BufferedReader;
    10 import java.io.IOException;
    11 import java.io.InputStreamReader;
    12 
    13 public class HelloClient {
    14     
    15     public static String host = "127.0.0.1";
    16     public static int port = 7878;
    17 
    18     /**
    19      * @param args
    20      * @throws InterruptedException 
    21      * @throws IOException 
    22      */
    23     public static void main(String[] args) throws InterruptedException, IOException {
    24         EventLoopGroup group = new NioEventLoopGroup();
    25         try {
    26             Bootstrap b = new Bootstrap();
    27             b.group(group)
    28             .channel(NioSocketChannel.class)
    29             .handler(new HelloClientInitializer());
    30 
    31             // 连接服务端
    32             Channel ch = b.connect(host, port).sync().channel();
    33             
    34             // 控制台输入
    35             BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    36             for (;;) {
    37                 String line = in.readLine();
    38                 if (line == null) {
    39                     continue;
    40                 }
    41                 /*
    42                  * 向服务端发送在控制台输入的文本 并用"
    "结尾
    43                  * 之所以用
    结尾 是因为我们在handler中添加了 DelimiterBasedFrameDecoder 帧解码。
    44                  * 这个解码器是一个根据
    符号位分隔符的解码器。所以每条消息的最后必须加上
    否则无法识别和解码
    45                  * */
    46                 ch.writeAndFlush(line + "
    ");
    47             }
    48         } finally {
    49             // The connection is closed automatically on shutdown.
    50             group.shutdownGracefully();
    51         }
    52     }
    53 }

      下面的是HelloClientInitializer代码貌似是和服务端的完全一样。我没注意看。其实编码和解码是相对的。多以服务端和客户端都是解码和编码。才能通信。

      

     1 package org.example.hello;
     2 
     3 import io.netty.channel.ChannelInitializer;
     4 import io.netty.channel.ChannelPipeline;
     5 import io.netty.channel.socket.SocketChannel;
     6 import io.netty.handler.codec.DelimiterBasedFrameDecoder;
     7 import io.netty.handler.codec.Delimiters;
     8 import io.netty.handler.codec.string.StringDecoder;
     9 import io.netty.handler.codec.string.StringEncoder;
    10 
    11 public class HelloClientInitializer extends ChannelInitializer<SocketChannel> {
    12 
    13     @Override
    14     protected void initChannel(SocketChannel ch) throws Exception {
    15         ChannelPipeline pipeline = ch.pipeline();
    16 
    17         /*
    18          * 这个地方的 必须和服务端对应上。否则无法正常解码和编码
    19          * 
    20          * 解码和编码 我将会在下一张为大家详细的讲解。再次暂时不做详细的描述
    21          * 
    22          * */
    23         pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
    24         pipeline.addLast("decoder", new StringDecoder());
    25         pipeline.addLast("encoder", new StringEncoder());
    26         
    27         // 客户端的逻辑
    28         pipeline.addLast("handler", new HelloClientHandler());
    29     }
    30 }

      HellClientHandler:

      

     1 package org.example.hello;
     2 
     3 import io.netty.channel.ChannelHandlerContext;
     4 import io.netty.channel.SimpleChannelInboundHandler;
     5 
     6 public class HelloClientHandler extends SimpleChannelInboundHandler<String> {
     7 
     8     @Override
     9     protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
    10         
    11         System.out.println("Server say : " + msg);
    12     }
    13     
    14     @Override
    15     public void channelActive(ChannelHandlerContext ctx) throws Exception {
    16         System.out.println("Client active ");
    17         super.channelActive(ctx);
    18     }
    19 
    20     @Override
    21     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    22         System.out.println("Client close ");
    23         super.channelInactive(ctx);
    24     }
    25 }

    本教程的示例源代码:http://pan.baidu.com/s/1hABzK#dir

    大家可以再我的百度云盘里面找到。

    下面上几张成果图:

      客户端在连接建立是输出了Client active 信息,并收到服务端返回的Welcome消息。

      输入Hello World ! 回车发送消息。服务端响应返回消息已接受。

    1.客户端控制台截图

    2.服务端控制台截图

     

    作者:TinyZ
    出处:http://www.cnblogs.com/zou90512/
    关于作者:努力学习,天天向上。不断探索学习,提升自身价值。记录经验分享。
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接
    如有问题,可以通过 zou90512@126.com 联系我,非常感谢。
    笔者网店: https://shop70768633.taobao.com/. 欢迎广大读者围观

     

  • 相关阅读:
    LeetCode 382. Linked List Random Node
    LeetCode 398. Random Pick Index
    LeetCode 1002. Find Common Characters
    LeetCode 498. Diagonal Traverse
    LeetCode 825. Friends Of Appropriate Ages
    LeetCode 824. Goat Latin
    LeetCode 896. Monotonic Array
    LeetCode 987. Vertical Order Traversal of a Binary Tree
    LeetCode 689. Maximum Sum of 3 Non-Overlapping Subarrays
    LeetCode 636. Exclusive Time of Functions
  • 原文地址:https://www.cnblogs.com/zou90512/p/3492878.html
Copyright © 2011-2022 走看看