闲的无聊想学习一下netty
写个netty入门吧
什么是netty
有了Netty,你可以实现自己的HTTP服务器,FTP服务器,UDP服务器,RPC服务器,WebSocket服务器,Redis的Proxy服务器,MySQL的Proxy服务器等等。
我们回顾一下传统的HTTP服务器的原理
1、创建一个ServerSocket,监听并绑定一个端口
2、一系列客户端来请求这个端口
3、服务器使用Accept,获得一个来自客户端的Socket连接对象
4、启动一个新线程处理连接
4.1、读Socket,得到字节流
4.2、解码协议,得到Http请求对象
4.3、处理Http请求,得到一个结果,封装成一个HttpResponse对象
4.4、编码协议,将结果序列化字节流 写Socket,将字节流发给客户端
5、继续循环步骤3
IO多路复用。它是由操作系统提供的系统调用,早期这个操作系统调用的名字是select,但是性能低下,后来渐渐演化成了Linux下的epoll和Mac里的kqueue。
客户端监听(Listen)时,Accept是阻塞的,只有新连接来了,Accept才会返回,主线程才能继
读写socket时,Read是阻塞的,只有请求消息来了,Read才能返回,子线程才能继续处理
读写socket时,Write是阻塞的,只有客户端把消息收了,Write才能返回,子线程才能继续读取下一个请求
传统的BIO模式下,从头到尾的所有线程都是阻塞的,这些线程就干等着,占用系统的资源,什么事也不干。
NIO是怎么做到非阻塞的呢。它用的是事件机制。它可以用一个线程把Accept,读写操作,请求处理的逻辑全干了。如果什么事都没得做,它也不会死循环,它会将线程休眠起来,直到下一个事件来了再继续干活,这样的一个线程称之为NIO线程。
官方那个给出的介绍是:
Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
然后我们简单理解一下,这玩意就是个程序,干什么的?netty是封装java socket noi的。
类似的功能是 apache的mina。
相对于Tomcat这种Web Server(顾名思义主要是提供Web协议相关的服务的),
Netty是一个Network Server,是处于Web Server更下层的网络框架,也就是说你可以使用Netty模仿Tomcat做一个提供HTTP服务的Web容器。
说白了,就是一个好使的处理Socket的东西。要是想了解详细点,可以去看看官方的介绍。
另外Netty框架是基于事件机制的,简单说,就是发生什么事,就找相关处理方法。就跟着火了找119,抢劫了找110一个道理。所以,这里,我们处理的是当客户端和服务端完成连接以后的这个事件。什么时候完成的连接,Netty知道,他告诉我了,我就负责处理。这就是框架的作用。Netty,提供的事件还有很多,以后会慢慢的接触和介绍。
Netty原理和使用
Netty是一个高性能 事件驱动的异步的非堵塞的IO(NIO)框架,用于建立TCP等底层的连接,基于Netty可以建立高性能的Http服务器。支持HTTP、 WebSocket 、Protobuf、 Binary TCP |和UDP,Netty已经被很多高性能项目作为其Socket底层基础,如HornetQ Infinispan Vert.x
Play Framework Finangle和 Cassandra。其竞争对手是:Apache MINA和 Grizzly。
传统堵塞的IO读取如下:
InputStream is = new FileInputStream(“input.bin”);
int byte = is.read(); // 当前线程等待结果到达直至错误
而使用NIO如下:
while (true) {
selector.select(); // 从多个通道请求事件
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectorKey key = (SelectionKey) it.next();
handleKey(key);
it.remove();
}
}
堵塞与非堵塞原理
传统硬件的堵塞如下,从内存中读取数据,然后写到磁盘,而CPU一直等到磁盘写完成,磁盘的写操作是慢的,这段时间CPU被堵塞不能发挥效率。
使用非堵塞的DMA如下图:CPU只是发出写操作这样的指令,做一些初始化工作,DMA具体执行,从内存中读取数据,然后写到磁盘,当完成写后发出一个中断事件给CPU。这段时间CPU是空闲的,可以做别的事情。这个原理称为Zero.copy零拷贝。
Netty底层基于上述Java NIO的零拷贝原理实现:
基础知识是nio
关于NIO基础的知识:https://my.oschina.net/andylucc/blog/614295
http://www.cnblogs.com/dolphin0520/p/3919162.html
http://blog.csdn.net/wuxianglong/article/details/6604817
netty 官方API: http://netty.io/4.1/api/index.html
netty 中文指南:https://waylau.com/netty-4-user-guide/ (来自个人)
Reactor线程模型
一个NIO线程+一个accept线程:
https://segmentfault.com/img/bVbj1ZH?w=550&h=211
使用原生JDK
API复杂
高可用的话:需要出路断连重连、半包读写、失败缓存等问题
JDK NIO的bug
空轮询的bug
netty版本:netty-5.0.0.Alpha2 http://files.cnblogs.com/files/applerosa/netty-5.0.0.Alpha2.7z
maven依赖:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
第一步:首先创建一个工程、引入包依赖。这都不会还是看看java入门吧
第二步
关于netty ,首先要搞清楚,这是建立在客户端和服务端之间的。
我们先说服务端,服务端建立相应的规则,然后运行起来,等待客户端访问或者发送”消息“。好了,我们先建立服务端代码:
代码后面再补上
bootStrap,通过添加hanlder,我们可以监听Channel的各种动作以及状态的改变,包括连接,绑定,接收消息等。
在基类AbstractBootstrap有handler方法,目的是添加一个handler,监听Bootstrap的动作,客户端的Bootstrap中,继承了这一点。
在服务端的ServerBootstrap中增加了一个方法childHandler,它的目的是添加handler,用来监听已经连接的客户端的Channel的动作和状态。
handler在初始化时就会执行,而childHandler会在客户端成功connect后才执行,这是两者的区别。
pipeline是伴随Channel的存在而存在的,交互信息通过它进行传递
们通常要绑定一个handler(Netty:Bootstrap的handler和childHandler)进行通道的监听,当收到数据时就会触发某个事件,从而进行进一步的处理。
目前我们用的比较多的就是ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter。
ChannelInboundHandlerAdapter,看名字中的 IN,就是进入的意思,一般就是事件(event),比如当有数据到来时,channel被激活时或者不可用时,下面介绍几个最常用的。
channelActive
通道激活时触发,当客户端connect成功后,服务端就会接收到这个事件,从而可以把客户端的Channel记录下来,供后面复用
channelRead
这个必须用啊,当收到对方发来的数据后,就会触发,参数msg就是发来的信息,可以是基础类型,也可以是序列化的复杂对象。
channelReadComplete
channelRead执行后触发
exceptionCaught
出错是会触发,做一些错误处理
ChannelOutboundHandlerAdapter,看到了out,表示出去的动作,监听自己的IO操作,比如connect,bind等,在重写这个Adapter的方法时,记得执行super.xxxx,否则动作无法执行。
bind
服务端执行bind时,会进入到这里,我们可以在bind前及bind后做一些操作
connect
客户端执行connect连接服务端时进入
Group:群组,Loop:循环,Event:事件,这几个东西联在一起,相比大家也大概明白它的用途了。
Netty内部都是通过线程在处理各种数据,EventLoopGroup就是用来管理调度他们的,注册Channel,管理他们的生命周期,下面就来看看EventLoopGroup是怎样工作的。
在Netty框架初探中,当我们启动客户端或者服务端时,都要声明一个Group对象
- EventLoopGroup group = new NioEventLoopGroup();
- NioEventLoopGroup extends MultithreadEventLoopGroup extends MultithreadEventExecutorGroup
- public NioEventLoopGroup() {
- this(0);
- }
- //他会连续调用内部的构造函数,直到用下面的构造去执行父类的构造
- //nThreads此时为0,马上就会提到这个参数的用处
- public NioEventLoopGroup(
- int nThreads, Executor executor, final SelectorProvider selectorProvider) {
- super(nThreads, executor, selectorProvider);
- }
- //这里会根据nThreads创建执行者数组
- private final EventExecutor[] children;
- protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
- if (nThreads <= 0) {
- throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
- }
- if (executor == null) {
- executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
- }
- //这里创建EventExecutor数组对象
- children = new EventExecutor[nThreads];
- if (isPowerOfTwo(children.length)) {
- chooser = new PowerOfTwoEventExecutorChooser();
- } else {
- chooser = new GenericEventExecutorChooser();
- }
- //此处循环children数组,来创建内部的NioEventLoop对象
- for (int i = 0; i < nThreads; i ++) {
- boolean success = false;
- try {
- //newChild是abstract方法,运行期会执行具体的实例对象的重载
- children[i] = newChild(executor, args);
- success = true;
- } catch (Exception e) {
- // TODO: Think about if this is a good exception type
- throw new IllegalStateException("failed to create a child event loop", e);
- } finally {
- //如果没有成功,做关闭处理
- if (!success) {
- for (int j = 0; j < i; j ++) {
- children[j].shutdownGracefully();
- }
- for (int j = 0; j < i; j ++) {
- EventExecutor e = children[j];
- try {
- while (!e.isTerminated()) {
- e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
- }
- } catch (InterruptedException interrupted) {
- // Let the caller handle the interruption.
- Thread.currentThread().interrupt();
- break;
- }
- }
- }
- }
- }
- ......
- }
- NioEventLoop extends SingleThreadEventLoop extends SingleThreadEventExecutor
服务端的bind方法执行的是AbstractBootstrap的bind方法,bind方法中先会调用validate()方法检查是否有group
所以,假如初始时,没有设置 Bootstrap的group的话,就会报错。
最终bind调用doBind,然后调用doBind0,启动一个Runnable线程
我们这里,channel.eventLoop()得到的是NioEventLoop对象,所以执行NioEventLoop的run方法,开始线程运行。
我们在创建Bootstrap初期,会调用它的group方法,绑定一个group,这样,一个循环就开始运行了。
后面在写channel channelpipeline options configs