1)下载Netty包:
Project Structure-->Modules-->Dependencies-->右边的+--Library-->From Maven-->io.netty:netty-all:4.1.20.Final(勾选Download Sources JavaDocs)
2)在Dependencies中勾选下载好的netty库
3)在工程的lib下发现了包,分为3类:
class(开发用的类)
javadoc
sources(源码文件): 里面还有个example目录,有很多例子
4)服务器启动流程:
(1)初始化
EventLoopGroup BossGroup(老板线程)
EventLoopGroup WorkerGroup(工作线程)
(2)初始化助手(链式操作):
ServerBootStrap bootstrap
1:group 设置2个线程组
2:channel 设置NioSocketChannel作为通道的实现
3:option 设置线程队列得到的连接个数
4:childOption 设置保持活动连接状态
5:childHandler 给我们的WorkerGroup的EventLoop对应的管道设置处理器:
现有的
自己自定义的
sync: 绑定一个端口并且同步,生成一个ChannelFuture对象,立即返回 // 启动服务器了,并且绑定了端口
closeFuture().sync() 对关闭通道进行监听 // 异步模型
shutdownGracefully: 优雅的关闭
(3)服务器端Handler的编写:
1.入栈的Handler的适配器: 我们自定义的Handler需要继承netty规定好的某个HandlerAdapter
2.这是我们自定义一个Handler,才能成为一个Handler
channelRead: 读取客户端发送的消息-->转发给Pipeline关联的Handler上:
ChannelHandlerContext ctx: 上下文对象,含有管道pipeline(管道里面可以关联很多的Handler,像一个水流一样-->业务逻辑处理管道),
通道channel(数据的读写-->数据通道),地址
Object msg: 客户端发送的数据,默认是Object-->要转成ByteBuf
channelReadComplete: 读取数据完毕
ctx.writeAndFlush(): 2个方法的合并,将数据写入到缓冲,写入到管道并刷新
exceptionCaught: 发生异常,关闭通道
--->Handler写好后,加入Pipeline
5)客户端启动流程
(1)事件循环组:
因为客户端发一个消息,服务器还有消息过来,肯定是要有一个循环组
(2)启动助手 Bootstrap: 注意和服务器的不一样,少了个Server
参数设置:
group: 设置线程组
channel: 设置客户端通道的实现类,要用反射来处理
handler: 客户端也在不停的循环,也是需要一个Handler来处理;
通过channel拿到pipeline-->向管道里面加入自己的处理器Handler
connect: 连接 sync立马回返回
关闭监听: ChannelFuture,涉及netty的异步模型
(3)客户端Handler(注意事件驱动):
1:继承: ChannelInboundHandlerAdapter-->Inbound就是入栈操作-->入栈、出栈就涉及到管道
2:接口:
channelActive: 当通道就绪就会触发
writeAndFlush-->Unpooled.copiedBuffer拷贝 UTF8编码
channelRead: 有数据可读被触发
注意是ByteBuf
exceptionCaught:
6)
(1)默认8个EventLoop;
Debug
(2)NioEventLoopGroup
EventExecutor
8个NioEventLoop
(3)但是BossGroup 1个也够了, 8个Worker
(4)NettyServerHandler-->channelRead在不同的Worker线程中
默认情况下,8个EventLoop,那么循环分配给客户端服务;
每一个EventLoop有自己的Selector
(5)channel与pipeline的关系(2个容易误会的概念):
pipeline的本质是一个双向链表,出栈入栈的问题;
Channel channel = ctx.channel();
ChannelPipeline pipeline = ctx.pipeline();
ctx(重量级对象):
handler: 自己自定义的NettyServerHandler
next:
pre:
inbound: true则表示入栈的状态
pipeline: DefaultChannelPipeline
head:头
tail:尾
channel:可以拿到管道,说明pipeline和channel是对应的关系,pipeline可以反向拿到channel,我中有你你中有我,相互包含;
localAddress:
remoteAddress:
eventLoop(反向拿到自己属于哪个EventLoop):
总结:pipeline与channel是相互包含的关系,你中有我,我中有你;
ctx更加强大,把pipeline和channel都包含了进去;
7)Netty模型中的任务队列
(1)Selector
(2)TaskQueue
在事件循环中,在Pipeline中,我们会有一系列的任务Handler需要处理,a如果我们在Handler中我们如果有一个长
时间操作,那么势必会对Pipeline造成阻塞,这时我们可以把这些花费较长的业务提交到TaskQueue中进行异步处理,
这样子Pipeline不会阻塞;
TaskQueue和Channel是有一个绑定关系的;
(3)3种任务队列
1:用户自定义的普通任务:
ctx.channel().eventLoop().execute(new Runnable(){
public void run(){xxx}
});
说明: 把任务提交到Channel对应的NIOEventLoop的taskQueue中
1:证明: ctx-->pipeline-->channel-->eventloop(NioEventLoop)-->taskQueue;
2:taskQueue还是在一个线程,因此多个任务的话,后面的后执行,时间之和累计;
2:用户自定义定时任务:
通过schedule定时任务:
说明: 把任务提交到了ScheduleTaskQueue
证明: ctx-->pipeline-->channel-->eventloop(NioEventLoop)-->scheduleTaskQueue
3:非当前Reactor线程调用Channel的各种方法:
例如:在推送系统的业务线程里面,根据用户的标识,找到对应的Channel引用,然后调用Write类方法向该用户推送信息,
就会进入到这种场景,最终的Write会提交到任务队列中后被异步消费;
因为一个用户对应一个Channel,通过用户标识找到用户的Channel,系统中维护所有的Channel,调用Write;
说明:
例子:
1:一个Server 3个管道: A、B、C
2:关键的地方:拿到各自的Channel,就可以拿到EventLoop,就可以推送过去
3:维护各个Channel引用的实现思路:
在NettyServer的initChannel中,每一个用户来了,都可以拿到SocketChannel,根据用户标识记录下,
可以使用一个集合管理 SocketChannel,再推送消息时,可以将业务加入到各个Channel对应的NioEvevntLoop
的taskQueue中 或者 scheduleTaskQueue
8)Netty模型再说明:
(1)Netty抽象出两组线程池,BossGroup专门负责接收客户端连接,WorkerGroup专门负责网络读写操作;
(2)NioEventLoop标识一个不断循环执行处理任务的线程,每个NioEventLoop都有一个Selector,用于监听绑定在其上的
socket网络通道;
(3)NioEventLoop内部采用串行化设计,从消息的 读取-->解码-->处理-->编码-->发送,始终由IO线程NioEventLoop负责:
1:NioEventLoopGroup下包含多个NioEventLoop;
2:每个NioEventLoop中包含有一个Selector,一个taskQueue;
3:每个NioEventLoop的Selector上可以注册监听多个NioChannel;
4:每个NioChannel只会绑定在一个唯一的NioEventLoop上;
5:每个NioChannel都绑定有一个自己的ChannelPipeline;
9)异步模型分析
ajax
出栈入栈
(1)ChannelFuture:
10)Netty开发Http服务
Pipeline独立,Handler也是独立的
(1)Codec[coder decoder]: netty提供的编解码器 HttpServerCodec
(2)添加一个自定义的处理器Handler
(3)设置返回值类型
设置返回的数据的长度
11)Netty核心组件
(1)引导类
Bootstrap: 客户端引导类
ServerBootstrap: 服务器引导类
作用:一个Netty应用通常由一个Bootstrap开始,主要作用是配置整个Netty程序,串联各个组件,Netty中
BootStrap是客户单程序的启动引导类, ServerBootStrap是服务器端的引导类;
(2)常用方法
-----服务器-----
1: public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
该方法用于服务端,用来设置2个EventLoop
2: public BootStrap channel(Class<? extends C> channelClass)
该方法用来设置1个服务端的通道实现
3: public BootStrap option(ChannelOption<T> option, T value)
用来给ServerChannel添加配置
4: public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value)
用来给收到的通道添加配置
5: public ServerBootstrap childHandler(ChannelHandler childHandler)
该方法用来设置业务处理类(自定义的Handler)
注意:
handler: 该handler对应的bossGroup
childHandler: 对应WorkerGroup
6: public ChannelFuture bind(int inerPort): 该方法用于服务器端,用来设置占用的端口号
-----客户端-----
1: public BootStrap group(EventLoopGroup group)
该方法用于客户端,用来设置1个EventLoopGroup
2: public ChannelFuture connect(String inetHost, int inerPort)
该方法用于客户端,用来连接服务器
(3)异步类: 把操作变成异步的操作
Channel channel(): 返回当前正在进行IO操作的通道
ChannelFuture sync: 等待异步操作执行完毕
(4)Channel:
1:配置
cf.channel().config().xxx
2:Channel的类型
NioSocketChannel(常用)
NioServerSocketChannel(常用)
NioDatagramChannel
NioSctpChannel
NioSctpServerChannel
(5)Selector
1:Netty基于Selector对象实现IO多路复用,通过Selector一个线程可以监听多个连接的Channel事件
2:当向一个Selector中注册Channel后,Selector内部的机制就可以自动不断的查询(Select)这些注册的Channel
是否有已经就绪的IO事件(可读、可写、网络连接完成等),这样程序就可以很家的使用一个线程高效的管理多个Channel
(6)ChannelHandler及其实现类
1:ChannelHandler是一个接口,处理IO事件或拦截IO操作,并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序;
2:ChannelHandler本身并没有提供很多方法,因为和这个接口有许多的方法需要实现,方法使用期间,可以继承它的子类;
3:ChannelHandler及其实现类:
总结: 入栈和出栈对应读写
ChannelInboundHandler:用于处理入栈IO事件
ChannelOutboundHandler:用于处理出栈IO操作
适配器类:
ChannelInboundHandlerAdapter:用于处理入栈IO事件
ChannelOutboundHandlerAdapter:用于处理出栈IO操作
ChannelDuplexHandler: 用于处理入栈和 出栈事件(不用,容易混淆)
理解出栈和入栈
出栈:
ChannelPipeline提供了ChannelHadler链的容器,以客户端应用程序为例子,如果事件的运动方向是从客户端-->服务器,那么这些事件
为出栈的,即客户端发送给服务端的数据会通过Pipeline中的一系列ChannelOutboundHandler,并被这些Handler处理
入栈:
相反
后面从:编码解码器例子来理解。
我们经常需要自定义一个Handler类去继承ChannelInboundHandlerAdapter,然后重写相应的方法实现业务逻辑:
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler{
channelRegister: channel注册发生
channelUnregister
channelActive: --通道就绪事件
channelInactive
channelRead: --通道读取数据事件
chanelReadComplete: --数据读取完毕事件
userEventTriggered
channelWritabilityChanged
exceptionCaught: --通道发生异常事件
参数: ChannelHandlerContext ctx
}
Handler是一个非常复杂的体系,构成了一个非常核心的业务处理的知识点;
(7)Pipeline和ChannelPipeline(重点: 是一个双向链表,每个元素都是ChannelHandlerContext)
1:ChannelPipeline是一个Handler的集合,它负责处理和拦截inbound或者outbound的事件和操作,相当于一个贯穿Netty的链
(也可以这样理解:ChannelPipeline是保存ChannelHandler的List,用于处理或拦截Channel的入栈事件和出栈操作);
2:Channelpipeline实现一种高级形式的 拦截过滤器 模式,使用户可以完全控制事件的处理方式,
以及Channel中各个的CahnnelHandler如何交互;
Channel
ChannelPipeline
head头 tail尾
ChannelHandler ChannelHandler ChannelHandler ChannelHandler
ChannelHandlerContext ChannelHandlerContext ChannelHandlerContext ChannelHandlerContext
理解:
一个Channel包含了一个ChannelPipeline(反过来认为也可以),而ChannelPipeline中又维护了一个由ChannelHandlerContext组成的双向
链表,并且每个ChannelHandlerContext(真实类型是DefaultChannelHandlerContext)中又关联着一个ChannelHandler;
入栈和出栈事件在一个双向链表中,出栈事件会从链表head往后传递到最后一个入栈的Handler,
出栈事件会从聊表tail往前传递到最前一个出栈的Handler,两种类型的Handler互不干扰
3:理解Pipeline到底是什么东西?
ChannelPipeline pipeline = ch.pipeline();
head:
next(DefaultChannelHandlerContext: 证明了双向链表里面放的是ChannelHandlerContext, 而不是直接放的Handler)
handler=TestServerInitializer(这个本身也是一个Handler)
next=
handler(HttpServerCodec 这也是一个Handler)
next=
handler=DefaultChannelHandlerContext(TestHttpServerHandler, 这)
next=DefaultChannelPipeline(这是最后一个了,而且没有包含Handler了)
next=null
prev=
prev = null
tail:
channel: 说明Pipeline可以拿到channel
入栈:(Server)head-->tail(Client), 因此服务器到客户端的话,是一个入栈, 客户端往服务器流动的话,认为是一个出栈
出栈: tail-->head
ctx.getClass()-->得到真实类型 DefaultChannelPipeline
4:常用的方法(把你的Handler放到哪里去)
ChannelPipeline addFirst(ChannelHandler handlers): 把一个业务处理类(Handler)添加到链中的第一个位置;
ChannelPipeline addLast(ChannelHandler handlers): 把一个业务处理类(Handler)添加到链中的最后一个位置;
(8)ChannelHandlerContext
1:保存Channel相关的上下文信息,同时关联一个ChannelHandler对象;
2:即ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,
同时ChannelHandlerContext中也绑定了对应的pipeline和Channel的信息,
方便对ChannelHandler进行调用;
3:常用方法:
ChannelFuture close() 关闭通道
ChannelOutboundinvoker flush() 刷新
ChannelFuture writeAndFlush(Object msg): 将数据写到ChannelPipeline中当前ChannelHandler的下一个ChannelHandler(出栈)
(9)ChannelOption
Netty在创建Channel实例后,一般都需要设置ChannelOption参数:
1:ChannelOption.SO_BACKING
可连接队列大小
2:ChannelOption.SO_KEEPALIVE 一直保持连接活动状态
(10)EventLoopGroup和其实现类NioEventLoopGroup
1:EventLoopGroup是一组EventLoopGroup的抽象,Netty为了更好的利用多核CPU资源,一般会有多个EventLoop同时工作,
每个EventLoop维护着一个Selector实例;
2:EventLoopGroup提供next接口,可以从组里面按照一定规则获取其中一个EventLoop来处理任务,在NEtty服务器编程中,
我们一般都需要提供2个EventLoopGroup,例如 BossEventLoopGroup和WorkerEventLoopGroup;
3:通常一个服务端口即一个ServerSocketChannel对应一个Selector和一个EventLoop线程,BossEventLoop负责接收客户端的连接
并将SocketChannel交给WorkerEventLoopGroup来进行IO处理,如下:
1:BossEventLoopGroup通常是一个单线程的EventLoop,EventLoop维护者一个注册了ServerSocketChannel的Selector
实例,BossEventLoop不断轮训Selector将连接事件分离出来;
2:通常是OP_ACCEPT事件,然后将接收到的SocketChannel交给WorkerEventLoopGroup
3:WorkerEventLoopGroup会由next选择其中一个EventLoopGroup来讲这个SocketChannel注册到其维护的Selector并
讲其后续的IO事件进行处理
常用方法:
shutdownGracefully: 断开连接 + 关闭线程 2个功能
12)Unpooled类
(1)Netty提供一个专门用来操作缓冲区(即Netty的数据容器)的工具类
(2)常用方法:
public static ByteBuf copiedBuffer(CharSequence string, Charset charset)
(3)readaerIndex
writerIndex
capacity
(4)writeByte
整型写入时可以被自动转化为byte
netty的buffer中,不需要flip翻转,因为里面维护了readaerIndex writerIndex 2个属性
(5)buffer:
1:
array:
readerIndex: 下一个读取的位置
writerIndex: 下一个写入的位置
2: 0 <= readerIndex <= writerIndex <= capacity
3: 方法:
readByte(): 会引起readerIndex的增加
getByte(int index): 获取固定位置,不造成增加
readableBytes(): 可读字节数
capacity(): 容量和里面实际存放数据多少是不一样的
4: 读可区域: readerIndex-->writerIndex
可写区域: writer-->capacity
5: 发送过程: 先将字符串转化为ByteBuf,然后发出
13)群聊系统
(1)channel: 服务器用的实现类
(2)option: ChannelOption.SO_BACKLONG 128
(3)childOption: 设置连接 ChannelOption.SO_KEEPALIVE, true
(4)childHandler: 给Pipeline指定相关的Handler
先获取到Pipeline
StringDecoder 接收数据,要先解码,也是Handler
StringDecoder 将来会送数据也要编码
加入自己的业务处理Handler
(5)集合的管理
static Channel组:
必须是static,因为每一个Channel都有一个Handler;
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE):
GlobalEventExcuter.INSTANCE 是一个全局的事件执行器,是一个单例;
static ArrayList
重要接口:
handlerAdded: 连接建立,一旦连接建立,第一个被执行--需要add;
handlerRemoved: 客户端断开连接,将xx客户端离开信息推送给当前在线用户--ChannelGroup的大小自动减小;
channelActive: 处理活动状态, 提示xxx上线,只在服务器打印下就行;
channelInactive: 处于不活动状态,提示xxx离线;
channelRead0: 读取数据
(6)记录登录状态进行私聊
Map<User, Channel>
Map<String, Channel>
14)Netty心跳检测机制
(1)IdleStateHandler
表示多长时间没有读,就会发送一个心跳连接检测是否连接
表示多长时间没有写,就会发送一个心跳连接检测是否连接
表示多长时间没有读写,就会发送一个心跳连接检测是否连接
(2)心跳检测不是那么的简单
当能触发handlerRemoved是非常好,但是有时断掉了,服务器根本无法感知:
如: 手机处于飞行状态, 强制关机
所以只有通过心跳包才能真正的检测有效的连接;
(3)处理空闲检测进一步处理的自定义的Handler,通过userEventTriggered:
1:获取哪个通道(客户端)发生了超时事件
ctx.channel().remoteAddress
来源:CSDN
作者:cocos+unity+node
链接:https://blog.csdn.net/themagickeyjianan/article/details/104238131