1.Netty基础
Netty是一个提供了易于使用的API的客户端、服务器框架;
并发高-NIO(非阻塞IO)
传输快-零拷贝:
分析:
使用了NIO的零拷贝;java中内存是分为堆和栈,还有字符串常量池等等;
如果有一些数据需要从IO中读取,并且放到堆里面;中间会经历一些缓冲区;也就是说,读的过程会分为两个步骤:从IO流中读取出来,放到缓存区;缓冲区读取放到堆里面;
当它需要去接受数据或者是传输数据。会去开辟一个新的堆内存;然后数据直接从IO读取到新的堆内存中去
对于协议的支持如下;
BIO:同步阻塞IO
Server会有一个专门的线程acceptor;专门来监听客户端之间的请求;
客户端增多的时候,就会频繁的创建和销毁相应线程,就要考虑线程池;
NIO:同步非阻塞IO
BIO、NIO与AIO的区别与理解:
三者的区别详细可参考如下链接:
https://blog.csdn.net/TOMORROW6COME/article/details/140048654
使用NIO所遇到的问题:
类库;api相对较为复杂;需要具备java多线程的技能;
断线重连,网络阻塞等问题的处理方式,实现难度较大;NIO内置使用存在一些bug;
Netty优点:高效;简单;支持主流协议;可定制性强;性能高;
Dubbo使用的是Netty;
2.Netty三种线程模型
Reactor线程模型;
只能用于小型的应用场景;
不适用于高负载高并发场景;因为一个NIO线程要去同时处理成百上千的请求;
性能支撑不了;即便给予服务器负载;对于海量的消息处理,编解码,处理跟发送消息
无法满足;超时,然后不断轮训;会造成恶性循环;
主从线程模型:一组线程池接受请求;一组线程池处理IO
3.Netty快速入门
(1)构建Netty的聊天服务器
使用主从线程池构建聊天服务器:
在启动的时候会返回一个实例;为ChannelFuture;
他的解释如下:大概意思就是因为NIO;所以会立即返回一个结果;同时,可能没有信息;
所以使用ChannelFuture,替代作为一个结果返回;
public static void main(String[] args) throws Exception {
// 构建主从线程池
// 定义主线程池,用于接受客户端的连接;但是不做任何处理,比如老板会谈业务,拿到业务就会交给workerGroup进行处理
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 定义从线程池,处理主线程池交代的任务;公司业务员开展业务,完成老板交代的任务
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 构建Netty服务器
ServerBootstrap server = new ServerBootstrap(); // 服务启动类;
server.group(bossGroup,workerGroup) // 把主从线程池组放入到启动类中
.channel(NioServerSocketChannel.class) // 设置NIO双向通道
.childHandler(null); // 设置处理器,用于处理workerGroup业务
// 启动server,并且绑定端口号875,同时启动方式为同步
ChannelFuture channelFuture = server.bind(875).sync(); //.sync是保证服务启动后一直运行;不写sync,代码一运行完就没有了
//监听关闭的channel
channelFuture.channel().closeFuture().sync();
} finally {
// 优雅关闭线程池组
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
(2)设定channel的初始化器
Pipeline需要开发者自己去编写;handler助手类:用于处理请求;
/**
* Created by nly
*
* 初始化器,channel注册后,会执行里面的响应的初始化方法
**/
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
// 通过SocketChannel 获得对应的管道
ChannelPipeline pipeline = channel.pipeline();
/**
* 通过管道,添加handler处理器
*/
// HttpServerCodec 是netty 自己提供的助手类,此处可以理解为管道中的拦截器
// 当请求到服务端,我们需要进行做解码,相应到客户端做解码
pipeline.addLast("",new HttpServerCodec());
// 添加自定哦助手类,当请求访问,返回"hello,netty"
pipeline.addLast("",null);
}
}
(3)编写HTTP自定义助手类
先处理http;先不构建Netty服务器;
HttpObject是处理的泛型;
先构建基本的逻辑与框架;
通过上下文对象ctx去获取channel;拿到channel之后,就能获取到跟客户端
相关的信息了
概念缓冲区:bytebuf:定义数据发送的消息;读写数据;
/**
* 创建自定义助手类
*
**/
public class HttpHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
protected void channelRead0(ChannelHandlerContext ctx,
HttpObject msg) throws Exception {
// 获取channel
Channel channel = ctx.channel();
// 打印客户端的远程地址
System.out.println(channel.remoteAddress());
// 通过缓冲区定义发送的消息,读写数据都是通过缓冲区进行数据交换的
ByteBuf content = Unpooled.copiedBuffer("hello netty!", CharsetUtil.UTF_8);
// 构建http的response
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
content);
// 为响应添加数据类型和数据长度
response.headers().set(HttpHeaderNames.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
// 把响应数据写到缓冲区再刷到客户端
ctx.writeAndFlush(response);
}
}
(3)Netty服务的生命周期
关注到netty的生命周期问题;
ChannelInboundHandlerAdapter