上篇文章(Netty入门—第一个完整的demo之智能客服)大明哥演示了强大的智能客服,它能够很好的解决用户的一些常见的问题,但是有些问题依然还需要人工客服来完成,所以这篇文章我们来实现人工坐席。功能实现当用户输入0的时候,智能客户需要将这个用户转发给某一个人工客服,如果没有人工客户在线需要提示用户是否继续等待。在等待期间若有人工客服上线则将其转接到该用户。那么服务端需要做如下几件事情:维护用户与人工坐席之间的关系,发的消息不能串。将人工坐席分配给用户。若没有人工坐席,则在用户等待期间,人工坐席上线则将其转接给该用户。消息的转发。至于用户和人工坐席,则相对来说就比较简单了,接收服务端的消息,并发
在前面8篇文章中,大明哥详细阐述了Netty核心组件的基本原理及核心API的使用,但是那些只是API的使用,且只有一个简单的helloworld的demo,没有一个demo来完整演示客户端与服务端之间的交互。这篇文章就完整演示客户端与服务端的交互,场景:智障客户(zzkf)。如果有小伙伴对Netty的核心组件还不是很清晰的话,可以移步相关文章再复习下:Netty入门—要想掌握Netty,你必须知道它的这些核心组件Netty入门—Bootstrap,一切从这里开始Netty入门—ByteBuf,Netty数据传输的载体Netty入门—Channel,把握Netty通信的命门Netty入门—Cha
在上篇文章(Netty入门—ChannelHandler,Netty的数据加工厂)提到ChannelHandler虽然是一个好的打工人,但是在我们实际业务线中,他不可能一个人干所有的活啊,毕竟都21世纪了,我们是要讲究分工的。所以Netty就需要一个好的组织者将这些ChannelHandler组织起来,形成一个条完整,高效的业务线,这个组织者就是ChannelPipeline。ChannelPipeline概述pipeline翻译为管道、流水线,在Netty这个大工厂中,ChannelPipeline就像一条流水线,数据流过ChannelPipeline,被一步一步地加工,最后得到一个成熟的工
经过前面几篇文章的介绍,我们掌握了Netty的5个核心组件,但是有了这5个核心组件Netty这个工厂还是无法很好的运转,因为缺少了一个最核心的组件:EventLoop,它是Netty中最最核心的组件,也是Netty最精华的部分,它负责Netty中I/O事件的分发,也就是说,这个事件谁来做,它说了算。再谈Reactor线程模型要想弄明白Netty的EventLoop,我们就必须先理解Reactor线程模型,Netty高性能的奥秘就在于Reactor线程模型。线程模型的优劣直接决定了系统的性能,一个好的线程模型比不好的线程模型性能会高出好多倍。而目前主流的网络框架选择的线程模型几乎都是I/O多路复
上篇文章(Netty入门—Bootstrap,一切从这里开始),我们了解了Netty的第一个核心组件:Bootstrap,它是Netty程序的开端。今天我们来熟悉Netty的第二个组件:ByteBuf,Netty数据传输的载体。在Netty中,数据的读写都是以ByteBuf为单位进行交互的。为什么要造轮子?在学习JavaNIO的时候,JavaNIO有一个原生的ByteBuffer,为什么Netty不直接使用原生的,要重复造一个呢?因为不好用啊。为什么不好用呢?因为JavaNIO的ByteBuffer有一些设计上的缺陷和痛点。我们先看ByteBuffer内部结构。JavaNIO的ByteBuff
上篇文章(Netty入门—要想掌握Netty,你必须知道它的这些核心组件)大明哥阐述了Netty的整体结构,从这篇文章开始大明哥就将这个整体进行拆分讲解,今天是第一个核心组件:Bootstrap。一句话来概括Bootstrap,无论是Netty的客户端还是服务端程序,都是从这里开始的。在Netty入门—亘古不变的HelloWorld一文的demo中,我们首先创建一个ServerBootstrap对象(服务端)或者Bootstrap对象(客户端),然后调用他们的各个方法进行组装整个Netty服务端和客户端,从代码中我们可以看出这两个类是一个辅助类,用来辅助服务端或者客户端初始化和启动的。Boot
在上篇文章(Netty入门—亘古不变的HelloWorld)中,我们简单认识了开发一个Netty服务端和客户端代码的主要步骤了,在这几大步骤中我们基本上可以看出Netty的几个核心组件。在真正进入Netty的学习之前,我们非常有必要先对这些组件进行一个整体的认识,对于Netty入门阶段的讲解,大明哥采用整体—>分解—>总结的模式来阐述。对于一头牛,我们需要先知道这是一头牛,了解这头牛有哪些组织,然后再把这些组织一个一个地拆开来认识,清楚里面每一个组织的功能,最后再将这些组织组合成一头牛,是不是就会清晰很多。Bootstrap:引导器Bootstrap的意思是引导,一个Netty应用
这篇文章我们正式开始学习Netty,在入门之前我们还是需要了解什么是Netty。什么是Netty为什么很多人都推崇Javaboy去研究Netty?Netty这么高大上,它到底是何方神圣?用官方的话说:Netty是一款异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络IO程序。为什么要使用Netty呢?因为使用原生的JavaNIO非常不爽,它存在一系列的问题,比如:使用JavaNIO需要了解很多概念,而且API非常繁琐。使用JavaNIO编程复杂,一不小心就会Bug横飞。开发工作量和难度也很大,例如我们要处理断开重连、网络闪断、半包读写、网络拥塞、异常处理,等等异常情况,
上一个死磕Java专栏【死磕NIO】(当然写的不是很好,争取今年将它重写一遍)是**【死磕Netty】**的铺垫,对于我们Java程序员而言,我们在实际开发过程一般都不会直接使用JavaNIO作为我们的网络编程框架,因为写出一套高质量的JavaNIO程序并不是一件容易的事,除了JavaNIO固有的复杂性和bug之外,作为NIO服务端,我们要处理的事情太多了,如网络闪断、客户端认证、消息编解码、半包读写,客户端一样也有很多复杂的事情要处理,所以如果我们对JavaNIO没有足够了解,没有足够的网络编程经验的话,利用JavaNIO来编写一个高性能的稳定网络编程框架并不是一件容易的事。所以我们一般都不
Netty初始化总结一些初始化的知识点。Netty组件总结一些常用组件,不过还有好多处理器,编解码器没说,这个可以看我的源码。Netty内存管理总结很多细节都没怎么写,还是得看源码,很多结构和流程图我的源码文章都有,有兴趣的可以看看。好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。
NioMessageUnsafe这个是专门来处理客户端连接的unsafe。read读客户端连接上篇分析了可调节的接收缓冲区,这篇就来看看他是怎么用的,所以就要看NioMessageUnsafe和NioByteUnsafe的read,我们先介绍NioMessageUnsafe的read。这个是用来接收客户端连接的,读取的是客户端通道。allocHandle的操作上篇文章里都讲了,就不多说了。这里是把所有的客户端连接接受完了,然后传递每一个连接的读事件。@Overridepublicvoidread(){asserteventLoop().inEventLoop();finalChannelCon
AbstractUnsafe的方法beginRead开始读判断下条件,准备开始读,真正读的是通道的doBeginRead方法。@OverridepublicfinalvoidbeginRead(){assertEventLoop();if(!isActive()){return;}try{doBeginRead();}catch(finalExceptione){invokeLater(newRunnable(){@Overridepublicvoidrun(){pipeline.fireExceptionCaught(e);}});close(voidPromise());}}write写数
Netty的Unsafe接口这个Unsafe可不是JDK原生的Unsafe哦,主要就是一些直接跟IO底层直接相关的通用操作:interfaceUnsafe{//接受数据的时候用于分配字节缓冲区的处理器RecvByteBufAllocator.HandlerecvBufAllocHandle();//本地地址SocketAddresslocalAddress();//远程地址SocketAddressremoteAddress();//向事件循环注册通道,完成后回调voidregister(EventLoopeventLoop,ChannelPromisepromise);//绑定本地地址,完成
RecvByteBufAllocator首先在说这个之前,先来点预备知识,不然不好理解。我们要读数据,当然是从通道里读,那我们是不是应该有个放数据的地方啊,这个就是接受缓冲区,那这个放数据的地方要多大呢,太大了浪费,太小了又不够,可能要涉及扩容,性能不好,所以netty设计了一个RecvByteBufAllocator分配接口。我们来看下这个接口。publicinterfaceRecvByteBufAllocator{//处理器,也就是做一些统计的HandlenewHandle();@DeprecatedinterfaceHandle{//分配缓冲区,实际是交给ByteBufAllocator
NioEventLoop我们继续NioEventLoop的构造函数,这里就是对原生的selectedKeys做了优化。openSelector先获取原生的Selector:如果禁止优化就直接包装原生的返回:SelectorTuple这个元组里面就是一个是原生的,一个是包装后的。反射出sun.nio.ch.SelectorImpl:创建优化的SelectedSelectionKeySet:SelectedSelectionKeySet里面就是用数组来存SelectionKey,不够就扩容:直接内存操作,将原生的Selector的selectedKeys和publicSelectedKeys替换成
一行代码的秘密我们今天就来看看一行代码里面的细节,涉及很多东西,有很多开始不会关心的一些问题。EventLoopGroupbossGroup=newNioEventLoopGroup(1);NioEventLoopGroup的terminationFuture终止回调在初始化后面会给每个终止回调添加一个相同的终止回调,只有所有的子事件循环NioEventLoop终止了才会回调事件循环组NioEventLoopGroup的成功终止。NioEventLoop的初始化io.netty.noKeySetOptimization是否禁用选择器的selectedKeys优化,默认是不禁用,也就是要优化,至
sysn同步上次说了异步监听回调,这次我们试试同步看看会怎么样。我在处理器里这么做:DefaultChannelPromise的syncDefaultPromise的syncDefaultChannelPromise的awaitDefaultPromise的await会先检查是否完成,完成了就不需要等了,然后看是否有中断,再检查死锁,最后再判断是否完成,没完成就进行wait,等待通知唤醒,唤醒了后还是要继续循环看是否完成了,只有完成了才可以返回。现在我们知道同步原理是内部用了一个无限循环来判断是否是否完成,如果完成了才会返回,否则就一直wait。@OverridepublicPromise&l
继续flush监听器添加完了,要开始发送了。我们一路下来,先到HeadContext的flush:AbstractUnsafe的flushChannelOutboundBuffer的addFlush这里就是快要发送了,要把消息设置成不可取消状态:DefaultPromise的setUncancellable如果设置成功,就说明已经不可取消了,如果没有什么设置过就能设置成功,result变为UNCANCELLABLE对象:AbstractUnsafe的flush0NioSocketChannel的doWrite然后来到这里,发送完成之后要进行删除操作:ChannelOutboundBuffer的
Netty的异步理解Netty是以事件驱动的,那什么是事件驱动,简单理解就是闹钟,你以前读书的时候用过闹钟吧,你晚上10点定时到噪声6点闹,你不会等着他走到6点到,你肯定睡觉去啦,6点了闹钟闹铃响了,你就知道要起床了,这就是事件驱动,闹钟闹铃就是一个事件,驱动着你得起来了。这个是最常见的生活中的例子啊,Netty基本上所有的逻辑都是事件驱动的,常见的就是处理器的监听读写事件,你应该没看到处理器是在一个死循环里轮询读写吧,只有当有读写事件的时候,从缓冲区读出事件了,才会去向管道里发送,然后被相应的处理器方法执行。而且他也提供了异步回调的机制,几乎所有的输出事件都是有异步回调结果的,一个Chann
SimpleChannelInboundHandler为什么不用手动释放他的关键是给你封装了,而且转换成你要的对象,最后会判断如果传给你了,使用后就帮你释放,否则传给下一个,不释放。那我们以后就都用这个好啦,不用自己释放。好是好,但是他的类型匹配器创建还是比较耗性能的,会用反射来检查你的泛型,然后去线程本地变量里获取。具体的可以参考我以前的文章。我们处理器拿到的缓冲区从哪里来我们还是来分析下,我们拿到的缓冲区从哪里来的,当然是读出来的,对了,那我们来看看怎么读呢,首先是有读事件,然后把数据读出来:NioByteUnsafe的read可以看到这里会分配缓冲区,记住他的ID是1918:等读完数据后
最容易内存泄漏的例子客户端代码写个处理器,里面就加这段,就给服务器发送1亿条1:服务端代码写个处理器继承ChannelInboundHandlerAdapter,就获取消息对象,转成字节缓冲区,打印内存地址和内容。堆内内存溢出虚拟机参数设置为了让堆内内存快速溢出,我加了虚拟机参数:-Xmx10m//堆内最大10M-XX:MaxDirectMemorySize=800M//堆外800M-Dio.netty.allocator.maxOrder=4//让缓存区变小,可以让块变小,可以开启缓存-XX:+HeapDumpOnOutOfMemoryError//监控堆内溢出了-XX:HeapDumpPa
Utf8FrameValidator今天把剩下的一些说下,这个是验证文本帧是否是UTF8编码的。来看下吧。其实他就是检查是否是最后一帧,如果是文本帧的话就检测内容,不是UTF8的就抛异常。如果是持续帧,只有第一帧是文本的才会开始检测,所以后续来的肯定是文本帧,就不用判断是不是文本帧了,只要判断是不是在检测就好了。@OverridepublicvoidchannelRead(ChannelHandlerContextctx,Objectmsg)throwsException{if(msginstanceofWebSocketFrame){WebSocketFrameframe=(WebSocke
WebSocket13FrameDecoder解码器我们拿这个解码器版本来讲,其他的原理都差不多的,这个就是我们在握手的时候创建的解码器,我们新来看看结构:其实主要的操作还是在WebSocket08FrameDecoder里面,你会发现WebSocket13FrameDecoder没什么东西。所以我们主要还是讲WebSocket08FrameDecoder吧。WebSocket08FrameDecoder解码器属性还是老规矩,先了解下一些属性,这个跟HTTP的请求解码器很类型,也是根据状态来执行的,因为毕竟协议还是有个解码的过程的。//读取状态enumState{READING_FIRST,/
handlerAdded处理器添加事件上一篇讲了一些基础的知识,本篇就讲下具体是怎么样的,首先就是添加WebSocketServerProtocolHandler之后的handlerAdded事件。因为是WebSocket协议,肯定需要一些处理器,所以这里就会添加一些处理器,比如第一次的握手处理器,UFT8帧验证器来验证文本帧,还要关闭帧处理器,用来响应关闭帧@OverridepublicvoidhandlerAdded(ChannelHandlerContextctx){ChannelPipelinecp=ctx.pipeline();if(cp.get(WebSocketServerPro
WebSocket一种应用层协议,主要解决HTTP强交互时候的问题,节省带宽,提高传输效率,用在实时性比较强的地方,比如竞技游戏,RPC通信,即时通信等。具体的通信协议,格式我后面会说。如何使用WebSocket在html中的js写socket=newWebSocket("ws://localhost:8080/wc");,即希望请求从HTTP升级到WebSocket,其实就是握手的协议内容。服务端代码一般代码:ChannelPipelinepipeline=ch.pipeline();pipeline.addLast(newHttpRequestDecoder());//
聚合消息前面我们讲了,一个HTTP请求最少也会在HttpRequestDecoder里分成两次往后传递,第一次是消息行和消息头,第二次是消息体,哪怕没有消息体,也会传一个空消息体。如果发送的消息体比较大的话,可能还会分成好几个消息体来处理,往后传递多次,这样使得我们后续的处理器可能要写多个逻辑判断,比较麻烦,那能不能把消息都整合成一个完整的,再往后传递呢,当然可以,用HttpObjectAggregator。先介绍一下一些属性HTTP有个头属性Except:100-continue用来优化服务器和客户端数据传输的,在要发送比较大的数据的时候,不会直接发送,而是会先征求下服务器意见是否可以继续发
READ_HEADER读取头我们继续上一篇,现在的状态到了读取了。首先会先解析请求头,然后看里面有没有transfer-encoding或者content-length,来进行后续的消息体读取。caseREAD_HEADER:try{//读取请求头StatenextState=readHeaders(buffer);if(nextState==null){return;}currentState=nextState;switch(nextState){caseSKIP_CONTROL_CHARS://没有内容,直接传递两个消息out.add(message);out.add(LastHttpC
HttpRequestDecoder这东西一看就知道是HTTP请求的解码器,就是用来解析HTTP协议格式的。我们用一个简单的例子来说,就下面这个,一个请求解码,一个响应编码,最后一个自定义的处理器,我们来分析下,请求解码做了什么。pipeline.addLast(newHttpRequestDecoder());pipeline.addLast(newHttpResponseEncoder());pipeline.addLast("MyTestHttpServerHandler",newMyHttpServerHandler());类结构看到他是继承了一个HttpObjec
HTTP的基本知识其实这个到处都有,我就讲几点Netty里比较关注的。首先先看下这个协议的格式,这个是请求,响应差不多的:找个真实的数据看看:这个是POST方法的:这个是GET的:至于区别什么的百度下吧,这里就想说几个点,Netty里会碰到。POST消息体传输方式直接发送消息体最长见的就是直接在POST请求头里设置了Content-Length属性,也就是所谓的定长,只要接收端根据这个去读定长的字节就行,这个方便是方便,但是如果一个比较大的数据,可能要消耗比较大的内存。块传输还有一种是Transfer-Encoding:chunked,然后消息体可以分成好几次传,有一定的格式规范,这个其实就可
MessageToByteEncoder前面说了下解码器,现在说说编码器,就是将一个消息编码下,编码成我们的字节数组形式。我们看到这个地方是有泛型了,为什么前面解码器没有呢,因为解码是把字节数组变成对象,对于我来说输入是字节数组,我不知道什么类型,只要转成对象类型即可。而编码器的泛型是针对特定类型的编码,你不告诉我什么类型,我没办法编码,我只能不处理,所以你要告诉我类型,那我检查到是我要编码的类型,我就进行相应的编码。解码是针对入站数据,那编码就是出站啦,结构就是这样:抽象方法:protectedabstractvoidencode(ChannelHandlerContextctx,Imsg,