Netty 应用程序的一个一般准则:尽可能的重用 EventLoop,以减少线程创建所带来的开销。
Netty 系列一(核心组件和实例). - JMCui - 博客园 https://www.cnblogs.com/jmcui/p/9154842.html
一、概念
早期的 Java API 只支持由本地系统套接字库提供所谓的阻塞函数来支持网络编程。由于是阻塞 I/O ,要管理多个并发客户端,需要为每个新的客户端Socket 创建一个 Thread 。这将导致一系列的问题,第一,在任何时候都可能有大量的线程处于休眠状态(不可能每时每刻都有对应的并发数);第二,需要为每个线程的调用栈都分配内存;第三,JVM 在线程的上下文切换所带来的开销会带来麻烦。
Java 在 2002 年引入了非阻塞 I/O,位于 JDK 1.4 的 java.nio 包中。class java.nio.channels.Selector 是Java 的非阻塞 I/O 实现的关键。它使用了事件通知以确定在一组非阻塞套接字中有哪些已经就绪能够进行 I/O 相关的操作。因为可以在任何的时间检查任意的读操作或者写操作的完成状态,所以如图 1-2 所示,一个单一的线程便可以处理多个并发的连接。
尽管可以直接使用 Java NIO API,但是在高负载下可靠和高效地处理和调度 I/O 操作是一项繁琐而且容易出错的任务,最好还是留给高性能的网络编程专家——Netty。
Netty 是一款异步的事件驱动的网络应用程序框架,支持快速的开发可维护的高性能的瞄向协议的服务端和客户端。它驾驭了Java高级API的能力,并将其隐藏在一个易于使用的API之后。首先,它的基于 Java NIO 的异步的和事件驱动的实现,保证了高负载下应用程序性能的最大化和可伸缩性。其次, Netty 也包含了一组设计模式,将应用程序逻辑从网络层解耦,简化了开发过程, 同时也最大限度地提高了可测试性、模块化以及代码的可重用性。
tips:面向对象的基本概念—> 用较简单的抽象隐藏底层实现的复杂性。
二、核心组件
- Channel
Channel是Java NIO的一个基本构造。可以看作是传入或传出数据的载体。因此,它可以被打开或关闭,连接或者断开连接。以下是常用的Channel:
-- EmbeddedChannel
-- LocalServerChannel
-- NioDatagramChannel
-- NioSctpChannel
-- NioSocketChannel
- 回调
当一个回调被触发时,相应的事件可以被一个interface-ChannelHandler的实现处理。
- Future
Netty中所有的I/O操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种在之后的某个时间点确定其结果的方法。
Future 和 回调 是相互补充的机制,提供了另一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问。
Netty 提供了ChannelFuture,用于在执行异步操作的时候使用。每个Netty的出站I/O操作都会返回一个ChannelFuture。ChannelFuture能够注册一个或者多个ChannelFutureListener 实例。监听器的回调方法operationComplete(),将会在对应的操作完成时被调用。
- ChannelHandler
Netty 的主要组件是ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。
Netty 使用不同的事件来通知我们状态的改变或者是操作的状态,每个事件都可以被分发给ChannelHandler类中某个用户实现的方法。Netty提供了大量预定义的可以开箱即用的ChannelHandler实现,包括用于各种协议的ChannelHandler。
现在,事件可以被分发给ChannelHandler类中某个用户实现的方法。那么,如果 ChannelHandler 处理完成后不直接返回给客户端,而是传递给下一个ChannelHandler 继续处理呢?那么就要说到 ChannelPipeline !
ChannelPipeline 提供了 ChannelHandler链 的容器,并定义了用于在该链上传播入站和出站事件流的API。使得事件流经 ChannelPipeline 是 ChannelHandler 的工作,它们是在应用程序的初始化或者引导阶段被安装的。这些对象接收事件、执行他们所实现的处理逻辑,并将数据传递给链中的下一个ChannelHandler:
1、一个ChannelInitializer的实现被注册到了ServerBootstrap中。
2、当 ChannelInitializer.initChannel()方法被调用时, ChannelInitializer将在 ChannelPipeline 中安装一组自定义的 ChannelHandler。
3、ChannelInitializer 将它自己从 ChannelPipeline 中移除。
- EventLoop
EventLoop 定义了Netty的核心抽象,用来处理连接的生命周期中所发生的事件,在内部,将会为每个Channel分配一个EventLoop。
EventLoop本身只由一个线程驱动,其处理了一个Channel的所有I/O事件,并且在该EventLoop的整个生命周期内都不会改变。这个简单而强大的设计消除了你可能有的在ChannelHandler实现中需要进行同步的任何顾虑。
这里需要说到,EventLoop的管理是通过EventLoopGroup来实现的。还要一点要注意的是,客户端引导类是 Bootstrap,只需要一个EventLoopGroup。服务端引导类是 ServerBootstrap,通常需要两个 EventLoopGroup,一个用来接收客户端连接,一个用来处理 I/O 事件(也可以只使用一个 EventLoopGroup,此时其将在两个场景下共用同一个 EventLoopGroup)。
1、一个 EventLoopGroup 包含一个或者多个 EventLoop;
2、一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
3、所有由 EventLoop 处理的 I/O 事件都将在它专有的Thread 上被处理;
4、一个 Channel 在它的生命周期内只注册于一个EventLoop;
5、NIO中,一个 EventLoop 分配给多个 Channel(面对多个Channel,一个 EventLoop 按照事件触发,顺序执行); OIO中,一个 EventLoop 分配给一个 Channel。
tips:Netty 应用程序的一个一般准则:尽可能的重用 EventLoop,以减少线程创建所带来的开销。
- Bootstrap 和 ServerBootstrap
BootStarp 和 ServerBootstrap 被称为引导类,指对应用程序进行配置,并使他运行起来的过程。Netty处理引导的方式是使你的应用程序和网络层相隔离。
BootStrap 是客户端的引导类,Bootstrap 在调用 bind()(连接UDP)和 connect()(连接TCP)方法时,会新创建一个 Channel,仅创建一个单独的、没有父 Channel 的 Channel 来实现所有的网络交换。
ServerBootstrap 是服务端的引导类,ServerBootstarp 在调用 bind() 方法时会创建一个 ServerChannel 来接受来自客户端的连接,并且该 ServerChannel 管理了多个子 Channel 用于同客户端之间的通信。
三、实例
所有的Netty服务端/客户端都至少需要两个部分:
1、至少一个ChannelHandler —— 该组件实现了对数据的处理。
2、引导 —— 这是配置服务器的启动代码。
服务端:
public class EchoServer { private final int port; public EchoServer(int port) {
this.port = port;
} public void start() throws InterruptedException {
final EchoServerHandler serverHandler = new EchoServerHandler();
//1、创建EventLoopGroup以进行事件的处理,如接受新连接以及读/写数据
EventLoopGroup group = new NioEventLoopGroup();
try {
//2、创建ServerBootstrap,引导和绑定服务器
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group, group)
//3、指定所使用的NIO传输Channel
.channel(NioServerSocketChannel.class)
//4、使用指定的端口设置套接字地址
.localAddress(new InetSocketAddress(port))
//5、添加一个 EchoServerHandler 到子 Channel的 ChannelPipeline
//当一个新的连接被接受时,一个新的子Channel将会被创建,而 ChannelInitializer 将会把一个你的EchoServerHandler 的实例添加到该 Channel 的 ChannelPipeline 中
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(serverHandler);
}
});
//6、异步地绑定服务器,调用sync()方法阻塞等待直到绑定完成
ChannelFuture channelFuture = bootstrap.bind().sync();
System.out.println(EchoServer.class.getName() + "started and listening for connections on" + channelFuture.channel().localAddress());
//7、获取 Channel 的 CloseFuture,并且阻塞当前线程直到它完成
channelFuture.channel().closeFuture().sync(); } finally {
//8、关闭 EventLoopGroup 释放所有的资源
group.shutdownGracefully().sync();
}
} public static void main(String[] args) throws InterruptedException {
new EchoServer(9999).start();
}
}
@ChannelHandler.Sharable //标识一个Channel-Handler 可以被多个Channel安全的共享
public class EchoServerHandler extends ChannelHandlerAdapter { /**
* 对于每个传入的消息都要调用
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server received:" + in.toString(CharsetUtil.UTF_8));
//将接收到的消息写给发送者,而不冲刷出站消息
//ChannelHandlerContext 发送消息。导致消息向下一个ChannelHandler流动
//Channel 发送消息将会导致消息从 ChannelPipeline的尾端开始流动
ctx.write(in);
} /**
* 通知 ChannelHandlerAdapter 最后一次对channel-Read()的调用是当前批量读取中的最后一条消息
*
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//暂存于ChannelOutboundBuffer中的消息,在下一次调用flush()或者writeAndFlush()方法时将会尝试写出到套接字
//将这份暂存消息冲刷到远程节点,并且关闭该Channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
} /**
* 在读取操作期间,有异常抛出时会调用
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
} }
客户端:
public class EchoClient { private final String host;
private final int port; public EchoClient(String host, int port) {
this.host = host;
this.port = port;
} public void start() throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建Bootstrap
Bootstrap bootstrap = new Bootstrap();
//指定 EventLoopGroup 以处理客户端事件;适应于NIO的实现
bootstrap.group(group)
//适用于NIO传输的Channel类型
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
//在创建Channel时,向ChannelPipeline中添加一个EchoClientHandler实例
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoClientHandler());
}
});
//连接到远程节点,阻塞等待直到连接完成
ChannelFuture channelFuture = bootstrap.connect().sync();
//阻塞,直到Channel 关闭
channelFuture.channel().closeFuture().sync();
} finally {
//关闭线程池并且释放所有的资源
group.shutdownGracefully().sync();
}
} public static void main(String[] args) throws InterruptedException {
new EchoClient("127.0.0.1", 9999).start(); System.out.println("------------------------------------"); new EchoClient("127.0.0.1", 9999).start(); System.out.println("------------------------------------"); new EchoClient("127.0.0.1", 9999).start();
} }
@ChannelHandler.Sharable //标记该类的实例可以被多个Channel共享
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> { /**
* 当从服务器接收到一条消息时被调用
*
* @param ctx
* @param msg ByteBuf (Netty 的字节容器) 作为一个面向流的协议,TCP 保证了字节数组将会按照服务器发送它们的顺序接收
* @throws Exception
*/
@Override
protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("Client" + ctx.channel().remoteAddress() + "connected");
System.out.println(msg.toString(CharsetUtil.UTF_8));
} /**
* 在到服务器的连接已经建立之后将被调用
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rock!", CharsetUtil.UTF_8));
} /**
* 在处理过程中引发异常时被调用
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
} }
四、结语
带着一阵迷糊就开始了Netty学习之旅,学到现在还是对Netty一堆专有名词头大!没办法,只好硬着头皮学下去了,毕竟,熟读唐诗三百首,不会作诗也会吟嘛!
来总结下,一个Netty服务端处理客户端连接的过程:
1、创建一个channel同该用户端进行绑定;
2、channel从EventLoopGroup获得一个EventLoop,并注册到该EventLoop,channel生命周期内都和该EventLoop在一起(注册时获得selectionKey);
3、channel同用户端进行网络连接、关闭和读写,生成相对应的event(改变selectinKey信息),触发eventloop调度线程进行执行;
4、ChannelPipeline 找到对应 ChannelHandler 方法处理用户逻辑。
我们项目中使用的 Netty 服务端启动类:
public class NettyServer { public static final Logger logger = LoggerFactory.getLogger(NettyServer.class); private static Integer LISTENER_PORT = PropertiesLoader.getResourcesLoader().getInteger("nettyPort"); private int port;
EventLoopGroup boss = null;
EventLoopGroup worker = null;
ServerBootstrap serverBootstrap = null; public static NettyServer nettyServer = null; public static NettyServer getInstance() {
if (nettyServer == null) {
synchronized (NettyServer.class) {
if (nettyServer == null) {
nettyServer = new NettyServer(LISTENER_PORT==null?9999:LISTENER_PORT);
}
}
}
return nettyServer;
} /**
* 构造函数
*
* @param port 端口
*/
private NettyServer(int port) {
this.port = port; } /**
* 绑定
*
* @throws InterruptedException
*/
public void init() throws InterruptedException {
try { //创建两个线程池
//目前服务器CPU为单核8线程,调整线程为8
boss = new NioEventLoopGroup(8);
worker = new NioEventLoopGroup(8); serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker);//两个工作线程
serverBootstrap.channel(NioServerSocketChannel.class);
//重用缓冲区
serverBootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
serverBootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
//自动调整下一次缓冲区建立时分配的空间大小,避免内存的浪费
serverBootstrap.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT);
//当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度,默认值50。
serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
//用于启用或关于Nagle算法。如果要求高实时性,有数据发送时就马上发送,就将该选项设置为true关闭Nagle算法;如果要减少发送次数减少网络交互,就设置为false等累积一定大小后再发送。默认为false。
serverBootstrap.option(ChannelOption.TCP_NODELAY, true);
//是否启用心跳保活机制
serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
//支持tcp协议
//bootstrap.childHandler(new TcpChannelInitializer()); //支持webSocket协议
serverBootstrap.childHandler(new WebSocketChannelInitializer());
ChannelFuture f = serverBootstrap.bind(port).sync();
if (f.isSuccess()) {
logger.info("netty server start...");
}
//等到服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
//优雅释放线程资源
boss.shutdownGracefully().sync();
worker.shutdownGracefully().sync();
}
} /**
* 销毁netty相关资源
*/
public void destroy() {
try {
if (boss != null) {
boss.shutdownGracefully();
}
if (worker != null) {
worker.shutdownGracefully();
}
if (serverBootstrap != null) {
serverBootstrap = null;
}
} catch (Exception e) {
logger.error("netty close err:" + e.getMessage(), e);
}
}
}
tips: ServerBootstrap 中增加了一个方法childHandler(),它的目的是添加 ChannelHandler ;Bootstrap 中添加 ChannelHandler 用 handler() 方法。
参考资料:《Netty IN ACTION》
演示源代码:https://github.com/JMCuixy/NettyDemo
Netty 应用程序的一个一般准则:尽可能的重用 EventLoop,以减少线程创建所带来的开销。的更多相关文章
- golang 创建一个简单的资源池,重用资源,减少GC负担
package main; import ( "sync" "errors" "fmt" ) //代码参考<Go语言实战>中第7 ...
- Netty入门二:开发第一个Netty应用程序
Netty入门二:开发第一个Netty应用程序 时间 2014-05-07 18:25:43 CSDN博客 原文 http://blog.csdn.net/suifeng3051/article/ ...
- 【netty】(2)---搭建一个简单服务器
netty(2)---搭建一个简单服务器 说明:本篇博客是基于学习慕课网有关视频教学.效果:当用户访问:localhost:8088 后 服务器返回 "hello netty"; ...
- Netty入门程序(四)
maven创建project,引入依赖: <dependency> <groupId>io.netty</groupId> <artifactId>ne ...
- 基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇
前提 前置文章: Github Page:<基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> Coding Page:<基于Netty和SpringBoot实现 ...
- Java基础-接口中国特色社会主义的体制中有这样的现象:地方省政府要坚持党的领导和按 照国务院的指示进行安全生产。请编写一个java应用程序描述上述的体制现象。 要求如下: (1)该应用程序中有一个“党中央”接口:CentralPartyCommittee,该接口中 有个“坚持党的领导”方法:void partyLeader() (2)该应用程序中有一个“国务院”抽象类:StateCouncil,
36.中国特色社会主义的体制中有这样的现象:地方省政府要坚持党的领导和按 照国务院的指示进行安全生产.请编写一个java应用程序描述上述的体制现象. 要求如下: (1)该应用程序中有一个“党中央”接口 ...
- 编写程序输入一个5x5的矩阵,将最大元素与中心元素交换,并按行列对齐输出。
编写程序输入一个5x5的矩阵,将最大元素与中心元素交换,并按行列对齐输出. 题目描述 编写程序输入一个5x5的矩阵,将最大元素与中心元素交换,并按行列对齐输出. 输入描述 编写程序输入一个5x5的矩阵 ...
- 假设程序需要一个int类型的变量来保持你所有的音乐CD的数量
假设程序需要一个int类型的变量来保持你所有的音乐CD的数量.初始值为0为该变量编写一条声明语句 int numCDs = 0;
- 微信小程序发布一个月,世界并没有什么不同
从某种意义上说,在张小龙身上,最可怕的事情莫过于微信小程序发布一个月,一开始的大红大紫居然渐归沉寂,曾经的风光无限已无人谈起,世界并没有什么不同. 这真像一场噩梦,一切都可怕地颠倒了.一款微信的战略级 ...
随机推荐
- linux中使用lftp上传下载文件
lftp是linux中一款ftp服务器相比windows中的ftp显得要复杂不少了,下面我来总结一下lftp文件上传,文件下载,及文件查找等等相关命令吧. lftp连接的几种方法,最常用的是lftp ...
- js中获取event keycode的兼容办法
window.onkeypress=function(e){ var event = e || window.event, //在ff下event会做为参数传进来,ie下会在window下 keyCo ...
- Groovy学习()Groovy是啥?
Groovy是啥? groovy是英文中的一个单词,有marvelous.wonderful和excellen的意思. groovy是轻量级的,动态的,面向对象的,并且运行在JVM上的. groovy ...
- ssh2——Interceptor拦截器
尽管没学过struts1吧.可是了解到struts1中并没有拦截器, 到Struts2才有.它是基于WebWork发展起来的, 顾名思义,说到拦截器大家首先肯定会想到它是拦截东西的,起到一个限制的作 ...
- ASP.NET MVC 使用 Datatables (1)
具体步骤: 1.建立实体类 public class Asset { public System.Guid AssetID { get; set; } [Display(Name = "Ba ...
- 京东阅读PDF导出
适用平台:windows 需要软件:FastStone Capture(截图软件),TinyTask(操作录制软件) 1.打开京东阅读 2.设置截图软件 (1)设置截图区域(FastStone Cap ...
- Unity5 AssetBundle打包加载及服务器加载
Assetbundle为资源包不是资源 打包1:通过脚本指定打包 AssetBundleBuild ab = new AssetBundleBuild ...
- OAuth认证协议原理分析及同步消息到Twitter和Facebook使用方法
OAuth有什么用?为什么要使用OAuth? twitter或豆瓣用户一定会发现,有时候,在别的网站,点登录后转到 twitter登录,之后转回原网站,你会发现你已经登录此网站了,这种网站就是这个效果 ...
- JAVA 并发编程-多个线程之间共享数据(六)
多线程共享数据的方式: 1.假设每一个线程运行的代码同样.能够使用同一个Runnable对象,这个Runnable对象中有那个共享数据,比如,卖票系统就能够这么做. 2,假设每一个线程运行的代码不同. ...
- KVC/KVO之暴力的KVC
本章将分为三个部分: KVC是什么 KVC之Set/Get KVC键值路径之Set/Get KVC是什么 KVC,即 NSKeyValueCoding,一个非正式的 Protocol,提供一种机制来间 ...