本篇的学习须要懂 NIO 的常识,不懂的能够参看上面的文章:
- NIO 学习笔记 (一) 初遇
- NIO 学习笔记 (二) 相识篇
- Socket 简介和 I / O 多路复用
- NIO 学习笔记(三) 甚欢篇
除此之外,还须要大略懂一点网络协议,比方 HTTP。
开始之前
这里咱们在温习一下简略的温习 NIO 的三个重要的外围知识点,Selector(选择器)、Channel(通道)、Buffer(缓冲区)。这三个概念是从 I / O 多路复用形象而来, 网络通信的开始是建设连贯(对于 TCP 协定来说), 在建设连贯后, 单方开始相互发送数据。那么如果是在很多客户端要和服务端进行通信,那么就会有很多连贯,然而就算是很多客户端发动申请想要和服务端建设连贯,那么发动的申请也是有先后顺序的,更为精确的说,和服务端建设连贯也是有先后顺序的,多路复用的思路在 Java 的实现就是选择器治理通道,连贯建设实现,也就是认为能够通信了,然而数据可能还没筹备好,选择器能够在数据达到实现的时候才解决数据。
咱们能够认为 Channel 是对连贯的形象,TCP 是面向连贯的,那么连贯建设之后,咱们能够认为就通信的单方就建设了一条信道,就能够用来传输数据了,数据咱们寄存在缓冲区中。选择器负责管理通道,当通道中有选择器感兴趣的事件 (能够读或者能够写) 之后,选择器就能够选中这个通道,由程序做对应的数据处理,或者读或者写。
缘起
Nowadays we use general purpose applications or libraries to communicate with each other. For example, we often use an HTTP client library to retrieve information from a web server and to invoke a remote procedure call via web services. However, a general purpose protocol or its implementation sometimes does not scale very well. It is like how we don’t use a general purpose HTTP server to exchange huge files, e-mail messages, and near-realtime messages such as financial information and multiplayer game data. What’s required is a highly optimized protocol implementation that is dedicated to a special purpose. For example, you might want to implement an HTTP server that is optimized for AJAX-based chat application, media streaming, or large file transfer. You could even want to design and implement a whole new protocol that is precisely tailored to your need. Another inevitable case is when you have to deal with a legacy proprietary protocol to ensure the interoperability with an old system. What matters in this case is how quickly we can implement that protocol while not sacrificing the stability and performance of the resulting application
现在咱们用不同的利用或者库来进行交换,例如,咱们常常应用 HTTP Client 的库从 WEB 服务器检索信息,通过 RPC 去调用 WEB 服务。然而,通用协定的扩展性是不高的,就像咱们不会应用通用的 HTTP 协定去实现替换绝大的文件,Email 信息,近乎实时的音讯,比方财务信息和多人游戏的数据。咱们须要的是一个高性能的协定实现,专门用于一个非凡的目标。比方,你可能想要实现一个 HTTP 服务器用于 Ajax 的聊天应用程序、媒体流、大型文件传输。甚至想本人设计实现一个协定。即便你实现了新协定,你也得保障与旧零碎的兼容性。在这种状况下,重要的是咱们可能以多快的速度实现该协定,同时又不会对最终的应用程序的稳定性和性能产生影响。
总结
Netty 为低延时、自定义协定打造,通用的协定扩展性不高,实时性不高。有的时候咱们想独自定制一个网络协议,又想和零碎中应用的旧有协定放弃兼容。那么就须要用到 NIO 了,然而 JDK 原生的 NIO 存在问题,写起来繁琐。咱们心愿简略一些,这就是 Netty 的缘起。
简介
Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.
‘Quick and easy’ doesn’t mean that a resulting application will suffer from a maintainability or a performance issue. Netty has been designed carefully with the experiences earned from the implementation of a lot of protocols such as FTP, SMTP, HTTP, and various binary and text-based legacy protocols. As a result, Netty has succeeded to find a way to achieve ease of development, performance, stability, and flexibility without a compromise.
Netty 是一个用来疾速开发网络应用程序 (网络协议的客户端和服务端) 的 NIO 框架,Netty 可能让开发网络应用程序 (比方 TCP 和 UDP 的服务端) 更为简略和高效
疾速和简略并不象征这 Netty 会有性能和可靠性上的问题。Netty 是通过精心设计的,在实现许多的网络协议,比方 FTP、SMTP、HTTP、各种二进制和基于文本的协定,有着良好的实际。起因在于,Netty 曾经胜利地找到了一种办法来实现开发的易用性、性能、稳定性和灵活性。
Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.
Netty 是一个异步事件驱动的网络通信框架,用于疾速开发可保护的高性能协定服务器和客户端。
到这里咱们就能够大抵给 Netty 定性,Netty 首先是一个 NIO 框架,性能好,稳定性强,能够被用来做应用服务器(比方 Tomcat)。能够用来实现网络协议。
在 NIO 学习笔记 (二) 相识篇中咱们实现了一个聊天室,写起来十分复杂,在 JDK 的某些版本(有人说 JDK 在 1.8 曾经解决了这个问题,然而有人说还没解决,有趣味的能够去搜一搜 JDK NIO 空轮询),还存在一些 BUG。咱们都喜爱简略的货色,于是 Netty 应运而生,简化了 NIO 的编程,解决了 JDK 原生 NIO 编程的 BUG。
在哪些地方用到了
-
Dubbo
- 引入的依赖用到了 Netty(RPC(近程过程调用, 也能够了解为通信))
- RocketMQ – 经典的 Hadoop 的高性能通信和序列化组件 Avro 的 RPC 框架,默认采纳 Netty 进行跨节点通信,它的 Netty Service 基于 Netty 框架二次封装实现。
基本上在 Java 语言内,须要用到网络通信的,都有 Netty 的影子。
特点
- 稳固
长年都在 4.x 版本,尽管出了个 5.0 版本,然而还被作者废除掉了。
-
设计
- Unified API for various transport types – blocking and non-blocking socket
对立的 API,实用于不同的协定(阻塞和非阻塞)
- Based on a flexible and extensible event model which allows clear separation of concerns
基于灵便的、可扩大的事件驱动模型。
- Highly customizable thread model – single thread, one or more thread pools such as SEDA
高度可定制的线程模型,一个或者多个线程池,比方 SEDA。
- True connectionless datagram socket support (since 3.1)
牢靠的无连贯数据的 Socket 反对
-
易用性(Ease of use)
-
- Well-documented Javadoc, user guide and examples
欠缺的 JavaDoc,用户指南和样例
- No additional dependencies, JDK 5 (Netty 3.x) or 6 (Netty 4.x) is enough.
不须要的额定的依赖,3.x 版本(只须要 JDK5),4.x 版本(只须要 JDK6 就足够了)
辅助测试工具 curl
curl 是罕用的命令行工具, 用来申请 Web 服务器。它的名字就是客户端(client)的 URL 工具的意思。
在 Netty 系列的学习笔记中, 咱们用 Netty 来实现一个简略的服务器, 咱们应用 curl 和浏览器来测试这个服务器。
如何装置参考 Windows 下装置应用 curl 命令
当然是从 Hello World 开始了啊
简略实例
咱们从一个例子来介绍 Netty 的应用,首先咱们还是一个 maven 工程,而后引入对应的依赖:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.56.Final</version>
</dependency>
public class NettyDemo01 {public static void main(String[] args) throws InterruptedException {
// 创立事件循环组 接管连贯
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 接管连贯 并分发给 worker 线程
EventLoopGroup workerGroup = new NioEventLoopGroup();
// 启动 Netty
ServerBootstrap serverBootstrap = new ServerBootstrap();
ChannelFuture channelFuture
= serverBootstrap.group(bossGroup, workerGroup).
channel(NioServerSocketChannel.class).
childHandler(new NettyServerInitHandler()).bind(8080).sync();
// Wait until the server socket is closed.
// 期待直到服务端的 socket 敞开, 在这个例子中,服务端的 Socket 永远不会敞开,然而你能够优雅的敞开
// 你的服务
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
channelFuture.channel().closeFuture().sync();
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();}
}
// 综合初始化器
public class NettyServerInitHandler extends ChannelInitializer<SocketChannel> {
// 通道被首次注册执行此办法等价于连贯被建设
@Override
protected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();
// 将处理器退出
pipeline.addLast("HttpServerCodec",new HttpServerCodec());
// 将自定义的解决退出
pipeline.addLast("NettyServerHandler",new NettyServerHandler());
}
}
public class NettyServerHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
// 如果音讯的类型是 Http 申请, 才解决
if (msg instanceof HttpRequest){
// 对立编码
ByteBuf content = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8);
// 发送 HTTP 响应
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
// 设置申请头
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
//
response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
// 返回响应
ctx.writeAndFlush(response);
}
}
}
在浏览器输出 localhost:8080 的测试后果:
在 curl 输出 localhost:8080 的测试后果:
浏览器的测试后果字很小,我调大了才这么显示。
根本剖析
还记得 Java 的网络编程吗?咱们要写 Java 的 NIO 做网络编程,通常是上面这样:
Selector selector = Selector.open();
// 创立服务端的通道
ServerSocketChannel serverSocketChannel1 = ServerSocketChannel.open();
// 将该通道设置为非阻塞形式
serverSocketChannel1.configureBlocking(false);
// 创立 Socket
ServerSocket serverSocket = serverSocketChannel1.socket();
// 绑定端口
serverSocket.bind(new InetSocketAddress(8888));
// 为 ServerSocketChannel 注册对对客户端申请连贯事件感兴趣
// 此时该 channel 处于选择器得治理之下
serverSocketChannel1.register(selector, SelectionKey.OP_ACCEPT);
基本上外围的思维是学生成选择器对象 (Selector),而后将通道设置为非阻塞模式,而后监听端口,而后将通道交给咱们产生的选择器治理。而后获取选择器上就绪的事件,依据不同的就绪事件做不同的数据处理。
Netty 将其简化了,提供了对立的 API,EventLoopGroup 用于接管连贯和解决连贯,咱们须要两个 EventLoopGroup,一个用于治理连贯,另一个解决连贯。在 ServerBootstrap 指定哪个治理连贯,哪个解决连贯。最初咱们还是要解决数据,也就是在 ServerBootstrap 指定数据的解决者。
Netty 解决的根本流程:
绝对于原生的 NIO, 逻辑又清晰了很多。
总结一下应用 Netty 编码的根本流程
-
入口类 / 主程序类
- 配置一些参数(端口,谁解决连贯)
- 内置初始化器
- 调用一些内置的类(编码、解码) -
自定义初始化器
- 编写一些自定义的类(解决数据)
Netty 外部提供了十分弱小的类库(内置初始化器),每个初始化器都能够实现一个小性能,当前在开发时,咱们第一步须要先将须要实现的性能分解成若干部,第二部只须要在 netty 类库中寻找,看哪些已有类可能帮忙咱们间接实现;第三步,如果某个性能 Netty 没有提供,则编写自定义的初始化器。
根本 API 梳理
- 继承链
留神 InBound 和 OutBound。
- 罕用的 API
咱们联合继承连来说罕用的 API, 从 SimpleChannelInboundHandler 开始吧:
ChannelHandlerAdapter 有两个空办法,是从 ChannelHandler(是一个接口)中而来。
而后办法上没有阐明,咱们去看对应的接口上的办法阐明:
/**
* Gets called after the {@link ChannelHandler} was added to the actual context
and it's ready to handle events.
在对应的 ChannelHandler(处理器)被退出到理论的上下文,该处理器筹备去解决一些事件时 被调用。*
*/
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
同样的 handlerRemoved 是在对应的处理器被移除之后,被调用。
void channelRegistered 通道被注册时触发
void channelActive 通道被激活触发
void channelInactive 通道失活触发
void channelUnregistered 通道勾销注册
咱们依照多路复用的 I / O 模型再来梳理一下哈, 首先咱们将通道等同于连贯, 依据咱们下面讲的 bossGroup 负责管理连贯的状态, 当连贯建设后,bossGroup 将连贯交给 workerGroup 去解决连贯。然而连贯建设实现之后,并不代表数据到来了, 所以 Netty 的设计者将这些都形象了进去,其实也是在形象网络通信的过程:
- 连贯建设 (通道被注册)
- 有数据到来 (通道被激活)
- 数据被读取结束(通道失活)
- 连贯勾销(通道勾销注册)
咱们重写这些办法来再次看下 HTTP 协定
再度扫视 HTTP 协定
public class NettyServerHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {if (msg instanceof HttpRequest) {HttpRequest httpRequest = (HttpRequest) msg;
URI uri = new URI(httpRequest.uri());
// 浏览器申请的时候会申请两次, 会默认申请一次 icon
// 这里先不解决这次申请。if (!"/favicon.ico".equals(uri.getPath())) {System.out.println(httpRequest.method().name());
ByteBuf content = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8);
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
ctx.writeAndFlush(response);
}
}
}
// 减少解决时, 主动触发
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {System.out.println("1. 减少处理器");
super.handlerAdded(ctx);
}
// 当把通道注册到选择器触发
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {System.out.println("2. 通道被注册");
super.channelRegistered(ctx);
}
// 通道被激活, 触发另一个办法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("3. 通道被激活");
super.channelActive(ctx);
}
// 激活的通道, 失去激活状态
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {System.out.println("4. 通道失活");
super.channelInactive(ctx);
}
// 通道勾销注册
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {System.out.println("5. 通道勾销注册");
super.channelUnregistered(ctx);
}
// 失败捕捉
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {System.out.println("6. 异样捕捉");
super.exceptionCaught(ctx, cause);
}
}
这里我认为你曾经装上了 curl 了,咱们启动服务端来测试一下。
- curl 的测试后果
对应网络申请一次申请,一次响应。
咱们当初用浏览器来测试一下:
有可能你呈现的是这种状况:
咱们首先来剖析一下为什么会呈现这种情况:
首先是浏览器发动了两次申请,一次申请就是一个连贯。
所以是两次,那为什么两次后果都不一样呢?
这就跟建设连贯的先后顺序有关系了,第一种后果就是两次连贯建设起来的工夫距离比拟短造成的。所以是 112233.
而第二种就是在第一个连贯建设结束的时候,第二个申请才建设起连贯。
而后咱们清空控制台,再来申请一下看一下后果:
这种状况是为什么呢? 因为咱们对于浏览器发动的 favicon.ico 申请没有响应,这个申请还始终在期待中。
咱们再次申请的时候,浏览器会将上一次的申请勾销,也就是连贯勾销建设,再度申请一次。
所以会呈现 1、2、3、4、5 这种状况。
那么为什么咱们解决了的申请,为很么连贯没有勾销呢?
这就波及到 HTTP 协定了,咱们晓得 HTTP 是无状态的,也就是说每次客户端和服务端建设连贯都是要 TCP 建设连贯,再传送数据,但有的时候这样开销有点大,咱们并不心愿每次 HTTP 申请都建设 TCP 连贯,那能不能放弃上一次的连贯呢?
这就是 keep-alive,放弃上一次的连贯,咱们下面指定的 HTTP 的协定是默认开启的,所以对于解决了的申请,并不会断开连接。在敞开网页之后就会断掉连贯。
再度总结
咱们当初曾经对 Netty 曾经有一个大抵的认知,晓得这是一个高性能的、高扩展性的 NIO 框架。也能通过一些 Netty 提供的接口来网络编程了。应用 Netty 进行网络编程的流程是相似的,像 JDBC 一样,先是在在 ServerBootstrap 中设定监测连贯的和解决连贯的,而后在 ServerBootstrap 设定综合处理器(也就是 NettyServerInitHandler),在综合处理器中退出 Netty 提供的处理器和自定义的处理器。在自定义的处理器中解决申请和响应。如果你还想在连贯建设的时候,做一些工作,Netty 也能满足你的需要。刚开始可能比拟晕哈,这个失常,能够先大抵记住这个流程
事件驱动机制与 Reactor 模式简介
Netty 将本身定义为异步事件驱动的网络编程框架,那什么是事件驱动,艰深的说,就是在 Netty 中某事件产生会触发某些办法,在下面的根本 API 梳理中,咱们曾经发现了, 连贯建设触发一个办法,勾销连贯触发一个办法。更为精确的说法是 Netty 在收到 Socket 数据流的时候,会在 pipeline 上的处理器上流传,某些事件被触发即为调用对应的办法。
那 Reactor(反应器)模式呢?这是 Netty 的高性能就在于 Reactor(反应器模式)模式上,Reactor 是这样一种模式,它要求主线程 (I/ O 处理单元) 只负责监听文件形容上是否有事件产生,有的话立即将该事件告诉工作线程。
用 Netty 编写两个之间的聊天
这个其实只是一个示例,咱们用来加深对 Netty 的了解而已,自身打算放在 GitHub 上,想了想还是放在这里吧。应用 Netty 编写代码的套路是统一的:
- 在入口类设置监测连贯的和真正解决连贯的
- 绑定端口
- 设置初始化处理器(NettyServerInitHandler),在这个处理器中退出 Netty 自带的和本人编写的
- 在本人编写的处理器中,做数据处理工作。
所以下面咱们编写的 NettyDemo01 也能够接着复用,这里咱们要再讲一下 ChannelPipeline,咱们下面曾经讲了,Netty 在收到 Socket 数据流的时候会在 pipeline 上的处理器上流传。而后咱们下面调用的都是 addlast。所以咱们自定义的处理器总是最初被流传上。
在编写对应的代码之前,咱们再来一点点网络编程的常识,网络协议中有一个报文的概念,限度了一次发送信息最多发送多少个,TCP 协定发送接收数据时,如果数据过大,接管到的数据会是分包的, 这就是通信的单方发送的音讯可能是通过了拆分和合并,只管应用微信聊天的咱们可能并没有感觉。
Netty 为咱们提供拆分和并的类咱们只须要将其退出到处理器中就行。除了拆分和合并,咱们还得对立编码,不必放心 Netty 也提供了。
所以下面的初始化处理器就被革新成了这样:
public class MyNettyServerInit extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();
// 退出拆分
pipeline.addLast("decoder",new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,8,0,8));
// 退出合并
pipeline.addLast("prepender",new LengthFieldPrepender(8));
// 退出解码
pipeline.addLast("StringDecoder",new StringDecoder(CharsetUtil.UTF_8));
// 退出编码
pipeline.addLast("StringEncoder",new StringEncoder(CharsetUtil.UTF_8));
// 退出自定义的处理器
pipeline.addLast("MyNettyServerHandler",new MyNettyServerHandler());
}
}
LengthFieldBasedFrameDecoder 这个类咱们简略解释一下,个别咱们称这个类为拆包器,咱们用的是这个构造函数:
public LengthFieldBasedFrameDecoder(int maxFrameLength,
int lengthFieldOffset,
int lengthFieldLength,
int lengthAdjustment,
int initialBytesToStrip)
maxFrameLength: 最大的报文长度,而后发送过去的信息超过这个,就会报异样,因而通信的单方须要约定一次说多少,
lengthFieldOffset: 一段报文,从哪开始读
LengthFieldBasedFrameDecoder 对于这个类咱们要细讲,恐怕这篇文章得再加上 8000 字。咱们权且就这么了解。
public class MyNettyServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
// 接管音讯
System.out.println("服务端 接管到了" + ctx.channel().remoteAddress() + ", 音讯是:" + msg);
// 发送音讯
System.out.println("请输出内容:");
String send = new Scanner(System.in).nextLine();
ctx.channel().writeAndFlush(send);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {ctx.writeAndFlush("开始聊天吧...");
}
}
下面咱们讲过 channelActive 这个办法,连贯真正建设的时候触发这个办法, 为什么这样做呢?因为咱们这个处理器继承的还是 SimpleChannelInboundHandler,先读后写,留神这里带的 In,就是解决读的,有 In 就有 Out。
聊天的客户端要略微改变一下:
public class MyNettyClient {public static void main(String[] args) throws InterruptedException {EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
// 客户端, 所以不是 ServerBootStrap
Bootstrap bootstrap = new Bootstrap();
ChannelFuture channelFuture = bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).
handler(new MyNettyClientInit()).connect("127.0.0.1", 8080).sync();
channelFuture.channel().closeFuture().sync();
eventLoopGroup.shutdownGracefully();}
}
MyNettyClientInit 能够复用服务端的:
public class MyNettyClientInit extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 8));
pipeline.addLast("pretender", new LengthFieldPrepender(8));
pipeline.addLast("StringDecoder", new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast("StringEncoder", new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast("MyNettyClientHandler", new MyNettyClientHandler());
}
}
客户端处理器
public class MyNettyClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {System.out.println("客户端 接管到了音讯:" + ctx.channel().remoteAddress() + ", 音讯是" + msg);
System.out.println("请输出内容:");
String send = new Scanner(System.in).nextLine();
ctx.channel().writeAndFlush(send);
}
}
留神下面的建设连贯时,发送的信息只能由一段收回,如果两端同时有,则两端再次陷入期待对方发送信息的状态。
有什么用
心跳机制
微服务正在称为常态的明天,微服务有个外围的组件也被咱们所熟知,就是注册核心,注册核心该怎么晓得某服务是活着的呢?咱们通过心跳来判断人是否活着,那么注册核心也是通过心跳,每隔一段时间和服务进行通信,活着发送或者接管。
在一段时间内没收到信息,那么注册核心就能够认为该服务因为某些起因下线了。Netty 也为咱们提供了这样的类库, 也是 Netty 内置的一个处理器 IdleStateHandler。
public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds)
readerIdleTimeSeconds: 每隔几秒读一下
writerIdleTimeSeconds: 每隔几秒写一下
allIdleTimeSeconds: 读或写超时工夫
留神是两次距离,客户端发送过去一次申请,服务端即实现了一次读写。
示例:
public class MyNettyServerInit extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("idleStateEvent", new IdleStateHandler(2,3,6));
pipeline.addLast("myIdleStatteHandler",new MyIdleStateHandler());
}
}
public class MyIdleStateHandler extends SimpleChannelInboundHandler<Object> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { }
// 读超时 或 写超时触发该办法
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {if (evt instanceof IdleStateEvent){
String eventType = null;
IdleStateEvent event = (IdleStateEvent)evt;
switch (event.state()){
case READER_IDLE:
// 客户端发送的两次申请距离超过两秒触发
eventType = "读超时";
break;
case WRITER_IDLE:
// 客户端发送的两次申请距离超过三秒触发
eventType = "写超时";
break;
case ALL_IDLE:
// 六秒内读或写都没触发,认为读写超时
eventType = "读写超时";
break;
}
System.out.println(eventType);
ctx.channel().close();
}
}
}
上面咱们用 curl 测试下:
写超时不再测试
netty 实现 webSocket
WebSocket 简介
HTTP 协定一贯是客户端发动申请,服务端回应,但有的状况下,咱们心愿服务端被动的向客户端推送。这就是 WebSocket 协定。在之前服务端要想向客户端被动推送信息,要么是轮询(隔断工夫问一下),要么始终是监听(要么是始终不挂电话),有响应之后,再度从新建设 HTTP 连贯。这种形式相当耗费资源。WebSocket 就可能做到一次 HTTP 信息,屡次数据发送。
示例
java 端示例:
public class NettyServerInitHandler extends ChannelInitializer<SocketChannel> {
// 通道被注册执行此办法
@Override
protected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("HttpServerCodec",new HttpServerCodec());
// 咱们用 POST 形式申请服务器的时候,对应的参数信息是保留在 message body 中的, 如果只是单纯的用 HttpServerCodec
// 是无奈齐全的解析 Http POST 申请的,因为 HttpServerCodec 只能获取 uri 中参数,所以须要加上
// HttpObjectAggregator.
pipeline.addLast("HttpObjectAggregator", new HttpObjectAggregator(4096));
// 设定地址
pipeline.addLast("WebSocketServerProtocolHandler",new WebSocketServerProtocolHandler("/myWebSocket"));
pipeline.addLast("NettyServerHandler",new MyWebSocketServerHandler());
}
}
public class MyWebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {System.out.println("server 接管到的客户端音讯:" + msg.text());
ctx.channel().writeAndFlush(new TextWebSocketFrame("hello client"));
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {System.out.println("连贯建设....");
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {System.out.println("连贯勾销建设....");
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript">
// websocket 是 ws 结尾
var webSocket = new WebSocket("ws://localhost:8080/myWebSocket");
function sendMessage(msg) {if (webSocket.readyState == webSocket.OPEN)
webSocket.send(msg);
}
webSocket.onmessage = function (event) {document.getElementById("tip").innerText = "接管的音讯:" + event.data;
}
webSocket.onopen = function (event) {document.getElementById("tip").innerText = "连贯开启";
}
webSocket.onclose = function (event) {document.getElementById("tip").innerText = "连贯敞开";
}
</script>
</head>
<body>
<form>
<textarea name="message">
</textarea>
<input type="button" onclick="sendMessage(this.form.message.value)" value="发送">
</form>
<div id="tip"></div>
</body>
</html>
成果:
顺带讲一下 Netty 官网指南
这里讲下,Netty 开发者指南写的还是挺不错的:
然而对刚写了没多少行代码的初学者来说,看这个就有点晕了,不仅须要良好的英文浏览能力,还须要对网络协议有肯定的理解,同时 Java 根底要扎实,对网络编程要有肯定的理解。不然不光看我这篇教程,你也非常晕。
Developer guide 开发者指南
参考资料:
- 艰深地讲,Netty 能做什么?
- netty 中 future.channel().closeFuture().sync()作用
- Java NIO 浅析
- 如何深刻理解 Reactor 和 Proactor?
- HTTP Keep-Alive 模式
- netty 的事件驱动
- 服务器端编程心得(二)—— Reactor 模式
- WebSocket 是什么原理?为什么能够实现长久连贯?