Netty
简介
学习完NIO后,学习Netty会有一些熟悉的感觉。Netty异步操作,本文主要借鉴目前为止最透彻的的Netty高性能原理和框架架构解析。优点什么的废话就不说了,直接上干货。
IO、线程相关补充
传统阻塞I/O(BIO),之前NIO总结也讲过NIO总结
IO复用模型:
接下来是线程模型
- 事件驱动模型
主要包括四个基本组件:事件队列、分发器、事件通道、事件处理器。一般有两种思路:轮询方式和事件驱动方式(消息通知方式) - Reactor线程模型
Reactor意为反应堆。有两个关键组成,Reactor和Handlers。Reactor在单独的线程中运行,负责监听和分发事件。类似电话接线员。Handlers处理程序执行I/O完成实际事件。
Netty模型、原理
Netty主要基于主从Reactors多线程模型做了一定的修改,其中主从Reactor多线程模型有多个Reactor:
- MainReactor负责客户端的连接请求,并将请求转交给SubReactor;
- SubReactor负责相应通道的IO读写请求;
- 非IO请求(具体逻辑处理)的任务则会直接写入队列,等待worker threads处理。
注:Netty的SubReactor和worker在同一线程,一个叫bossGroup,一个叫workerGroup。
NettydeI/O操作是异步的,包括Bind、Write、Connect等操作会简单的返回一个channelFuture。通过Future-Listener机制,用户可以主动获取IO操作结果。
模块组件
模块比较多,介绍完模块后会有整体的图,方便大家理解。
- Bootstrap意为引导,一个netty应用由一个Bootstrap开始,主要作用是配置整个Netty程序,串联各个组件。客户端启动引导类是Bootstrap、服务端是ServerBootstrap。
- Future、ChannelFuture。通过Future、ChannelFuture注册监听,获取异步操作的执行结果。
- ChannelHander。 一个接口,处理或拦截IO操作,并转发到ChannelPipeline的下一个处理程序。
- ChannelHandlerContext,保存Channel上下文信息,与ChannelHandler关联。
- ChannelPipeline,是个list,保存ChannelHander
- Channel,通道。个人理解,channel里面有一个业务处理链(channelPipeline),处理链是一个双向链表,由channelHandlerContext组成。这个双向链表每项与一个channelHandler关联。
- Selector 看NIO的看Selector都是老熟人了。选择器,对channel分发任务。
- NioEventLoop 线程。
- NioEventLoopGroup 线程池。线程池分两种bossGroup和workerGroup。想不起这两个线程池,往上看,在Netty模型、原理那里有提到。或许还是不太懂,别急,往下看,这些名称介绍只是让你有个初步印象。
简单示例
服务端结构
服务端代码
package com.ldz.server;
import com.ldz.protocol.RpcDecoder;
import com.ldz.protocol.RpcEncoder;
import com.ldz.protocol.RpcRequest;
import com.ldz.protocol.RpcResponse;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @author LDZ
*/
public class NettyServer {
public void bind(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
//bossGroup就是parentGroup,是负责处理TCP/IP连接的
EventLoopGroup workerGroup = new NioEventLoopGroup();
//workerGroup就是childGroup,是负责处理Channel(通道)的I/O事件
ServerBootstrap sb = new ServerBootstrap();
sb.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
//初始化服务端可连接队列,指定了队列的大小128
.childOption(ChannelOption.SO_KEEPALIVE, true)
//保持长连接
.childHandler(new ChannelInitializer<SocketChannel>() {
// 绑定客户端连接时候触发操作
@Override
protected void initChannel(SocketChannel sh) throws Exception {
sh.pipeline()
.addLast(new RpcDecoder(RpcRequest.class)) //解码request
.addLast(new RpcEncoder(RpcResponse.class)) //编码response
.addLast(new ServerHandler()); //使用ServerHandler类来处理接收到的消息
}
});
//绑定监听端口,调用sync同步阻塞方法等待绑定操作完
ChannelFuture future = sb.bind(port).sync();
if (future.isSuccess()) {
System.out.println("服务端启动成功");
} else {
System.out.println("服务端启动失败");
future.cause().printStackTrace();
bossGroup.shutdownGracefully();
//关闭线程组
workerGroup.shutdownGracefully();
}
//成功绑定到端口之后,给channel增加一个 管道关闭的监听器并同步阻塞,直到channel关闭,线程才会往下执行,结束进程。
future.channel().closeFuture().sync();
}
}
package com.ldz.server;
import com.ldz.protocol.RpcRequest;
import com.ldz.protocol.RpcResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.UUID;
/**
* @author LDZ
*/
public class ServerHandler extends ChannelInboundHandlerAdapter{
//接受client发送的消息
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
RpcRequest request = (RpcRequest) msg;
System.out.println("接收到客户端信息:" + request.toString());
//返回的数据结构
RpcResponse response = new RpcResponse();
//自动生成主键
response.setId(UUID.randomUUID().toString());
response.setData("server响应结果");
response.setStatus(1);
ctx.writeAndFlush(response);
}
//通知处理器最后的channelRead()是当前批处理中的最后一条消息时调用
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("服务端接收数据完毕..");
ctx.flush();
}
//读操作时捕获到异常时调用
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
}
//客户端去和服务端连接成功时触发
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush("hello client");
}
}
package com.ldz.protocol;
import com.alibaba.fastjson.JSON;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
* @author LDZ
*/
public class RpcDecoder extends ByteToMessageDecoder {
//目标对象类型进行解码
private Class<?> target;
public RpcDecoder(Class target) {
this.target = target;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < 4) {
//不够长度丢弃
return;
}
in.markReaderIndex();
//标记一下当前的readIndex的位置
int dataLength = in.readInt();
// 读取传送过来的消息的长度。ByteBuf 的readInt()方法会让他的readIndex增加4
if (in.readableBytes() < dataLength) {
//读到的消息体长度如果小于我们传送过来的消息长度,则resetReaderIndex. 这个配合markReaderIndex使用的。把readIndex重置到mark的地方
in.resetReaderIndex();
return;
}
byte[] data = new byte[dataLength];
in.readBytes(data);
Object obj = JSON.parseObject(data, target);
//将byte数据转化为我们需要的对象
out.add(obj);
}
}
package com.ldz.protocol;
import com.alibaba.fastjson.JSON;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* @author LDZ
*/
public class RpcEncoder extends MessageToByteEncoder {
//目标对象类型进行编码
private Class<?> target;
public RpcEncoder(Class target) {
this.target = target;
}
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
if (target.isInstance(msg)) {
byte[] data = JSON.toJSONBytes(msg);
//使用fastJson将对象转换为byte
out.writeInt(data.length);
//先将消息长度写入,也就是消息头
out.writeBytes(data);
//消息体中包含我们要发送的数据
}
}
}
package com.ldz.protocol;
/**
* @author LDZ
*/
public class RpcRequest {
private String id;
private Object data;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
@Override
public String toString() {
return "RpcRequest{" + "id='" + id + '\'' + ", data=" + data + '}';
}
}
package com.ldz.protocol;
/**
* @author LDZ
*/
public class RpcResponse {
private String id;
private Object data;
// 0=success -1=fail
private int status;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
@Override
public String toString() {
return "RpcResponse{" + "id='" + id + '\'' + ", data=" + data + ", status=" + status + '}';
}
}
package com.ldz;
import com.ldz.protocol.RpcRequest;
import com.ldz.server.NettyServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.UUID;
/**
* @author LDZ
*/
public class ServerApplication {
public static void main(String[] args) throws Exception {
new NettyServer().bind(8080);
}
}
客户端,protocol文件夹下的四个文件与服务端一样,就不重复了。
package com.ldz.client;
import com.ldz.protocol.RpcResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* @author LDZ
*/
public class ClientHandler extends SimpleChannelInboundHandler<RpcResponse>{
//处理服务端返回的数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcResponse response) throws Exception {
System.out.println("接受到server响应数据: " + response.toString());
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
package com.ldz.client;
import com.ldz.protocol.RpcDecoder;
import com.ldz.protocol.RpcEncoder;
import com.ldz.protocol.RpcRequest;
import com.ldz.protocol.RpcResponse;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import sun.misc.Unsafe;
/**
* @author LDZ
*/
public class NettyClient {
private final String host;
private final int port;
private Channel channel;
//连接服务端的端口号地址和端口号
public NettyClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws Exception {
final EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
// 使用NioSocketChannel来作为连接用的channel类
.handler(new ChannelInitializer<SocketChannel>() {
// 绑定连接初始化器
@Override
public void initChannel(SocketChannel ch) {
System.out.println("正在连接中...");
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new RpcEncoder(RpcRequest.class)); //编码request
pipeline.addLast(new RpcDecoder(RpcResponse.class)); //解码response
pipeline.addLast(new ClientHandler()); //客户端处理类
}
});
//发起异步连接请求,绑定连接端口和host信息
final ChannelFuture future = b.connect(host, port).sync();
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture arg0) throws Exception {
if (future.isSuccess()) {
System.out.println("连接服务器成功");
} else {
System.out.println("连接服务器失败");
future.cause().printStackTrace();
group.shutdownGracefully(); //关闭线程组
}
}
});
this.channel = future.channel();
}
public Channel getChannel() {
return channel;
}
}
package com.ldz;
import com.ldz.client.NettyClient;
import com.ldz.protocol.RpcRequest;
import io.netty.channel.Channel;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.UUID;
/**
* @author LDZ
*/
public class ClientApplication {
public static void main(String[] args) throws Exception {
NettyClient client = new NettyClient("192.168.9.97", 8080);
//启动client服务
client.start();
Channel channel = client.getChannel();
//消息体
RpcRequest request = new RpcRequest();
request.setId(UUID.randomUUID().toString());
request.setData("client.message");
//channel对象可保存在map中,供其它地方发送消息
channel.writeAndFlush(request);
}
}
补充
Netty对外输出,统一使用ChannelOutboundBuffer封装,执行flush()方法,数据才会向socket写出