首页 > 技术文章 > netty知识记录

carry-huang 2022-02-08 16:32 原文

Paths,Files 文件和目录的相关操作
Path path = Paths.get("d:/environment");
Path path1 = Paths.get("d:\\environment"); //和上面等价

byteBuffer
ByteBuffer bb = ByteBuffer.allocate();创建方法
StandardCharsets.UTF_8.decode() byteBuffer的转换
StandardCharsets.UTF_8.encode()

读写转换操作
allocate.flip();
allocate.clear();
allocate.compact();

socket开启 客户端与服务端 多路复用,在单线程的基础上,实现多个channel的可读写事件的监控
ServerSocketChannel ssc = ServerSocketChannel.open();
SocketChannel client = SocketChannel.open();
服务端绑定端口和接受连接
ssc.bind(new InetSocketAddress(8080));
SocketChannel accept1 = ssc.accept();
客户端连接
client.connect(new InetSocketAddress("localhost",8080));
非阻塞设置
socket.configureBlocking(false);
select版socket配置
Selector selector = Selector.open(); 创建select注册器
SelectionKey sscKey = ssc.register(selector, 0, null); 注册到select
sscKey.interestOps(SelectionKey.OP_ACCEPT); 选择触发事件类型,int类型,可以多个
selector.select();阻塞,开启事件监听 ,read没全读完还会继续触发
selector.wakeup() 三个会唤起select的阻塞
selector.close()
selector.interrupt()
监听类型分为,可读,可写,可连接,连接
Iterator<SelectionKey> iterator = selector.selectedKeys(); 返回触发监听事件的key
ServerSocketChannel channel = (ServerSocketChannel) key.channel();获取监听的channel,需要强转
scKey.attach();绑定附件信息
ByteBuffer bbf = (ByteBuffer) key.attachment();获取绑定的附件,需要强转,一个channel只能有一个byteBuffer否则会数据混乱


channel异常和正常关闭
channel.read(bbf)=-1表示正常关闭;key.cancel();也需要取消掉key
key.cancel();抛出异常时,需要将这个key取消掉

接受socket和可读事件的触发,需要考虑selector.select()会阻塞,注册。多线程下会发生
对列的作用:连接两个线程的通信!!
selector.wakeup();唤醒阻塞,当前没有存在阻塞,会唤醒下一次阻塞
System.out.println(ix.getAndIncrement());先获取值再+1
index.getAndIncrement() % workers.length 轮询方法,原子变量+1

read方法阻塞,java程序调用操作系统读写,等待+复制数据,在等待时阻塞了
非阻塞在等待时不阻塞,一直循环,看没有数据,复制数据时还是会阻塞


为何使用netty,而不是自己搭建
需要自己构建协议
解决了tcp的粘包,半包问题
epoll空轮询导致的cpu 100%
对api进行了增强, 例如byteBuffer ---> byteBuf

EventLoop是一个单线程执行器,类似于nio里的selector,一个channel只会绑定一个EventLoop
EventLoopGroup 是一个单线程组,接口
实现类:NioEventLoopGroup IO事件,普通任务,定时任务 普通任务,boss.next().summit(),scheduleAtFixedRate
DefaultEventLoopGroup 普通任务,定时任务

异步任务回调有两种 get/sync 或者增加监听器addListener,netty建议使用这种方法
客户端channel.close(),可以通过,channel.closeFuture(),捕获何时关闭,然后在关闭整个eventLoopGroup对应在服务端就会正常关闭

future 阻塞: (get,sync,await),getNow,cause非阻塞,cancel,isCanceled,isSuccess,isDone
promise setSuccess,setFailure
线程池不关闭的话,程序不会停止,因为他不是守护线程。


head->h1->h2->tail
入栈与出栈顺序相反

入栈中,写的方法 ch调用时 会从tail往前找所有的出栈
ctx调用时会从当前的handler往前找所有的出栈

byteBuf构建方法
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();//默认256 可以动态扩容,自动扩容
读写指针分开,不像bytebuffer读写需要切换,而是自动切换
支持各种数据类型写 int log double 也支持bytebuffer类型
read和write都会改变对应指针,get的话可以根据索引

直接内存和堆内存
直接内存少一次内存复制,读写效率高,且不受gc影响,gc压力减少,但分配效率低
堆内存分配效率搞,受gc影响,读写效率低
默认使用直接内存

byteBuf池化管理
池化最大意义就是在于重用ByteBuf,ByteBuf每次创建对内存开销大,如果是堆内存还会影响gc,池化之后复用,并且有分配的算法,高并发时节约内存,减少oom
4.1后默认开始了池化功能 安卓平台默认不开启,其他平台默认开启

bytebuf回收机制 netty提供了一个接口

bytebuf创建方法 一般在重新方法里带的ctx 参数,ctx.alloc.buffer(20)

channelActive 服务端方法,连接时会触发

粘包现象:客户端分十次发了数据,服务端一次全部都受到了
半包现象:客户端发送数据,服务端没有一次性读取
tcp会有以上问题,udp不会有
serverBootStrap可以设置参数 .option(),设置buy的缓存区大小

滑动窗口,tcp 一次请求有应答在发送,窗口大小可以先发送下一次的不必等待上一次的应答,起到了缓存作用,也限制了流量
无需等待应答而可以继续发送的数据最大值,会产生粘包半包现象

粘包现象的原因:bytebuf设置太大,默认1024; 滑动窗口 ; nagle算法
半包现象:bytebuf设置太大,默认1024; 滑动窗口 ; MSS限制
本质原因tco消息没有边界

滑动窗口会自适应,一般都不用设置

解决方法:1 短链接 : 发送一次客户端断开一次 好用?只能解决粘包,不能解决半包
2 消息定长,服务端接受固定长度,客户端长度不够自动补充
ch.pipeline().addLast(new FixedLengthFrameDecoder(10)); 服务端解码时可以用固定长度接收
3 使用分隔符
ch.pipeline().addLast(new LineBasedFrameDecoder(100)); /n /r/n都会一起作用,需要设置长度,不能无限读下去,超过这个长度会报错
自定义分割符:
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.copiedBuffer("$".getBytes(StandardCharsets.UTF_8))));
4 LTC解码器
得记住!十分常用,四个参数
* lengthFieldOffset = 0 实际内容长度字节偏移量
* lengthFieldLength = 2 实际内容的长度
* lengthAdjustment = 0 实际内容长度外的长度信息 (读取信息 前面 + 实际内容长度 + 调整长度 = 前面 + 实际内容的长度 + 额外信息 )
* initialBytesToStrip = 0 总长度去掉前面几个字节

协议!!
每个服务有自己不通的协议,例如redis协议!协议格式为:
/*
//信息内容如下,每行都要有回车符号
*3 // 表示内容有几个元素 redis常见 set key value 所以*3三个元素
$num1 //表示这个元素有几个字节 set三个自己 所以$3
set
$num2
key
$num#
value

*/

connect(new InetSocketAddress("39.104.84.240",6379));//通过此连接
codec包含了解码也包含编码
HttpRequest 请求头
HttpContent 请求体
ch.pipeline().addLast(new SimpleChannelInboundHandler<HttpContent>() 制定具体类型的类进行处理
写回相应:
DefaultFullHttpResponse response = new DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK);
response.content().writeBytes("<h1>hello,world<h1>".getBytes(StandardCharsets.UTF_8));
ctx.writeAndFlush(response);

推荐阅读