1.Netty
网络应用通信框架,概念性的东西就不多说了,直接说她能做什么,比如我一个应用需要和其他服务通讯,那么就可以用到这个框架,那我http不行吗也行,但是Netty框架封装了更多的功能,不仅仅是通讯。
下面说一下netty的优点
- netty功能强大(具体为什么强大,用了就知道)
- 线程安全
- 高可用
- 消除了一写nio层面bug
本身Netty 可以基于BIO、NIO、AIO,但是最常用的就是NIO模型,并且Netty框架也支持reactor三种线程模型。
2. Netty 主从Reactor的设计
这边的boss就是Reactor主线程做的事情,主要是连接的建立,然后worker 就是IO线程池,主要监听读写IO的工作,然后读取数据之后会给pipeline通过handler处理数据并且返回。
2.1 ChannelPipeline 和 ChannelHandler
这两个就是我们要使用的东西,用图来说明一下这两个东西的关系
2.1.1 channelhandler的分类
他有三种类型handler ,看我干嘛看图!面的head就是duplex 类型的都处理,tail是只处理in的数据,
而我们写的时候看我们情况,想添加哪一种就添加哪一种handler
2.1.1 netty编码
废话不多说直接上代码
Netty编写服务端
package com.zx.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public class NettyServer {
public static void main(String[] args) {
//建立连接线程池,只负责建立socketchannel
EventLoopGroup boss = new NioEventLoopGroup(1);
//从属线程池负责io的线程池
EventLoopGroup worker = new NioEventLoopGroup();
//可以理解为启动器
ServerBootstrap serverBootstrap = new ServerBootstrap();
//组成一个Reactor
serverBootstrap.group(boss,worker)
//这个就是主线程监听的类,nio里面是ServerSocketChannel
.channel(NioServerSocketChannel.class)
//给主线程添加日志
.handler(new LoggingHandler(LogLevel.INFO))
//这边每一个SocketChannel建立的时候都会相应建立一个pipeline 一一对应的
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new MyInHandler());
}
});
try {
ChannelFuture cf = serverBootstrap.bind(8110).sync();
cf.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
worker.shutdownGracefully();
boss.shutdownGracefully();
}
}
}
客户端的handler 写法,当然这边只重写了部分方法。还有方法自己研究。
package com.zx.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class MyInHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("通道建立完毕!");
super.channelActive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("读到数据里!你要处理了!");
ByteBuf byte1 = (ByteBuf) msg;
byte [] a = new byte[byte1.readableBytes()];
byte1.readBytes(a);
String msgNew = new String(a, Charset.defaultCharset());
System.out.println("读取数据"+ msgNew);
super.channelRead(ctx, msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("数据读取完毕了!");
Channel channel = ctx.channel();
//这边的方法是创建一个buff 对象
ByteBuf buffer = channel.alloc().buffer();
buffer.writeBytes("你好我是老八".getBytes(StandardCharsets.UTF_8));
channel.writeAndFlush(buffer);
super.channelReadComplete(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("有错误了!");
super.exceptionCaught(ctx, cause);
}
}
客户端写法这边不贴代码直接图片标注出与客户端不一样的地方,handler 和服务端差不多的写法
2.1.2 Outboundhandler
这个是监听出去的数据的方法。进来的方法有专门的方法监听出去肯定也有
这边注意一下,Inboundhandler往外写了几次数据,我这个out就会触发几次。并不是把前面所有写的数据汇总然后一起出发,注意这边写了多少次就会触发多少次
package com.zx.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import java.nio.charset.StandardCharsets;
public class MyOutHandler extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf byte1 = (ByteBuf) msg;
System.out.println("我看看你要发什么JB数据"+byte1.toString(StandardCharsets.UTF_8));
super.write(ctx, msg, promise);
}
}
2.1.3 执行顺序
所有的handler 执行都是有顺序的,我们分析一小段代码
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new MyOutHandler());
pipeline.addLast(new MyInHandler());
pipeline.addLast(new MyInHandler2());
注意看这边有个addLast方法,当然还有子方法addFirst 之前说过handler是双向链表结构的,那么看图
要想顺序没问题out一定要靠近数据的出口。结合上图了解一下
2.1.3 事件传递
这边的super 就是执行的向后传递的工作,因为是链表结构,某种意义上前面不给后面传递,那么后面的事件就没有意义。super注释掉,后面的handler的该事件就不会被触发
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("通道建立完毕!");
//所有的super都是往后传递的,如果注释掉,后面的这个事件就不会发生!注意这边是某个事件,而不是所有事件
super.channelActive(ctx);
}
2.1.4 往外写数据的两种方式
一个是channel一个是ctx,这两个是有本质的区别的,如果是channel那么会从最后的tail往前面找outbound方法,如果是ctx 只会从当前handler往前找outbound。这也解释了为什么outbound会触发两次的问题。
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("数据读取完毕了!");
Channel channel = ctx.channel();
ByteBuf buffer = channel.alloc().buffer();
buffer.writeBytes("你好我是老八".getBytes(StandardCharsets.UTF_8));
//channel.writeAndFlush(buffer);
//这两个有区别,主要的区别是从那边开始返回!
ctx.writeAndFlush(buffer);
super.channelReadComplete(ctx);
}
所以在outhandler里面不能用channel,这样消息就会陷入死循环,能理解这个情况,那么这个顺序理解的就非常到位了。
2.1.4 handler 是可以共享的
我们这边每次都new 了handler,意思就是每个pipleine 都会对应一个新的handler,但是也存在多个pipeline 对应一个handler 只需要在该handler上加一个共享注解@ChannelHandler.Sharable
@ChannelHandler.Sharable
public class MyInHandler2 extends ChannelInboundHandlerAdapter {
2.1.5 Bytebuf
这边内容还比较多,后面会开一篇新的文章介绍一下,这边介绍一下重要的指针。
其实就是我们数学里面追击的场景。这边需要注意的是butebuf你读过的数据,再读是读不到的,
上面的handler读取过的数据往后传递的时候后面的handler是读不出数据的。
2.2 结束语
这篇主要说一下netty的应用,但是netty里面重要的组件后面会各自单独的开一篇文章解释,内容较多,就不在这篇阐述 了。