`## JAVA NIO
这里有三件宝贝:buffer,selector,channel
先来说说buffer
简介:在传统的面向流操作时,所有的数据都是直接传入stream对象中进行操作的,而面向缓冲区的nio是对缓冲区操作的,无论读取还是写入,缓冲区就是buffer,所有的基本类型都有对应的buffer类,所有缓冲buffer类都继承buffer
原理:buffer就是一个数组实现的,有三个变量对这个数组的读入和写入控制———— position,limit,capacity。position默认是0,limit默认和capacity一样大,capacity就是这个数组底层分配的初始大小。
系统从通道中读取一些数据到buffer中去,position就会++,直到读入的数据停下来
当系统开始从buffer里取数据时,必须先对buffer进行flip 操作,这个时候posijtion就会变为0,指向数组的起点,然后limit会变为position之前的位置,限制get() 时只能到之前写入的最远的position ,最后使用clear()方法三个变量会初始化为初值。
关于buffer的其余种类:缓冲区分片,只读缓冲区,直接缓冲区,内存映射
再来说说selector
传统的client/server 会使用tpr模式,thread per request 客户端来一个请求,服务端就来一个线程负责和客户端通信,这样的弊端就是线程数量随着用户的访问量极速上升,然后大家为了避免这种现象的发生,搞一个线程池,开200个线程去轮流处理,降低开销,但是如果这时候有第201个用户进来下载什么东西的话就会连接失败。
针对之上提到的问题,现在使用reactor 模式,io不会被阻塞而注册感兴趣的事件, 当可读数据到达,新的套接字连接时,发生特定的事件时,系统再通知我们。selector就是注册各种io事件的地方,当事件发生的时候,selector会告诉我们
原理:当有读写等任何注册的事件发生时,可以从selector中获取相应的selectionKey,同时从selectionKey中可以找到相对应的事件和该事件所对应的selectableChannel ,以获得客户端发送来的数据
步骤:
(1)给selector对象注册感兴趣的事件
(2)从selector中获取感兴趣的事件
(3)根据不同的事件进行相应的处理。
代码实例:
server端
package com.zdj.nettytest.core;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class Server {
public static void main(String[] args) throws IOException {
//创建ServerSocketChannel
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//得到一个selector对象
Selector selector = Selector.open();
//绑定端口
InetSocketAddress inetSocketAddress=new InetSocketAddress(78);
serverSocketChannel.socket().bind(inetSocketAddress);
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//把serverSokcetChannel注册到selector 关心事件为OP_ACCPET,也就是连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环等待连接
while(true){
//先等待一秒钟 如果没有连接事件发生,则返回
if(selector.select(1000)==0){
System.out.println("等待了1秒 无事发生");
continue;
}
//如果返回大于0 则获得有事件发生的selectionKey
//通过selectionKey反向获取通道
Set<SelectionKey> selectionKeys=selector.selectedKeys();
//使用迭代器遍历Set集合
Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
while(selectionKeyIterator.hasNext()){
SelectionKey selectionKey=selectionKeyIterator.next();
//根据selectionKey的通道发生的事件进行处理
if(selectionKey.isAcceptable()){
//处理的是OP_ACCPET,也就是连接事件
//为该客户生成一个通道 socketChannel
SocketChannel socketChannel=serverSocketChannel.accept();
System.out.println("客户端连接成功");
//将该通道设置为非阻塞状态 否则会报java.nio.channels.IllegalBlockingModeException异常
socketChannel.configureBlocking(false);
//将当前通道注册到selector中,关注事件为OP_READ,并绑定一个buffer
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
else if(selectionKey.isReadable()){
//如果selectionKey是可读的
//反向获取通道
SocketChannel channel =(SocketChannel)selectionKey.channel();
//获取该通道绑定的buffer
ByteBuffer attachment = (ByteBuffer)selectionKey.attachment();
channel.read(attachment);
System.out.println("来自客户端的信息是"+new String(attachment.array()));
}
//从集合中删除key 防止重复操作
selectionKeyIterator.remove();
}
}
}
}
client 端
package com.zdj.nettytest.core;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class Server {
public static void main(String[] args) throws IOException {
//创建ServerSocketChannel
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//得到一个selector对象
Selector selector = Selector.open();
//绑定端口
InetSocketAddress inetSocketAddress=new InetSocketAddress(78);
serverSocketChannel.socket().bind(inetSocketAddress);
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//把serverSokcetChannel注册到selector 关心事件为OP_ACCPET,也就是连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环等待连接
while(true){
//先等待一秒钟 如果没有连接事件发生,则返回
if(selector.select(1000)==0){
System.out.println("等待了1秒 无事发生");
continue;
}
//如果返回大于0 则获得有事件发生的selectionKey
//通过selectionKey反向获取通道
Set<SelectionKey> selectionKeys=selector.selectedKeys();
//使用迭代器遍历Set集合
Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
while(selectionKeyIterator.hasNext()){
SelectionKey selectionKey=selectionKeyIterator.next();
//根据selectionKey的通道发生的事件进行处理
if(selectionKey.isAcceptable()){
//处理的是OP_ACCPET,也就是连接事件
//为该客户生成一个通道 socketChannel
SocketChannel socketChannel=serverSocketChannel.accept();
System.out.println("客户端连接成功");
//将该通道设置为非阻塞状态 否则会报java.nio.channels.IllegalBlockingModeException异常
socketChannel.configureBlocking(false);
//将当前通道注册到selector中,关注事件为OP_READ,并绑定一个buffer
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
else if(selectionKey.isReadable()){
//如果selectionKey是可读的
//反向获取通道
SocketChannel channel =(SocketChannel)selectionKey.channel();
//获取该通道绑定的buffer
ByteBuffer attachment = (ByteBuffer)selectionKey.attachment();
channel.read(attachment);
System.out.println("来自客户端的信息是"+new String(attachment.array()));
}
//从集合中删除key 防止重复操作
selectionKeyIterator.remove();
}
}
}
}
效果:
Channel
NIO读取数据:
(1) 从FileInputStream 获取Channel
(2) 创建buffer
(3) 讲数据从Channel 读取到Buffer中
代码实例:
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("/Users/zhangdongjie/Desktop/1.txt");
FileChannel fc = fileInputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
fc.read(buffer);
buffer.flip();
while(buffer.remaining() > 0) {
byte b = buffer.get();
System.out.print((char)(b));
}
fileInputStream.close();
}
NIO写入数据
(1)从FileInputStream获取Channel
(2)创建Buffer
(3)将数据从Channel写入Buffer
public static void main(String[] args) throws IOException {
FileOutputStream fileInputStream = new FileOutputStream("/Users/zhangdongjie/Desktop/1.txt");
FileChannel fc = fileInputStream.getChannel();
ByteBuffer buffer = ByteBuffer.wrap("hello world".getBytes(StandardCharsets.UTF_8));
fc.write(buffer);
fileInputStream.close();
}
Netty 样例
服务端
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public void start() throws InterruptedException {
final EchoServerHandler serverHandler = new EchoServerHandler();
//1、创建EventLoopGroup以进行事件的处理,如接受新连接以及读/写数据
EventLoopGroup group = new NioEventLoopGroup();
try {
//2、创建ServerBootstrap,引导和绑定服务器
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group, group)
//3、指定所使用的NIO传输Channel
.channel(NioServerSocketChannel.class)
//4、使用指定的端口设置套接字地址
.localAddress(new InetSocketAddress(port))
//5、添加一个 EchoServerHandler 到子 Channel的 ChannelPipeline
//当一个新的连接被接受时,一个新的子Channel将会被创建,而 ChannelInitializer 将会把一个你的EchoServerHandler 的实例添加到该 Channel 的 ChannelPipeline 中
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(serverHandler);
}
});
//6、异步地绑定服务器,调用sync()方法阻塞等待直到绑定完成
ChannelFuture channelFuture = bootstrap.bind().sync();
System.out.println(EchoServer.class.getName() + "started and listening for connections on" + channelFuture.channel().localAddress());
//7、获取 Channel 的 CloseFuture,并且阻塞当前线程直到它完成
channelFuture.channel().closeFuture().sync();
} finally {
//8、关闭 EventLoopGroup 释放所有的资源
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws InterruptedException {
new EchoServer(9999).start();
}
}
@ChannelHandler.Sharable //标识一个Channel-Handler 可以被多个Channel安全的共享
public class EchoServerHandler extends ChannelHandlerAdapter {
/**
* 对于每个传入的消息都要调用
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server received:" + in.toString(CharsetUtil.UTF_8));
//将接收到的消息写给发送者,而不冲刷出站消息
//ChannelHandlerContext 发送消息。导致消息向下一个ChannelHandler流动
//Channel 发送消息将会导致消息从 ChannelPipeline的尾端开始流动
ctx.write(in);
}
/**
* 通知 ChannelHandlerAdapter 最后一次对channel-Read()的调用是当前批量读取中的最后一条消息
*
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//暂存于ChannelOutboundBuffer中的消息,在下一次调用flush()或者writeAndFlush()方法时将会尝试写出到套接字
//将这份暂存消息冲刷到远程节点,并且关闭该Channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
/**
* 在读取操作期间,有异常抛出时会调用
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建Bootstrap
Bootstrap bootstrap = new Bootstrap();
//指定 EventLoopGroup 以处理客户端事件;适应于NIO的实现
bootstrap.group(group)
//适用于NIO传输的Channel类型
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
//在创建Channel时,向ChannelPipeline中添加一个EchoClientHandler实例
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoClientHandler());
}
});
//连接到远程节点,阻塞等待直到连接完成
ChannelFuture channelFuture = bootstrap.connect().sync();
//阻塞,直到Channel 关闭
channelFuture.channel().closeFuture().sync();
} finally {
//关闭线程池并且释放所有的资源
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws InterruptedException {
new EchoClient("127.0.0.1", 9999).start();
System.out.println("------------------------------------");
new EchoClient("127.0.0.1", 9999).start();
System.out.println("------------------------------------");
new EchoClient("127.0.0.1", 9999).start();
}
}
@ChannelHandler.Sharable //标记该类的实例可以被多个Channel共享
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
/**
* 当从服务器接收到一条消息时被调用
*
* @param ctx
* @param msg ByteBuf (Netty 的字节容器) 作为一个面向流的协议,TCP 保证了字节数组将会按照服务器发送它们的顺序接收
* @throws Exception
*/
@Override
protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("Client" + ctx.channel().remoteAddress() + "connected");
System.out.println(msg.toString(CharsetUtil.UTF_8));
}
/**
* 在到服务器的连接已经建立之后将被调用
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rock!", CharsetUtil.UTF_8));
}
/**
* 在处理过程中引发异常时被调用
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
Netty组件和设计
Channel—Socket;
EventLoop—控制流、多线程处理、并发;
ChannelFuture—异步通知。
Channel
对底层网络io操作提供封装,基本构造是class socket 降低了socket直接使用的复杂度
EventLoop
用于处理连接生命周期中所发生的事件,
目前,图3-1
在高层次上说明了Channel、EventLoop、Thread 以及EventLoopGroup 之间的关系
ChannelFuture
正如我们已经解释过的那样,Netty 中所有的I/O 操作都是异步的。因为一个操作可能不会
立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此,Netty 提供了
ChannelFuture 接口,其addListener()方法注册了一个ChannelFutureListener,以
便在某个操作完成时(无论是否成功)得到通知。
ChannelHandler ChannelPipeline
ChannelHandler 是处理所有进站出站数据的处理逻辑的容器
netty有许多不同类型的ChannelHandler,它们各自的功能主要取决于
它们的超类。Netty 以适配器类的形式提供了大量默认的ChannelHandler 实现,其旨在简化
应用程序处理逻辑的开发过程。你已经看到了,ChannelPipeline中的每个ChannelHandler
将负责把事件转发到链中的下一个ChannelHandler。这些适配器类(及它们的子类)将自动
执行这个操作,所以你可以只重写那些你想要特殊处理的方法和事件。
ChannelPpieline
ChannelPipeline是将所有的channelhandeler穿起来(使得事件流经ChannelPipeline 是ChannelHandler 的工作,它们是在应用程序的初始化或者引导阶段被安装的。这些对象接收事件、执行它们所实现的处理逻辑,并将数据传递给
链中的下一个ChannelHandler。它们的执行顺序是由它们被添加的顺序所决定的。实际上,
被我们称为ChannelPipeline 的是这些ChannelHandler 的编排顺序。)
ChannelPipeline 提供了ChannelHandler 链的容器,并定义了用于在该链上传播入站
和出站事件流的API。当Channel 被创建时,它会被自动地分配到它专属的ChannelPipeline。
ChannelHandler 安装到ChannelPipeline 中的过程如下所示:
- 一个ChannelInitializer的实现被注册到了ServerBootstrap中;
- 当ChannelInitializer.initChannel()方法被调用时,ChannelInitializer
将在ChannelPipeline 中安装一组自定义的ChannelHandler;- ChannelInitializer 将它自己从ChannelPipeline 中移除。