netty的理解

1、传统io:
1、server端创建连接方法server.accept()、从连接中读取数据的方法inputStream.read(bytes),这两个方法都是阻塞的,表示如果这两个方法没有返回值时候,
线程就是一直在等待返回结果的,不能处理其他请求的。
2、一个线程同时只能为一个client的连接服务,如果这个连接不断开,这个线程就不能为别的client服务。这导致处理多个连接需要多个线程,线程的创建会消耗
内存,这样会消耗大量的内存,所以在处理多连接的场景中传统io是不适合的。
2、nio:
1、nio中的channel对应传统io中的socket
2、一个线程同时可以为多个client的连接服务,一个线程对应一个selector即一个线程借助一个selector来为多个连接服务。
3、selector.select()这个方法是阻塞的,如果有返回值,表示这个selector管理的连接中有一部分连接需要进行处理了,得到的是selectionkey的集合,根据selectionkey
就可以找到对应的连接,为这个连接进行相应的事件处理(读数据、创建连接),这个方法底层是c语言实现的。
1、selector.select()这个方法默认是阻塞的,也可以通过参数设置为不阻塞立刻返回结果的,关键他的read方法是不阻塞的,所以nio是非阻塞的io。


4、一个线程管理一个selector,一个selector管理多个selectionkey,一个selectionkey对应一个连接。
5、开发时候根据业务需要,比如读取到数据后在写数据给client,如果没有读取到数据可能就是client断开连接了,这时候就可以不需要写出数据了。
6、server监听一个端口,client建立连接,创建连接后得到对应的channel,把这个channel注册到一个selector上面,让这个selector管理这个channel的读取数据事件。


3、netty:
1、传统io是socket,nio是channel,netty是对nio的封装,让我们使用起来更简单。
2、netty的主要使用场景是基于tcp长连接实现进程之间的通信,比如大数据底层好多进程之间的通信都是使用的netty。
3、boss线程是管理线程,worker线程是工作线程,bootstrap是netty的启动类,启动类中指定了pipeline管道,管道可以看成一个拦截器链,可以添加多个handler进行
处理。不同handler进行不同的处理,比如decoder是处理上行数据,encoder是处理下行数据。
4、messageReceive方法中判断频次过高就可以通过channel把这个连接关闭掉,把client的ip计入黑名单,channelConnected方法中判断如果是黑名单的ip就不进行连接。
5、boss线程池中一个线程对应一个selector,这个selector负责一部分连接的创建。worker线程池中一个线程对应一个selector,这个selector负责一部分连接的读取
数据事件处理。
6、更确切的是一个Runnable任务对象有一个selector的成员变量,一个Runnable对应一个selector,一个线程对应一个Runnable任务,一个selector对应多个channel,
一个channel对应一个selectionkey。


3、线程池:
1、CachedThreadPool:带缓存的线程池,来了请求如果没有空闲线程,就会创建新的线程来处理请求,这样可能导致创建无限多的线程。
2、FixedThreadPool:固定大小的线程池,来了请求如果没有空闲线程,就会放入这个线程池对应的一个队列中,如果队列大小没限制,无限的请求可能导致无限大的队列。
3、线程池中一个线程对应一个队列,一个队列中可以存放多个runnable任务对象。多个线程之间执行的任务是并行,一个线程执行的任务是串行。


4、解决线程安全:
1、a线程通过在b线程的任务队列中添加任务的方式,让b线程去改变某个数据的值,而不是a、b两个线程都来修改这个数据的值,这样就很好的解决了线程安全问题,这是
一种思路。
2、boss、worker线程不都是直接修改channel数据的,而是boss线程创建一个任务丢给某个work线程去处理,这样间接的达到boss和worker都处理一个channel数据,但是
还没有线程安全问题。
3、如果任务对象是线程安全的,即可以多个线程同时操作数据也不会乱,这样多个线程操作这个任务对象时候不需要有阻塞等待,这个时候可以使用对象数组来存放任务
对象。
4、如果任务对象是线程不安全的,多个线程操作一个任务对象,这个数据可能会乱,这样多个线程不能同时操作一个任务,需要阻塞等待,这个时候可以使用对象池来
存放任务队列,类似连接池。
5、对象组中取出对象执行时候,是不需要把对象从数组中移除的(数组)。对象池取出对象执行时候,是需要把对象从池中移除使用完后在放回(队列)。
6、client实现自动重连server进行数据通信:本质是判断原来的channel不是active状态,就重新创建一个channel,利用新的channel跟server通信。
1、网关、设备、手机app作为client时候,为了提高用户体验,需要实现自动重连功能。
2、client可以给server发送心跳报文,server返回服务器的当前时间,如果心跳报文失败,client判断连接断开了,就可以进行重连了。
7、client可以实现多个连接,比如放到数组中,并且这多个连接都是可以自动重连的。这样的实例可以参见MultiClient类。


5、netty使用经验总结:
1、防止恶意攻击改进方案:
原来流程:client跟server创建后,发送登陆报文,成功走后面流程,不成功断开连接,这样存在的漏洞是,client跟server创建连接后不发送登陆报文,而是发
送其他一些报文,这样虽然不能进行后面业务逻辑操作,但是这个连接没有断开,还是占用服务器的资源。
改进流程:client跟server创建连接后,必须第一步就发送登陆报文,否则断开连接。思想是记录每个连接的第一个报文,判断是否是登陆报文。
2、server端可以指定一个handler,设置读超时、写超时、读写超时的时间,设置完后某个client连接到server,如果长时间没有读写操作,server端是可以感知到的,感
知到后可以踢用户下线,或者设置设备为下线状态,看业务需求进行处理。
1、server端长时间没有收到client的读写请求时候,server端就可以关闭channel释放资源,清除掉僵尸连接。
2、正常情况,client跟server端建立连接后,如果client端主动断开连接了,会发送通知给server端,server端channelclose(netty3)方法、inactive(netty5)
方法会被触发执行,这样我们可以在里面把channel关闭释放资源。
3、但是特殊情况,client突然断电或者断网,这样server端channelclose(netty3)方法、inactive(netty5)方法是不会触发的,就不会执行channel.close()了,
所以这种情况我们可以使用上面handler判断长时间没有跟client进行读写时候,就把这个channel关闭掉,这样达到释放资源效果更准确。
4、由client通知server释放资源,修改为server端自己检查如果长时间没交互,就关闭连接释放资源。
3、server端可以利用一个监听器,监听到给client端发送成功提示消息后,在关闭连接踢用户下线。
4、server端返回通常不是返回字符串,返回一个结果码,client根据提前约定的查找结果码代表什么意思。

6、对象序列化:
1、序列化:把对象转换成byte[]
2、利用protocol buff序列化和反序列化
1、自己编写.pro文件,利用他提供的工具根据.pro文件生成java类、和工具类
2、我们在项目中就可以使用这些工具类来进行序列化和反序列化了
3、比java自带的序列化得到的结果小很多,这样可以减少传输带宽,提高传输效率
3、java自带的ObjectStream进行序列化反序列化

3、把一个byte[]中的内容打印出来可以使用Arrays.toString(byte[])


7、分包粘包:
1、server端接收client端的报文数据,不知道哪个是一个完整的报文
2、解决办法1、可以使用特殊分隔符对报文进行分割 2、长度+数据的报文格式
3、自定义协议开发client和server端通信过程:
1、client构建request对象,利用利用requestEncoder转换成字节数组,发送给server
2、server收到byte[],利用requestDecoder解码得到request对象
3、server进行业务处理,构建response对象,利用responseEncoder得到byte[]返回给client
4、client得到byte[]利用responseDecoder解码得到response对象
4、缓冲区中的内容可能是多个报文的数据,这就产生了粘包问题,缓存区中取出的是一个报文的一部分,这就发生了分包现象
5、可以自定义一个Decoder类继承FrameDecoder,在里面处理封装成一个完整报文在传递给handler进行处理


6、报文格式如果只是长度+数据,这样可能被恶意攻击,发送很大内容的数据把服务器缓存撑爆,即socket字节流攻击
7、解决办法是:报文中必须还要增加报文头(也可以叫做包头),这样一个特殊的分隔符来判断报文的开始,这样遇到非法数据后,就可以丢弃非法数据,从后面
一个字节一个字节的查找包头,找到下一个报文的起始位置,把非法数据丢弃掉不处理,可能会损失一些真正报文,如果业务不允许,就要进行复杂的
代码处理。
8、decode方法中return null,表示数据为到全,等待数据到来。return一个对象表示解析出来了一个报文数据,传给下一个handler进行处理。
9、调用了buffer的read方法,指针就会后移,buffer.readableBytes()可读取的内容就会减少。


10、netty接收数据的思想:定义好报文的格式,把接收到的数据先放到一个缓冲字节数组中,先判断长度是否满足报文定义的基本长度才开始进行处理,字节数组
中内容太长表示恶意攻击,清空字节数组的内容,字节数组中的长度小于了报文定义的基本长度,又会回到起始位置等待接收新的数据,如果字节数组
的长度大于报文定义的基本长度,可读取的内容也不是太大,那么就开始读取包头,如果没找到就一个字节一个字节的略过,直到找到包头,顺序读取
协议规定的内容,读取真正数据时候如果长度不够,就return null等待数据到来后重新前面的逻辑。
11、netty简单理解:数据来了放入缓存,解析出一个报文就后传,解析不出来表示数据还没有到齐在缓存中等待剩余部分到来。所有没处理的报文都在缓冲区中
存放着,凑个了一个报文解析出来向后传递,缓冲区中的报文就减少一个。


8、利用netty开发中,encoder、decoder、handler的作用
1、接收到数据利用decoder可以把字节数组转换成对象
2、利用encoder把对象的各个部分顺序写出去,这样就可以保证发送出去的是相应的字节数组了
3、handler中收到的数据就是对象了,我们直接用来进行业务逻辑处理


4、decoder和handler是配合使用的,先经过decoder的处理,handler中就会拿到相应的结果进行处理了
5、如果使用的是DelimiterBasedFrameDecoder,那么handler中拿到的就是分割好的byte[]
6、如果使用的是自定义的RequestDecoder,那么handler中拿到的就是RequestDecoder中解析好的Request对象了


7、不管是client还是server端,都是decoder后得到数据handler中进行处理,业务处理完后,把返回对象encoder成byte[]写出去
9、一个消息在channel中是怎样在多个handler间进行流转的
1、encoder和decoder本质上也是handler
2、messageEvent进入管道后,会顺序查找哪些handler是可以处理这个event的,可以处理的就进行处理
3、这些handler是通过双向链表串联起来的
4、一个messageEvent经过一个handler处理后,可能产生一个或者多个event,这些event向后流转查看是否有相应的handler对他们进行处理
5、handler往下传递对象的方法是sendUpstream(event),如果是decoder他向后传递对象的方法就是return对象就可以了,decoder返回的什么类型的对象handler中
就可以进行相应的强转就可以使用了。
6、一个线程处理的一个handler对象,所以handler是线程安全的,可以使用他的成员变量int count来记录处理的次数,是没有并发线程安全问题的。


10、自定义注解的使用:
1、自定义注解标示到自己的业务类上面,比如模块注解、命令注解
2、把业务类交给spring管理,这样就可以利用spring的一个工具类(自定义Scanner实现BeanPostProcessor),实例化完bean后,解析bean上面的注解
3、如果发现bean上面有我们自定义的注解,并且bean的方法上面有自定义的注解,那么就把这个bean和他的method进行存储,这样springioc启动完成后
会把所有标记了注解的,bean、method构造成一个invoker对象,然后把这些invoker对象放到一个map中,根据业务要求map的结构如下:
Map<"模块标示",Map<"命令标示",invoker>> ,如果业务分多个模块了
Map<"命令标示",invoker>,如果业务没有分多个模块开发
4、接收报文解析出模块标示、命令标示,就可以从map中找到对应的invoker了,解析出的报文把参数传递过来,进行invoker调用就可以了。实现通信框架和业务的解耦。
5、根据bean找到字节码文件,然后找到类名,然后找到类上面的注解,然后找到类里面方法上面的注解。


11、netty3和netty4的主要区别:
netty3 netty4或5


ChannelBuffer ByteBuf


ChannelBuffers PooledByteBufAllocator(要注意使用完后释放buffer)或UnpooledByteBufAllocator或Unpooled


FrameDecoder ByteToMessageDecoder


OneToOneEncoder MessageToByteEncoder


messageReceive channelRead0(netty5里面是messageReceive)


12、使用自定义的业务线程池,避免worker线程处理业务产时间占用,导致worker线程池不能处理高并发的请求:
1、handler的channelRead方法得到request对象后,直接交给业务对象去处理,这样handler和serviceimpl对象中都使用的是worker线程
2、打印日志可以看出来
3、有高并发的client的请求时候,worker线程不能及时处理
4、解决办法就是增加公工作线程池,handler中的worker线程接收到request对象后,直接构建成runnable任务对象丢给工作线程池中的线程去处理
5、这样worker线程可以处理的client的qps就更高了
6、业务线程池的大小设置的是cpu核数*2,仿照netty中设置worker线程池大小设置的


7、上面是netty3的解决方案,netty4中可以自定义一个EventLoopGroup的业务线程池,这样处理业务的线程就不是worker线程了,思想是类似的。
8、netty4中使用EventLoopGroup的业务线程池,在addhandler时候指定即可。
9、这种方式比netty3中使用自定义的业务线程池,把runnable任务丢进去的方式要好,他可以做到一个channel即一个用户的多个请求是顺序处理的。
10、线程池中的每个线程都对应一个队列,使用EventLoopGroup的业务线程池这种方式他底层保证了,一个channel(一个用户)的请求放到一个线程池的队列中去处理
这样就保证了,肯定是一个线程来顺序处理这些请求的了。
11、本质是把一个用户的请求放到了一个线程对应的队列中去处理,netty3中自定义的线程池,可能是把一个用户的请求放到了不同线程的多个队列中,这样导致这些
请求的处理就不是顺序的了。

12、一个用户的请求让一个业务线程去处理,一个线程可能处理多个用户请求的,不是让多个去处理。





















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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值