Netty 是什么?
Netty 是一个异步的、基于事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端
netty是基于nio 的封装,并且在nio的基础上构建了传输协议、解决了粘包半包等问题、对一些api进行了增强,关于nio的介绍可参考:【精选】nio的介绍-CSDN博客
netty中的组件:
EventLoop:本质是一个单线程执行器(维护了一个selector),通过run方法处理channel中的请求事件,一个EventLoop可以注册多个channel;
EventLoopGroup :
1、一个netty服务器一般会维护两个EventLoopGroup ,一个EventLoopGroup 用于接收客户端的连接请求(accept事件)、称之为boss,一个EventLoopGroup 用于处理channel中的读写事件(read和write事件)、称之为worker;
2、每个EventLoopGroup 可以维护多个 EventLoop,如果服务端只绑定了一个端口则boss中只需要维护一个EventLoop即可,worker中EventLoop的数量可以手动设置,如果没有手动设置则默认为CPU核心数的两倍;
3、当客户端连接服务端成功后生成一个channel,由boss将这个channel分配到worker中的某个EventLoop,这个channel中的所有请求都由这个EventLoop来处理(这样保证了io事件的线程安全);
netty的demo如下,有客户端(左)和服务端(右):
BootStrap 是服务的启动类,启动时指定 EventLoopGroup 、指定数据传输通道NioServerSocketChannel
pipeline是处理数据的通道,里面可以添加多个handler,每个handler对自己感兴趣的事件进行处理,当接收到客户端的数据后由pipeline中的handler对数据依次加工处理,handler分为Inbound和Outbound,Inbound是读数据操作、Outbound是将结果返回给客户端时的写操作;
ByteBuffer的增强ByteBuf,ByteBuf的特性:
池化和非池化:池化是将多个ByteBuf放在一个池中,需要用到的时候取出来用,用完之后再放回去,减少了对象创建时的消耗 和 GC的压力,同时也提供了非池化的创建方式;
直接内存和堆内存:ByteBuf也提供了基于系统内存和jvm内存的创建方式,基于系统内存也就是实现了零拷贝;
ByteBuffer中只有一个索引,当读和写切换的时候需要调用对应的方法进行读写模式的切换,而在ByteBuf中则维护了两个索引,一个读一个写,使用起来更加简单;
ByteBuf在初始化时可以指定初始容量和最大容量,当读取的数据超过初始容量时就自动扩容,但是不能超过最大容量;
ByteBuf的很多方法支持零拷贝,新特性不再一一列举,参考最下面的文档 Netty02-入门.md;
粘包和半包:
服务端通过buffer接收数据时会指定一个大小,客户端一次发送了多条报文,也就是服务端一次接收到了多条本文信息,数据是粘在一起的,叫做粘包
当客户端发送一个报文的数据量比较大时,超过了服务端接收数据的buffer大小,此时服务端需要接收几次才能接收完,这种情况叫做半包;
解决方案:
1、使用短链接发送数据,每次发送数据都建立一个新的链接,发完即断开,这样每个链接就是一个完整的报文,但这样效率太低;
2、在发送的消息中找一个最长的,设置为服务端的buffer长度,当客户端发送的消息达不到这个最大长度时以空格填充,这样在服务端每次接收的消息都是一个完成的消息,但是最大长度的消息在大多数时候都是不能确定的,这种方案并不实用;
3、在发送的每个消息之间通过 /n 进行分隔,但是当发送的消息本身就存在 /n 这样的分隔符时 服务端就会当成两条消息处理,也不实用;
4、客户端在发送消息的时候在消息前面增加一个header,herder中记录的是消息的长度,服务端在读取消息的时候首先获取这个长度,然后读取后面这个长度的字节作为一个消息,当读取了一个消息的一半而这个报文已经结束了,则会接着读这个channel中接下来的消息,一致读到这个长度为止,这样就完美的解决了粘包合半包的问题,也是目前主流的解决方案;
心跳:
当客户端连接服务端后,因为客户端死机或网络断开等原因造成客户端不可用,但是在服务端任然认为这个客户端是有效的造成服务端资源的浪费;
netty为避免这种 假死 情况使用了心跳机制,客户端在指定的时间内没有向服务端发送数据 则向服务端发一个心跳数据,服务端在指定的时间内没收到数据则断开客户端的连接;
一般客户端设置的事件是服务端设置事件的一半还少一点;
心跳实现原理,查看IdleStateHandler.initialize:
协议:
客户端和服务端之间的数据传输是没办法通过边界来划分报文的,协议的目的是划定消息的边界,指定通信双方共同遵守的通信规则;
自定义协议要素(参看下面代码 MessageCodecSharable):
魔数:用来判定是否是无效数据包,比如java的class文件是 cafe babe开头的;
版本号:可以支持协议的升级
序列化算法:客户端和服务端必须使用相同的序列化算法才能正确解析数据,比如 json、jdk等算法
指令类型:区分业务类型,比如登录、创建用户、注册等
请求序号:在一个客户端中只有一个channel,这个channel是多个业务在使用的,在客户端发起请求并获得服务端返回的数据后并不清楚这个数据对应的是哪个业务,所以需要使用一个序列号,发起请求时记录序列号和业务处理器的关系,返回后通过序列号获得处理器来处理数据,参考下面代码中的 RpcClientManager
正文长度:记录每个报文的长度
消息正文
netty_demo:
下面链接是nio和netty的demo,ChatServer和ChatClient 是聊天室服务端和客户端的入口,RpcServer和RpcClient是用netty简单的实现了一个RPC框架
netty_demo 链接:https://pan.baidu.com/s/1DJe5qVF_ahsp3pVNMQofSA?pwd=gwjg
netty的学习视频推荐:
黑马程序员Netty全套教程, netty深入浅出Java网络编程教程_哔哩哔哩_bilibili
课程对应的讲义:链接:https://pan.baidu.com/s/1DE2nx7-Iwcyv08tK8X6HAw?pwd=d92j