一、应用场景
目前,部分的硬件厂商对接硬件要求需要基于TCP协议发送报文的形式,以及可能部分系统做集成时都需要基于TCP协议,建立Socket通道,发送实时消息,相信很多接触过Java的,都使用过Socket编程,包括BIO/NIO/AIO,但是这些编程起来较为复杂,Netty很好的帮我们简化了这些配置的流程,让我们更方便的使用。
二、Netty的使用
我们先不谈原理,先来看看Netty是如何使用的。
jar包:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
Socket需要一个服务端和客户端
2.1 服务端
服务端的创建
public static void main(String[] args) throws Exception {
// 创建两个线程组bossGroup和workerGroup,含有的子线程NioEventLoop的个数默认为cpu核数的两倍
// bossGroup只是处理连接请求,真正的和客户端业务处理,会交给workerGroup完成
// 这里面的int值参数代表了要创建的线程数量
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(8);
try {
// 创建服务器端的启动对象
ServerBootstrap bootstrap = new ServerBootstrap();
//使用链式编程来配置参数
bootstrap.group(bossGroup,workerGroup) //设置两个线程组
//使用NioServerSocketChannel做为服务器的通道实现
.channel(NioServerSocketChannel.class)
// 初始化服务器连接队列大小,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。
// 多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理
.option(ChannelOption.SO_BACKLOG,1024)
//创建通道初始化对象,设置初始化参数
.childHandler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel ch)throws Exception {
//对workerGroup的SocketChannel设置处理器
ch.pipeline().addLast(new NettyServerHandler());
}
});
sout("netty server start...")
// 绑定一个端口并且同步,生成一个ChannelFuture异步对象,通过isDone()等方法可以判断异步事件的执行情况
// 启动服务器(并绑定端口),bind是异步操作,sync方法是异步操作完毕
ChannelFuture cf = bootstrap.bind(9000).sync();
//给
}
}
服务端收发消息的处理类
这里注意这个ChannelHandlerContext参数,如果要发送消息 ,需要通过这个参数,如果外部的方法中需要发送消息,可以将这个参数赋给一个全局的变量。
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
// 客户端连接
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
// 客户端发送信息
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
}
// 数据读取完毕时的处理方法
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = Unpooled.copiedBuffer("HelloClient".getBytes(CharsetUtil.UTF_8));
ctx.writeAndFlush(buf);
}
}
2.2 客户端
客户端的创建
public static void main(String[] args)throws Exception {
// 客户端需要一个事件循环组
EventLoopGroup group = new NioEventLoopGroup();
try {
// 创建客户端启动对象
// 注意客户端使用的对象不是ServerBootstra而是Bootstrap
Bootstrap bootstrap = new Bootstrap();
// 设置相关参数
bootstrap.group(group)// 设置线程组
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 加入处理器事件
socketChannel.pipeline().addLast(new NettyClientHandler());
}
});
sout("netty client strat..");
//启动客户端去连接服务器端
ChannelFuture cf = bootstrap.connect("127.0.0.1",9000).sync();
//对通道关闭进行监听
cf.channel().closeFuture.sync();
} finally {
group.shutdownGracefully();
}
}
客户端处理类
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx){
ByteBug buf = Unpooled.copiedBuffer("HelloServer".getBytes(CharsetUtil.UTF_8));
ctx.writeAndFlush(buf);
}
@Override
public void channelRead(ChannelHandlerContext ctx,Object msg){
ByteBuf buf = (ByteBuf)msg;
sout("收到服务端发送的消息" + bug.toString(CHarsetUtil.UTF_8);
}
}
基本的使用就是这样了,下面我们来看一下Netty的底层
三、Netty底层
3.1 通信模型
我们先来看一张Netty底层的通信模型,这张图是从网上找到的,画的很不错
这里有一个多路复用器Selector的概念
如果想详细了解这个概念,这里有一篇博客专门讲这个:多路复用器Selector
这里大概讲一下Selector的作用,在Netty中,它主要起到了监听的作用,用来监听的一个IO操作有没有发生数据的交互,如果发生了,它可以判断这个操作是读操作,还是连接操作,如果是连接操作,服务端的BossGroup首先将这个连接放入WorkerGroup中的队列中去,然后进行我们配置的处理类里面的过程,如果是读操作,就会被WorkerGroup中的Selector监听到,进行我们编写的读操作的逻辑,Netty实现的是NIO的操作,所以线程之间不会发生阻塞。
3.2 Netty的零拷贝
再来说说Netty的零拷贝
首先要明确一点,没有真正意义上的零拷贝,我们这里说的零拷贝,是指Java虚拟机没有对数据进行再次的拷贝,而是直接处理。
传统Socket的操作:
操作系统在进行Socket通信操作时,接收到的数据会分配到操作系统的直接内存当中,所谓的直接内存,就是宿主机的内存,不是java虚拟机管理的内存。而我们的Java程序想要操作Socket传来的数据,需要将直接内存中的数据拷贝到java虚拟机中来进行操作。
Netty的实现 :
与传统Socket操作不同的地方是,Netty在操作Socket传来的数据时,不会将这些数据拷贝到Java的虚拟机中来,而是直接给出这些数据的引用,指向这些数据的地址,进行数据操作时,直接操作的是直接内存,所以还是有拷贝的,只不过没有拷贝到虚拟中来。