消息通知系统详解1—通讯方式消息通知系统详解2—后端设计消息通知系统详解3—Netty消息通知系统详解4—整合Netty和WebSocket
目录
- Netty
- 为什么使用Netty
- Netty的事件驱动
- 核心组件
这小节我们讲解下Netty
Netty
为什么使用Netty
我们已经有了NIO能够提高程序效率了,为什么还要使用Netty?
简单的说:Netty封装了JDK的NIO,让你用得更爽,你不用再写一大堆复杂的代码了。
官方术语:Netty是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。
下面是使用Netty不使用JDK原生NIO的一些原因:
使用JDK自带的NIO需要了解太多的概念,编程复杂
- Netty底层IO模型随意切换,而这一切只需要做微小的改动,就可以直接从NIO模型变身为IO模型
- Netty自带的拆包解包,异常检测等机制,可以从NIO的繁重细节中脱离出来,只需要关心业务逻辑
- Netty解决了JDK的很多包括空轮询在内的bug
- Netty底层对线程,selector做了很多细小的优化,精心设计的线程模型做到非常高效的并发处理
- 自带各种协议栈让你处理任何一种通用协议都几乎不用亲自动手
- Netty社区活跃,遇到问题随时邮件列表或者issue
- Netty已经历各大rpc框架,消息中间件,分布式通信中间件线上的广泛验证,健壮性无比强大
和IO编程一样的案例:
添加Netty依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.5.Final</version>
</dependency>
服务端:
public class NettyServer {
public static void main(String[] args) {
ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup boos = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
serverBootstrap
.group(boos, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println(msg);
}
});
}
})
.bind(8000);
}
}
客户端:
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(new StringEncoder());
}
});
Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
while (true) {
channel.writeAndFlush("测试数据");
Thread.sleep(2000);
}
}
}
Netty的事件驱动
例如很多系统都会提供 onClick() 事件,这个事件就代表鼠标按下事件。事件驱动模型的大体思路如下:
- 有一个事件队列;
- 鼠标按下时,往事件队列中增加一个点击事件;
- 有个事件泵,不断循环从队列取出事件,根据不同的事件,调用不同的函数;
- 事件一般都各自保存各自的处理方法的引用。这样,每个事件都能找到对应的处理方法;
为什么使用事件驱动? - 程序中的任务可以并行执行
- 任务之间高度独立,彼此之间不需要互相等待
- 在等待的事件到来之前,任务不会阻塞
Netty使用事件驱动的方式作为底层架构,包括:
- 事件队列(event queue):接收事件的入口。
- 分发器(event mediator):将不同的事件分发到不同的业务逻辑单元。
- 事件通道(event channel):分发器与处理器之间的联系渠道。
- 事件处理器(event processor):实现业务逻辑,处理完成后会发出事件,触发下一步操作。
核心组件
Netty 的功能特性图:
Netty 功能特性:
- 传输服务,支持 BIO 和 NIO。
- 容器集成:支持 OSGI、JBossMC、Spring、Guice 容器。
- 协议支持:HTTP、Protobuf、二进制、文本、WebSocket 等,支持自定义协议。
BIO和NIO的区别:
场景 | BIO | NIO |
有新连接请求时 | 开一个新的线程处理 | 使用多路复用原理,一个线程处理 |
适用场景 | 连接数小且固定 | 连接数特别多,连接比较短(轻操作)的场景 |
Netty框架包含如下的组件:
- ServerBootstrap :用于接受客户端的连接以及为已接受的连接创建子通道,一般用于服务端。
- Bootstrap:不接受新的连接,并且是在父通道类完成一些操作,一般用于客户端的。
- Channel:对网络套接字的I/O操作,例如读、写、连接、绑定等操作进行适配和封装的组件。
- EventLoop:处理所有注册其上的channel的I/O操作。通常情况一个
- EventLoop可为多个channel提供服务。
- EventLoopGroup:包含有多个EventLoop的实例,用来管理 event Loop
的组件,可以理解为一个线程池,内部维护了一组线程。 - ChannelHandler和ChannelPipeline:例如一个流水线车间,当组件从流水线头部进入,穿越流水线,流水线上的工人按顺序对组件进行加工,到达流水线尾部时商品组装完成。流水线相当于ChannelPipeline,流水线工人相当于ChannelHandler,源头的组件当做event。
- ChannelInitializer:用于对刚创建的channel进行初始化,将ChannelHandler添加到channel的ChannelPipeline处理链路中。
- ChannelFuture:与jdk中线程的Future接口类似,即实现并行处理的效果。可以在操作执行成功或失败时自动触发监听器中的事件处理方法。
上面的Netty框架包含如下的组件大概看的有点蒙,我们对之前编写的代码加上注释:
服务端:
public class NettyServer {
public static void main(String[] args) {
// 用于接受客户端的连接以及为已接受的连接创建子通道,一般用于服务端。
ServerBootstrap serverBootstrap = new ServerBootstrap();
// EventLoopGroup包含有多个EventLoop的实例,用来管理event Loop的组件
// 接受新连接线程
NioEventLoopGroup boos = new NioEventLoopGroup();
// 读取数据的线程
NioEventLoopGroup worker = new NioEventLoopGroup();
//服务端执行
serverBootstrap
.group(boos, worker)
// Channel对网络套接字的I/O操作,
// 例如读、写、连接、绑定等操作进行适配和封装的组件。
.channel(NioServerSocketChannel.class)
// ChannelInitializer用于对刚创建的channel进行初始化
// 将ChannelHandler添加到channel的ChannelPipeline处理链路中。
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
// 组件从流水线头部进入,流水线上的工人按顺序对组件进行加工
// 流水线相当于ChannelPipeline
// 流水线工人相当于ChannelHandler
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
//这个工人有点麻烦,需要我们告诉他干啥事
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println(msg);
}
});
}
})
.bind(8000);
}
}
客户端:
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
// 不接受新的连接,并且是在父通道类完成一些操作,一般用于客户端的。
Bootstrap bootstrap = new Bootstrap();
// EventLoopGroup包含有多个EventLoop的实例,用来管理event Loop的组件
NioEventLoopGroup group = new NioEventLoopGroup();
//客户端执行
bootstrap.group(group)
// Channel对网络套接字的I/O操作,
// 例如读、写、连接、绑定等操作进行适配和封装的组件。
.channel(NioSocketChannel.class)
// 用于对刚创建的channel进行初始化,
// 将ChannelHandler添加到channel的ChannelPipeline处理链路中。
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
// 组件从流水线头部进入,流水线上的工人按顺序对组件进行加工
// 流水线相当于ChannelPipeline
// 流水线工人相当于ChannelHandler
ch.pipeline().addLast(new StringEncoder());
}
});
//客户端连接服务端
Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
while (true) {
// 客户端使用writeAndFlush方法向服务端发送数据,返回的是ChannelFuture
// 与jdk中线程的Future接口类似,即实现并行处理的效果
// 可以在操作执行成功或失败时自动触发监听器中的事件处理方法。
ChannelFuture future = channel.writeAndFlush("测试数据");
Thread.sleep(2000);
}
}
}