第1章
此方式(java API阻塞方式):一个客户端——一个线程
当客户端连接非常多时很不理想 Java NIO——非阻塞
回调就是ChannelHandler的方法(一个Handler包含多个回调)
事件是 按照它们与入站或出站数据流的相关性进行分类的。即网络事件 连接激活或连接失活 数据读取 用户事件 错误事件 打开或关闭到远程节点的连接 将数据写到或者冲刷到套接字 注册,绑定,写入,写入并冲刷等操作后都有返回ChannelFuture Bootstrap方法(绑定或连接成功后netty执行自己定义的Listener回调方法)和ChannelOutboundInvoker方法(ChannelInitializer自己实现方法initChannel 先后注册Handler到pipeline,netty自动根据顺序处理这些Handler,Handler调用这些自己实现的回调方法)
一个回调其实就是一个方法,一个指向已经被提供给另外一个方法的方法的引用,多种事件有多种回调【如建立连接,断开连接】
Channel 类型——EpollServerSocketChannel(Linux) 或 NioServerSocketChannel
连接回调后:Future添加Listener处理连接的多种情况(连接成功,发生错误等),即Future是回调的精细化处理。
第2章 你的第一款netty应用程序
********************************第2章 客户端和服务端主要两个东西 1ChannelHandler逻辑 2EventLoop引导
EventLoop:顾名思义 事件循环
EventLoop中有一个死循环的run方法,每时每刻都在检查绑定在自己身上的socket是否有事件发生,并处理这些事件;
一个ChannelPipeline有一个ChannelHandler实例链,即:一个Channel有一个ChannelHandler实例链(ChannelHandler集合)
引导ServerBootstrap
/** * 启动函数,地址和端口号 */ public boolean start(String address, int port, ChannelHandler handler) { boolean isEpoll = Epoll.isAvailable(); int cpuNum = Runtime.getRuntime().availableProcessors(); if (isEpoll) { bossGroup = new EpollEventLoopGroup(cpuNum); workGroup = new EpollEventLoopGroup(cpuNum * 2 + 1); } else { bossGroup = new NioEventLoopGroup(cpuNum); workGroup = new NioEventLoopGroup(cpuNum * 2 + 1); } try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workGroup); bootstrap.channel(isEpoll ? EpollServerSocketChannel.class : NioServerSocketChannel.class); bootstrap.handler(new LoggingHandler(LogLevel.INFO)); bootstrap.childHandler(handler); // TIME_WAIT时可重用端口,服务器关闭后可立即重启,此时任何非期 // 望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不可能 bootstrap.option(ChannelOption.SO_REUSEADDR, true); // 设置了ServerSocket类的SO_RCVBUF选项,就相当于设置了Socket对象的接收缓冲区大小,4KB bootstrap.option(ChannelOption.SO_RCVBUF, 1024 * 8); // 请求连接的最大队列长度,如果backlog参数的值大于操作系统限定的队列的最大长度,那么backlog参数无效 bootstrap.option(ChannelOption.SO_BACKLOG, 1024); // 使用内存池的缓冲区重用机制 bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); // 当客户端发生断网或断电等非正常断开的现象,如果服务器没有设置SO_KEEPALIVE选项,则会一直不关闭SOCKET。具体的时间由OS配置 bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); // 在调用close方法后,将阻塞n秒,让未完成发送的数据尽量发出,netty中这部分操作调用方法异步进行。我们的游戏业务没有这种需要,所以设置为0 bootstrap.childOption(ChannelOption.SO_LINGER, 0); // 数据包不缓冲,立即发出 bootstrap.childOption(ChannelOption.TCP_NODELAY, true); // 发送缓冲大小,默认8192 bootstrap.childOption(ChannelOption.SO_SNDBUF, 1024 * 8); // 使用内存池的缓冲区重用机制 bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); bootstrap.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(1024 * 64, 1024 * 128)); channelFuture = bootstrap.bind(new InetSocketAddress(address, port)); channelFuture.sync();//阻塞,知道绑定操作完成为止 Log.info("NettyComponentSocket->start, service start success address:{} port:{} isEpoll:{}", address, port, isEpoll); return true; } catch (Exception e) { Log.error("NettyComponent->start, init netty exception, address:{} port:{}", address, port, e); return false; } }
最大的区别在于SimpleChannelInboundHandler会对没有外界引用的资源进行一定的清理,并且入站的消息可以通过泛型来规定。
ChannelInboundHandlerAdapter是ChannelInboundHandler一个简单实现,默认情况下不会做任何处理。只是简单的将操作通过fire方法传到ChannelPipeline中的下一个ChannelHandler中让链中的下一个ChannelHandler去处理。但是需要注意的是信息经过channelRead方法处理之后不会自动释放(是因为信息不会被自动释放所以能将信息传给下一个ChannelHandler处理。其次,在实现类Handler中,你仍然需要将传入消息回送给发送者,而 write() 操作是异步的,直到 channelRead() 方法返回后可能仍然没有完成。为此,实现类Handler扩展了 ChannelInboundHandlerAdapter ,其在这个时间点上不会释放消息。) SimpleChannelInboundHandler支持泛型的消息处理,默认情况下消息处理完之后将会自动释放,无法提供fire*方法传递给ChannelPipeline中的下一个ChannelHandler.在客户端,当 channelRead0() 方法完成时,你已经有了传入消息,并且已经处理完它了。当该方法返回时,SimpleChannelInboundHandler负责释放指向保存该消息的ByteBuf的内存引用(自动调用Bytebuffer.release())。而为何服务器端不能用呢,因为我们想让服务器把客户端请求的数据发送回去,而服务器端有可能在channelRead方法返回前还没有写完数据,因此不能让它自动release。项目中服务端也用了,有点奇怪?——可能是s2s用到。
引导Bootstrap,服务端是local,客户端是remoteAddress
第3章 Netty的组件和设计
********************************第3章 Netty的组件和设计 引导——网络层配置容器
一个线程——一个selector(注册表作用) —— 多个客户端——多个Channel
服务端两组Channel(EventLoopGroup),一组管理服务端ServerChannel,一组管理传入的客户端连接的Channel。
第4章 传输
每个Channel都将会分配一个ChannelPipeline和ChannelConfig
拦截过滤器——执行一遍注册的拦截器(Handler方法)
1、先基于长度的解码器LengthFieldBasedFrameDecoder【继承自ByteToMessageDecoder】 (2 3)、服务端多两个Handler(心跳监测IdleStateHandler,心跳日志处理ChannelInboundHandlerAdapter) 2、再编解码器CombinedChannelDuplexHandler 的容器。 3、最后是接收处理消息的Handler
基于长度的解码器LengthFieldBasedFrameDecoder【继承自ByteToMessageDecoder】:
读取到的数据是基于流的, 而且是有方向的. 然而数据是没有边界的, 不知道从哪儿到哪儿是一个完整的数据, 下一个数据又是从哪个到哪个. 因此应用层需要设定规则, 根据规则就可以知道数据的边界在哪儿. 如何根据规则知道数据边界:规则一般是什么样的
如上图, 便是根据设定的规则, 就可以’筛选’出来真正有意义的数据(data)在哪个. 而且允许每个data的长度是不一样大小.
那么就要说下这个规则是什么了. 规则是由4个主要的属性构成, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip. 通过一个数据块为例介绍这4个属性.
如上图, 从红色箭头指向的位置开始读取数据. lengthFieldOffset表示长度字段的偏移量, 经过lengthFieldOffset之后, 箭头指向了下一个位置. 如果lengthFieldOffset=3, 那么箭头需要向右边走3个字节.
零拷贝特性。nio和 epoll类型传输时才可使用。ByteBuf内置的复合缓冲区类型 零拷贝。
第 5 章 ByteBuf
slice:片,切片
尽量使用slice替换copy方法,减少复制开销,但是要注意 共享数据问题。
第 6 章 ChannelHandler 和 ChannelPipeline
ChannelHandlerContext的高级用法。
第 7 章 EventLoop 和 线程模型
第8章 引导
标签:实战,netty,ChannelHandler,bootstrap,笔记,Handler,客户端,方法,ChannelOption From: https://www.cnblogs.com/erlongxizhu-03/p/17171048.html