1.Netty的简介
Netty是一个高性能,异步事件驱动的NIO框架,他提供了对TCP、UDP和文件传输的支持。使用更高效的 socket 底层,对epoll空轮询引起的cpu占用飙升在内部进行了处理,避免直接使用NIO陷阱,简化了NIO处理的方式。
采用多种decoder/encoder支持,对TCP粘包/分包进行自动化处理。可接受处理线程池,提高连接效率,对重连,心跳检测的简单支持。可配置IO线程数,TCP参数,TCP接受和发送缓冲区使用直接内存代替堆内存,通过内存池的方式循环利用ByteBuf。使用单线程串行化的方式,高效的Reactor线程模型,大量使用volitale,使用了CAS和院子类,线程安全类的使用,读写锁的使用。
2.Netty的核心组件
Channel:
Channel 是 NIO 基本的结构。它代表了一个用于连接到实体如硬件设备、文件、网络套接字 或程序组件,能够执行一个或多个不同的 I/O 操作(例如读或写)的开放连接。 现在,把 Channel 想象成一个可以“打开”或“关闭”,“连接”或“断开”和作为传入和传出数据的运输 工具。
boorstrap:
Netty应用程序通过设置bootstrap(引导)类的开始,该类提供一个用于应用程序网络层配置的容器
Future
Future 提供了另外一种通知应用操作已经完成的方式。这个对象作为一个异步操作结果的占 位符,它将在将来的某个时候完成并提供结果。
JDK 附带接口 java.util.concurrent.Future ,但所提供的实现只允许您手动检查操作是否完成或 阻塞了。这是很麻烦的,所以 Netty 提供自己了的实现,ChannelFuture,用于在执行异步操作 时使用。
Channel channel = ...;
//不会阻塞
ChannelFuture future = channel.connect( new InetSocketAddress("192.168.0.1", 25));
Callback (回调)
callback (回调)是一个简单的方法,提供给另一种方法作为引用,这样后者就可以在某个合适的 时间调用前者。这种技术被广泛使用在各种编程的情况下,最常见的方法之一通知给其他人操 作已完成。
Netty 内部使用回调处理事件时。一旦这样的回调被触发,事件可以由接口 ChannelHandler 的实现来处理。如下面的代码,一旦一个新的连接建立了,调用 channelActive(),并将打印一条 消息。
public class ConnectHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//1 System.out.println( "Client " + ctx.channel().remoteAddress() + " connected");
}
}
Event 和 Handler
Netty 使用不同的事件来通知我们更改的状态或操作的状态。这使我们能够根据发生的事件触发适当的行为。这些行为可能括: 日志数据转换 流控制 应用程序逻辑 。
由于 Netty 是一个网络框架,事件很清晰的跟入站或出站数据流相关。因为一些事件可能触发 传入的数据或状态的变化包括: 活动或非活动连接 / 数据的读取 / 用户事件 / 错误
出站事件是由于在未来操作将触发一个动作。
这些包括: 打开或关闭一个连接到远程 写或冲刷数据到 socket
每个事件都可以分配给用户实现处理程序类的方法。这说明了事件驱动的范例可直接转换为 应用程序构建块。
下图显示了一个事件可以由一连串的事件处理器来处理
Netty 的 ChannelHandler 是各种处理程序的基本抽象。想象下,每个处理器实例就是一个回 调,用于执行对各种事件的响应。 在此基础之上,Netty 也提供了一组丰富的预定义的处理程序,您可以开箱即用。比如,各种协 议的编解码器包括 HTTP 和 SSL/TLS。在内部,ChannelHandler 使用事件和 future 本身,创建 具有 Netty 特性抽象的消费者。
Future, CallBack 和 Handler
Netty 的异步编程模型是建立在 future 和 callback 的概念上的。所有这些元素的协同为自己的 设计提供了强大的力量。 拦截操作和转换入站或出站数据只需要您提供回调或利用 future 操作返回的。这使得链操作 简单、高效,促进编写可重用的、通用的代码。一个 Netty 的设计的主要目标是促进“关注点分 离”:你的业务逻辑从网络基础设施应用程序中分离。
Selector, Event 和 EventLoop。
Netty 通过触发事件从应用程序中抽象出 Selector,从而避免手写调度代码。EventLoop 分配 给每个 Channel 来处理所有的事件,包括 注册感兴趣的事件 调度事件到 ChannelHandler 安排进一步行动 该 EventLoop 本身是由只有一个线程驱动,它给一个 Channel 处理所有的 I/O 事件,并且在 EventLoop 的生命周期内不会改变。这个简单而强大的线程模型消除你可能对你的 ChannelHandler 同步的任何关注,这样你就可以专注于提供正确的回调逻辑来执行。该 API 是简单和紧凑。
3.Netty 客户端/服务器 总览
图中显示了连接到服务器的多个并发的客户端。在理论上,客户端可以支持的连接数只受限 于使用的 JDK 版本中的制约。
4.实现一个简单地Netty连接
服务端:
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
/**
* 创建两个事件循环组EventLoopGroup
* 第一个EventLoopGroup的作用接受客户端连接(ACCEPT),将接收到的通道(channel)分配给工作线程处理
* 第二个EventLoopGroup的作用是主要来接受主EventLoopGroup提交的任务
* EventLoopGroup实质上是一个线程池,线程池大小默认为
* EventLoopGroup 事件循环实质是一个for死循环处理:for(;;)
*/
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup();
try{
//ServerBootStrap服务端启动辅助类,设置启动相关的配置信息
ServerBootstrap bootstrap = new ServerBootstrap()
//将主线程和工作线程传入对应线程组
.group(boss, worker)
//设置主线程处理的通道实例类
.channel(NioServerSocketChannel.class)
//处理事件单元
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ServerHandler());
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
}
});
//同步启动服务端
ChannelFuture sync = bootstrap.bind(9999).sync();
//阻塞通道关不
sync.channel().closeFuture().sync();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
客户端:
public class NettyClient {
public static void main(String[] args) {
NioEventLoopGroup loopGroup = new NioEventLoopGroup(1);
try {
//启动辅助类
Bootstrap bootstrap = new Bootstrap()
.group(loopGroup)
.channel(NioSocketChannel.class)
.remoteAddress("127.0.0.1",9999)
.handler(new ChannelInitializer <NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ClientHandler());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
}
});
//同步阻塞等待连接成功
ChannelFuture sync = bootstrap.connect().sync();
//发送消息
String msg = "hello\n";
sync.channel().writeAndFlush(msg);
//同步阻塞关闭客户端连接
sync.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
loopGroup.shutdownGracefully();
}
}
}
ServerHandler:
public class ServerHandler extends SimpleChannelInboundHandler<String> {
//接收用户发送消息
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
Channel channel = ctx.channel();
SocketAddress remoteAddress =
channel.remoteAddress();
System.out.println("接收消息0:"+remoteAddress+":"+msg);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = ctx.channel();
SocketAddress remoteAddress =
channel.remoteAddress();
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String s = new String(bytes);
channel.writeAndFlush("ACK:"+s);
System.out.println("接收消息1:"+remoteAddress+":"+s);
}
//用户连接上线
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
SocketAddress remoteAddress =
channel.remoteAddress();
System.out.println("客户端:"+remoteAddress+" 上线了");
}
//用户连接下线
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
SocketAddress remoteAddress =
channel.remoteAddress();
System.out.println("客户端:"+remoteAddress+" 下线了");
}
}
ClientHandler:
public class ClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = ctx.channel();
SocketAddress remoteAddress =
channel.remoteAddress();
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String s = new String(bytes);
System.out.println(s);
}
}
运行结果:
5.为什么要使用Netty?
1.API使用简单,更容易上手,开发门槛低
2.功能强大,预置了多种编码功能,支持多种主流协议
3.定制能力高,可通过ChannelHandler对通信框架进行灵活的扩展
4.高性能,与多种NIO主流框架相比,Netty综合性能更好
5.高稳定性,解决了NIO的Bug
6.经历了大过的商业应用应用考验,质量的可靠性都有很好的验证
6.Netty能提供什么服务?
1.开发异步非阻塞的TCP网络应用程序
2.开发异步非阻塞的UDP网络应用程序
3.开发异步文件传输程序
4.开发HTTP异步程序的服务端和客户端
5.提供多种编码的集成框架,包括谷歌Protobuf、JBossMarshalling、Java序列化、压缩编解码、XML解码、字符串编解码等
6.提供多种形式的编解码基础类库,可以方便的进行私有协议栈编解码框架的二次开发
7.基于职责链的Pipeline-Handler机制,可以方便对网络事件进行拦截
8.所有IO操作都是异步,用户可以通过Future-Listeren机制主动get结果或IO线程完成操作之后主动Notify来通知,用户业务线程不用同步等待
9.基于链路空闲事件检测的心跳机制
10.流量控制和整形
————参考书籍《Netty实战》