Netty学习笔记(1)-谈谈netty的使用

前言

本文是个人的一些笔记整理,粗略看了一遍netty源码之后,进行的二次总结,可能有不到位的地方,请多多见谅。

Netty的简介

Netty是 一个异步事件驱动的网络应用程序框架, 用于快速开发可维护的高性能协议服务器和客户端。(来自官网的简介)

此处不再重复介绍netty,具体介绍请看官网https://netty.io/

Netty的使用

这里,我就用一个小小的案例来引入netty,先看看结果

先看看服务端:

20200621202532

再来看看客户端:

我们下面就来看看我是怎么使用netty来完成客户端与服务端的交互

  1. 引入netty依赖

            <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-all</artifactId>
                <version>4.1.50.Final</version>
            </dependency>
  2. 编写WSServer(启动类)

    /**
     * 启动类
     */
    public class WSServer {
    
        public static void main (String[] args) throws InterruptedException {
            // 定义一对线程组
            // 主线程组,用于接收客户端的连接,不做任何处理
            EventLoopGroup mainGroup = new NioEventLoopGroup ();
    
            // 从线程组,主线程组会把任务丢给他,让手下线程组去做任务
            EventLoopGroup subGroup = new NioEventLoopGroup ();
    
            try {
                // netty服务器的创建,serverBootstrap 是一个启动类
                ServerBootstrap server = new ServerBootstrap ();
                server.group (mainGroup, subGroup)  // 设置主从线程组
                        .channel (NioServerSocketChannel.class)  // 设置nio的双向通道
                        .childHandler (new WSServerInitialzer ()); //  子处理器,用来处理subGroup
                // 启动server,并且设置8088为启动的端口号,同时启动方式为同步
                ChannelFuture channelFuture = server.bind (8088).sync ();
                // 监听关闭的channel,设置为同步方式
                channelFuture.channel ().closeFuture ().sync ();
            } finally {
                mainGroup.shutdownGracefully ();
                subGroup.shutdownGracefully ();
            }
    
        }
    }
  3. 编写WSServerInitialzer来完成childHandler的初始化

    public class WSServerInitialzer extends ChannelInitializer<SocketChannel> {
        protected void initChannel (SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline ();
    
            // websocket基于http协议,所以要有http编解码器
            pipeline.addLast (new HttpServerCodec ());
    
            // 对写大数据流的支持
            pipeline.addLast (new ChunkedWriteHandler ());
    
            // 对httpMessage进行聚合,聚合成FullHttpRequest或FullHttpResponse
            // 几乎再netty中的编程,都会使用到hanler
            pipeline.addLast (new HttpObjectAggregator (1024*64));
            // websocket 服务器处理的协议,用于指定给客户端连接访问的路由 /ws
            // 本handler会帮你处理一些繁重的复杂的事
            // 会帮你处理握手动作: handshaking
            // 对于websocket来讲,都是以frames进行传输的,不同的数据类型对应的frames也不同
            pipeline.addLast (new WebSocketServerProtocolHandler ("/ws"));
    
            // 自定义的handler
            pipeline.addLast (new ChatHandler ());
        }
    }
  4. 编写一个自定义handler来处理数据

    /**
     * 处理消息的handler
     * TextWebSocketFrame:在netty中,是用于为websocket专门处理文本的对象,frame是消息的载体
     */
    public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    
        // 用于记录和管理所有的客户端的channel
        private static ChannelGroup clients = new DefaultChannelGroup (GlobalEventExecutor.INSTANCE);
    
    
        protected void channelRead0 (ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
    
            // 获取客户端传输过来的消息
            String content = msg.text ();
            System.out.println ("接收到的数据"+ content);
    
            for (Channel channel : clients){
                channel.writeAndFlush (new TextWebSocketFrame ("[服务器在]" + LocalDateTime.now ()
                        + "接收到消息, 消息为:" + content));
    
            }
    
        }
    
        /**
         * 当客户端连接服务端之后,
         * 获取客户端的channel,并且放到ChannelGroup中进行管理
         * @param ctx
         * @throws Exception
         */
    
        @Override
        public void handlerAdded (ChannelHandlerContext ctx) throws Exception {
            clients.add (ctx.channel ());
        }
    
        @Override
        public void handlerRemoved (ChannelHandlerContext ctx) throws Exception {
    
            // 当触发handlerRemove,ChannelGroup会自动移除对应客户端的channel
    //        clients.remove (ctx.channel ());
            System.out.println ("客户端断开,channel对应的长id:" + ctx.channel ().id ().asLongText ());
            System.out.println ("客户端断开,channel对应的短id:" + ctx.channel ().id ().asShortText ());
    
        }
    }

前端代码这里就不再过多介绍了。

代码地址:https://gitee.com/oreo_team/netty-study-week01

看完这几段代码,是不是觉得很懵逼,我就用一张图(注意,此图只是一个简陋的图,里面还涉及到很多细节没有画出来),总结一下我刚刚写的代码吧netty实例

Reactor开发模式

这种模式,就是我们最常用的主从多线程模式.主 reactor 只负责连接建立的处理,而其他处理事件的逻辑分给子reactor 来处理,最后得到的结果异步返回。

那这里也顺带提一下什么是Reactor线程模式呢?简单来说就是将原来一个人做的工作,分给多个人来做,这多个人分别负责不同的模块,每个模块都可以以集群的方式来做。

Reactor是一种开发模式,模式的核心流程:
注册感兴趣的事件-> 扫描是否有感兴趣的事件发生-> 事件发生后做出 相应的处理。

下面就来看看剩下的两种线程模式分别是:

  1. **单线程,**这种单线程模式是指一个线程去处理一个事件,是不可取的,当处理的事件足够多的时候,很多事件都会处于阻塞状态,会导致事件的响应变慢,最终导致系统不可用。image-20200621152916329

  2. 多线程,可能针对第一个问题你可能会提出要引入线程池机制来避免浪费,但是本质上是没有变的,如果连接数量急剧上升,这种实现方式就无法很好地工作了,因为线程上下文切换开销会在高并发时变得很明显,这是同步阻塞方式的低扩展性劣势。(这也说明,多线程不一定就是好的)image-20200621152857611

Netty的组件和设计

Channel—Socket;
EventLoop—控制流、多线程处理、并发;
ChannelFuture—异步通知。

Channel、EventLoop、Thread 以及EventLoopGroup 之间的关系

image-20200620222826386

  1. 一个EventLoopGroup 包含一个或者多个EventLoop
  2. 一个EventLoop 在它的生命周期内只和一个Thread 绑定
  3. 所有由EventLoop 处理的I/O 事件都将在它专有的Thread 上被处理
  4. 一个Channel 在它的生命周期内只注册于一个EventLoop
  5. 一个EventLoop 可能会被分配给一个或多个Channel

ChannelFuture 接口

Netty 中所有的I/O 操作都是异步的,可以将ChannelFuture 看作是将来要执行的操作的结果的占位符。它究竟什么时候被执行则可能取决于若干的因素,因此不可能准确地预测,但是可以肯定的是它将会被执行。此外,所有属于同一个Channel 的操作都被保证其将以它们被调用的顺序被执行。

ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器

ChannelPipeline提供了ChannelHandler 链的容器,并定义了用于在该链上传播入站和出站事件流的API。当Channel 被创建时,它会被自动地分配到它专属的ChannelPipeline。

ChannelHandlerChannelPipeline的关系,这些对象接收事件、执行它们所实现的处理逻辑,并将数据传递给链中的下一个ChannelHandler。它们的执行顺序是由它们被添加的顺序所决定的。实际上,被我们称为ChannelPipeline 的是这些ChannelHandler 的编排顺序

运动方向是从客户端到服务器端,那么我们称这些事件为出站(从tail->head)的,反之则称为入站的。

image-20200620231022093

image-20200621082323372

个人唠叨

6月也接近尾声了,很少做总结的我,也该说说近况了,这篇文章也算是作为奥利奥团队的一员发布的一篇文章,之前虽然一直都有做笔记的习惯,但是没有像今天这样,老老实实把零碎的知识点,整理成一篇完整的文章(虽然也不算太完整,感觉思路还是有点乱的,大家没有基础的话,可能不知道我在说啥),其实,我一直有这么一个梦想,就是拥有属于自己的一个团队,所以,我之前也试过去找人,但是,最终以失败告终,可能我不太擅长说话吧,导致最后整个团队还没找齐人就决定放弃解散了,但是,在6月初,有幸被邀请到奥利奥团队,作为一个成员,我其实是挺激动的,因为也终于找到一群人,一起学习,一起成长了。

再来说说最近的一些学习状况吧,最近学源码真的学得脑袋疼,这细节扣得死死的,感觉也不知道学了啥,不过,也总算意识到这种学习方式的错误,好像一开始真的不能这样子抠细节,还是边应用边抠细节吧,所以,下周的计划还是继续以项目为驱动来学习源码,重点把期末大项目搞出来了先,文档也进来在这周出个初稿了,所以,这周任务比较重,加油!!

上次说到我在疫情中干了什么,趁今天做总结,再来说一下疫情期间,我是如何做到自律的,先谈谈我对自律的观点吧,我认为的自律是,你有目的,有规律地去干一件事,而不是三天打鱼两天晒网,这里很多人可能会说,自律就是不玩游戏,不玩手机,天天呆在电脑前面学习,我是不太建议这种自律的方法的,我比较推崇的是,该玩的时候放开玩,该学习的时候就要尽心学习。

好,接下来就说说我的日常:

  1. 7:00-8:00 起床、吃早餐、玩会收集
  2. 8:00-12:00 敲代码、做笔记、期间,专注了一个多小时之后,也会拿起手机玩一会,然后吃点东西,大概休息10分钟左右就开始继续学习(别小看这10分钟,这10分钟不能多也不能少,多了的话,你想再次提起精神来学习是很难的,其实,这个休息时间是没有限制的,是当你觉得撑不下去,需要摸一下手机的那一刻起,你就要休息了,自律性不那么强的人,建议还是别忍着,因为你一忍着,就会很容易每隔10分钟就会碰一次手机,导致你的学习效率大大降低,真的得不偿失啊!)
    1. 这里还有一个小插曲的,一般你很专注做一件事(在舒适区)的时候,这个专注的时间会比较长,这里指的专注做一件事就是,你做这件事的时候,很少出现卡壳,很顺畅一直做下去,简单来说就是越简单的东西(例如,一些写日记啊,整理笔记,写一些很熟悉的代码等等),你专注的时间就会越长,你需要放松的时间自然也就短了
    2. 但是,当你做一些很难的事情的时候,这个难是相对你的舒适区来说的,当你跳出你的舒适区,想去做一些令你感到恐慌的事情的时候(例如:读源码、接触一些新技术的时候),你的专注时间就会稍微短一点
    3. 所以这里建议你,一定要挑选自己认为最佳的时间学习一些令你感到恐慌的事情,比如我的最佳学习时间实在早上,所以我一般是在早上读源码,甚至有时候会接触一些新技术,有时候还会刷刷算法题,每个人的最佳学习时间都不一样的,你要尝试去找出你的最佳学习时间。
  3. 12:00-14:30,休息时间,想怎么玩都行
  4. 14:30-17:30,重复早上的操作,一般这个时候,如果感觉状态还不错,就会继续早上的学习
  5. 17:30-18:30,打球运动时间
  6. 19:00-20:00,吃饭洗澡时间
  7. 20:00-23:00,如果感觉状态还不错,就会继续下午的学习,如果状态比较差,就会整理一下笔记
  8. 23:45之前就要睡觉了

也说得差不多了,今天的总结就到这吧,接下来可能会每隔3天左右,就会总结一篇笔记发上来,嘻嘻,喜欢的朋友记得点个赞!

14:30-17:30,重复早上的操作,一般这个时候,如果感觉状态还不错,就会继续早上的学习
5. 17:30-18:30,打球运动时间
6. 19:00-20:00,吃饭洗澡时间
7. 20:00-23:00,如果感觉状态还不错,就会继续下午的学习,如果状态比较差,就会整理一下笔记
8. 23:45之前就要睡觉了

也说得差不多了,今天的总结就到这吧,接下来可能会每隔3天左右,就会总结一篇笔记发上来,嘻嘻,喜欢的朋友记得点个赞!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值