一、Java的IO((Input/Output))模型
传统IO和Java NIO最大的区别是传统的IO是面向流,NIO是面向Buffer Socket之间建立链接及通信的过程!实际上就是对TCP/IP连接与通信过程的抽象: 1.服务端Socket会bind到指定的端口上,Listen客户端的”插入” 2.客户端Socket会Connect到服务端 3.当服务端Accept到客户端连接后 4.就可以进行发送与接收消息了 5.通信完成后即可Close
1、BIO(阻塞)
1.socketServer的accept方法是阻塞的;
2.获得连接的顺序是和客户端请求到达服务器的先后顺序相关;
3.适用于一个线程管理一个通道的情况;因为其中的流数据的读取是阻塞的;
4.适合需要管理同时打开不太多的连接,这些连接会发送大量的数据。
客户端代码
//Bind,Connect
Socket client = new Socket("127.0.0.1",7777);
//读写
PrintWriter pw = new PrintWriter(client.getOutputStream());
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
pw.write(br.readLine());
//Close
pw.close();
br.close();
服务端代码
Socket socket;
//Bind,Listen
ServerSocket ss = new ServerSocket(7777);
while (true) {
//Accept
socket = ss.accept();
//一般新建一个线程执行读写
BufferedReader br = new BufferedReader(
new InputStreamReader(socket .getInputStream()));
System.out.println("you input is : " + br.readLine());
}
2、NIO(非阻塞)
1.基于事件驱动,当有连接请求,会将此连接注册到多路复用器上(selector);
2.在多路复用器上可以注册监听事件,比如监听accept、read;
3.通过监听,当真正有请求数据时,才来处理数据;
4.会不停的轮询是否有就绪的事件,所以处理顺序和连接请求先后顺序无关,与请求数据到来的先后顺序有关;
5.优势在于一个线程管理多个通道;但是数据的处理将会变得复杂;
6.适合需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据。
NIO客户端
//连接
//获取socket通道
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
//获得通道管理器
selector=Selector.open();
channel.connect(new InetSocketAddress(serverIp, port));
//为该通道注册SelectionKey.OP_CONNECT事件
channel.register(selector, SelectionKey.OP_CONNECT);
//监听
while(true){
//选择注册过的io操作的事件(第一次为SelectionKey.OP_CONNECT)
selector.select();
while(SelectionKey key : selector.selectedKeys()){
if(key.isConnectable()){
SocketChannel channel=(SocketChannel)key.channel();
if(channel.isConnectionPending()){
channel.finishConnect();//如果正在连接,则完成连接
}
channel.register(selector, SelectionKey.OP_READ);
}else if(key.isReadable()){ //有可读数据事件。
SocketChannel channel = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
byte[] data = buffer.array();
String message = new String(data);
System.out.println("recevie message from server:, size:" + buffer.position() + " msg: " + message);
}
}
}
服务端
//连接
//获取一个ServerSocket通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(port));
//获取通道管理器
selector = Selector.open();
//将通道管理器与通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
//监听
while(true){
//当有注册的事件到达时,方法返回,否则阻塞。
selector.select();
for(SelectionKey key : selector.selectedKeys()){
if(key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel)key.channel();
SocketChannel channel = server.accept();
channel.write(ByteBuffer.wrap(
new String("send message to client").getBytes()));
//在与客户端连接成功后,为客户端通道注册SelectionKey.OP_READ事件。
channel.register(selector, SelectionKey.OP_READ);
}else if(key.isReadable()){ //有可读数据事件
SocketChannel channel = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(10);
int read = channel.read(buffer);
byte[] data = buffer.array();
String message = new String(data);
System.out.println("receive message from client, size:" + buffer.position() + " msg: " + message);
}
}
}
二、Netty的IO
1、Netty特性
谁在用Netty
1. 框架,GRPC、Dubbo、Spring WebFlux、Spring Cloud Gateway 2. 大数据,Spark、Hadoop、Flink 3. 消息队列,RocketMQ、ActiveMQ 4. 搜索引擎,Elasticsearch 5. 分布式协调器,Zookeeper 6. 数据库,Cassandra、Neo4j 7. 负载均衡,Ribbon
2、Reactor 线程模型
Reactor 模式是 Dispatcher 模式,即 I/O 多了复用统一监听事件,收到事件后分发(Dispatch 给某进程)。就是将消息放到了一个队列中,通过异步线程池对其进行消费!
3、Netty 线程模型
Netty的线程模型是基于主从Reactor多线程做了改进。 MainReactor 负责客户端的连接请求,SubReactor 负责相应通道的 IO 读写请求。具体逻辑处理的任务写入队列,等 worker threads 进行处理。
4、Netty重要组件
一、Channel
Socket 连接,负责基本的 IO 操作,传入或者传出的数据载体,可以被打开或者关闭,连接或者断开连接。
提供了以下操作:
1.当前网络连接的通道的状态(例如是否打开?是否已连接?)
2.网络连接的配置参数 (例如接收缓冲区大小)
2.提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即返回,并且不保证在调用结束时所请求的 I/O 操作已完成。
调用立即返回一个 ChannelFuture 实例,通过注册监听器到 ChannelFuture 上,可以 I/O 操作成功、失败或取消时回调通知调用方。
4.支持关联 I/O 操作与对应的处理程序。
常用的 Channel 类型:
1.NioSocketChannel,异步的客户端 TCP Socket 连接。
2.NioServerSocketChannel,异步的服务器端 TCP Socket 连接。
3.NioDatagramChannel,异步的 UDP 连接。
4.NioSctpChannel,异步的客户端 Sctp 连接。
5.NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO。
二、EventLoop
netty针对网络编程时创建的多线程进行了封装和优化,构建了自己的线程模型。
EventLoop用于处理IO事件,多线程模型、并发:
1.一个EventLoopGroup包含一个或者多个EventLoop;
2.一个EventLoop在它的生命周期内只和一个Thread绑定;
3.所有有EventLoop处理的I/O事件都将在它专有的Thread上被处理;
4.一个Channel在它的生命周期内只注册于一个EventLoop;
5.一个EventLoop可能会被分配给一个货多个Channel;
NioEventLoopGroup
NioEventLoopGroup,主要管理 EventLoop 的生命周期,可理解为一个线程池,每个线程(NioEventLoop)负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程。
三、ChannelHandler和ChannelPipeline
ChannelHandler其实就是用于负责处理接收和发送数据的的业务逻辑,Netty中可以注册多个handler,以链式的方式进行处理,根据继承接口的不同,实现的顺序也不同。
1、ChannelInboundHandler:对接收的信息进行处理。一般用来执行解码、读取客户端数据、进行业务处理等。如ByteToMessageDecoder;
2、ChannelOutboundHandler:对发送的信息进行处理,一般用来进行编码、发送报文到客户端。如MessageToByteEncoder;
ChannelPipeline
保存 ChannelHandler 的 List,为ChannelHandler链提供了容器,用于处理 Channel 的入栈事件和出栈操作。
ChannelHandlerContext
保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象。
四、ByteBuf
强大高效的字节容器,提供了更加丰富的API用于字节的操作,同时保持了卓越的功能性和灵活性;传统的 I/O 是面向字节流或字符流的,以流式的方式顺序地从一个 Stream 中读取一个或多个字节, 不能随意改变读取指针的位置。NIO引入了 Channel 和 Buffer 的概念。
在 NIO 中,只能从 Channel 中读取数据到 Buffer 中或将数据从 Buffer 中写入到 Channel。基于 Buffer 操作不像传统 IO 的顺序操作,NIO 中可以随意地读取任意位置的数据。
5、代码
1.客户端
/**
* 定义Netty客户端
*/
public class NettyClient {
public static void main(String[] args) throws Exception {
//客户端需要一个事件循环组
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建客户端启动对象 Bootstrap
Bootstrap bootstrap = new Bootstrap();
//设置相关参数
bootstrap.group(group) // 设置线程组
.channel(NioSocketChannel.class) // 用NioSocketChannel作为客户端的通道实现
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//加入处理器
ch.pipeline().addLast(new NettyClientHandler());
}
});
System.out.println("netty 客户端启动......");
//启动客户端去连接服务器端
ChannelFuture cf = bootstrap.connect("127.0.0.1", 9000).sync();
//对通道关闭进行监听
cf.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
/**
* 定义 客户端NettyClientHandler
*/
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 当客户端连接服务器完成就会触发该方法
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
ByteBuf buf = Unpooled.copiedBuffer("HelloServer".getBytes(CharsetUtil.UTF_8));
ctx.writeAndFlush(buf);
}
//当通道有读取事件时会触发,即服务端发送数据给客户端
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg;
System.out.println("收到服务端的消息:" + buf.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
2.服务端
/**
* 定义Netty服务端
*/
public class NettyServer {
public static void main(String[] args) throws Exception {
// 创建两个线程组bossGroup和workerGroup, 含有的子线程NioEventLoop的个数默认为cpu核数的两倍
// bossGroup只是处理连接请求 ,真正的和客户端业务处理,会交给workerGroup完成
EventLoopGroup bossGroup = new NioEventLoopGroup(3);
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>() {//创建通道初始化对象,设置初始化参数,在 SocketChannel 建立起来之前执行
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//对workerGroup的SocketChannel设置处理器
ch.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println("netty 服务端启动......");
// 绑定一个端口并且同步, 生成了一个ChannelFuture异步对象,通过isDone()等方法可以判断异步事件的执行情况
// 启动服务器(并绑定端口),bind是异步操作,sync方法是等待异步操作执行完毕
ChannelFuture cf = bootstrap.bind(9000).sync();
// 给cf注册监听器,监听我们关心的事件
/*cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (cf.isSuccess()) {
System.out.println("监听端口9000成功");
} else {
System.out.println("监听端口9000失败");
}
}
});*/
// 等待服务端监听端口关闭,closeFuture是异步操作
// 通过sync方法同步等待通道关闭处理完毕,这里会阻塞等待通道关闭完成,内部调用的是Object的wait()方法
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
/**
* 定义服务端NettyServerHandler
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 当客户端连接服务器完成就会触发该方法
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("客户端连接通道建立完成");
}
/**
* 读取客户端发送的数据
* @param ctx 上下文对象, 含有通道channel,管道pipeline
* @param msg 就是客户端发送的数据
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
//Channel channel = ctx.channel();
//ChannelPipeline pipeline = ctx.pipeline(); //本质是一个双向链接, 出站入站
//将 msg 转成一个 ByteBuf,类似NIO 的 ByteBuffer
ByteBuf buf = (ByteBuf) msg;
System.out.println("收到客户端的消息:" + buf.toString(CharsetUtil.UTF_8));
}
/**
* 数据读取完毕处理方法
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ByteBuf buf = Unpooled.copiedBuffer("HelloClient".getBytes(CharsetUtil.UTF_8));
ctx.writeAndFlush(buf);
}
/**
* 处理异常, 一般是需要关闭通道
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
}
}