NIO是什么?
是同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。连接数目多且连接比较短(轻操作)的架构,比如聊天服务器。
JDK1.4引入的一种新型IO。对BIO的一种改进,基于Reactor模型。一个Socket连接其实只有在一小部分情况下才会发生数据 传输IO操作,大部分时间都是空闲的,但是还是占着线程。NIO作出的改进是在连接到服务端的众多Socket中,只有需要进行IO操作的才能获取服务端的处理线程进行IO。客户端的Socket在连接到服务端时就会在事件分离器注册一个IO请求事件和IO 事件处理器。在该系统的连接发生IO请求时,IO事件处理器就会启动一个线程来处理这个IO请求,不断尝试获取系统的IO使用权限,一旦成功呢,则通知这个Socket进行IO数据传输
NIO 以C/S模型为例介绍TCP连接:
Server服务端:
1、创建ServerSocketChannel实例,并绑定端口
2、创建Selector实例,将ServerSocketChannel实例设置为非阻塞,并注册到选择器上,关注ACCEPT事件
3、选择器进行选择(Selector.select()),监听关注事件
4、当有事件发生,遍历选择器上感兴趣事件集合
5、当是ACCEPT事件时,获取ServerSocketChannel实例,并调用accept方法,获取新用户连接实例SocketChannel
6、将SocketChannel实例设置为非阻塞,并关注READ事件,继续轮序监听事件发生
7、当有READ事件发生,获取SocketChannel实例,进行读操作(read)
8、关闭相关的通道及选择器
Client客户端:
1、创建SocketChanel实例,并设置为非阻塞
2、创建选择器Selector实例
3、将ocketChanel注册到选择器并关注CONNECT事件
4、选择器轮序监听事件是否发生
5、当有事件发生CONNECT事件,完成连接并发送消息
6、将SocketChanel注册到选择器关注READ事件
7、选择器轮序监听事件是否发生
8、当READ事件发生,进行相应的读操作
9、关闭相关的通道及选择器
NIO的实现
服务端:
public class NIOServer {
/**
* 主线程选择器
*/
public static Selector selector = null;
/**
* 一个ServerSocketChannel实例
*/
public static ServerSocketChannel channel;
/**
* 线程池
*/
static ExecutorService executorService = Executors.newFixedThreadPool(4);
public static void main(String[] args) throws IOException{
/**
* 端口号
*/
int port = 8989;
/**
* 初始化
*/
channel = ServerSocketChannel.open();
channel.bind(new InetSocketAddress(port));
selector = Selector.open();
channel.register(selector,SelectionKey.OP_ACCEPT);
}
public void start() throws IOException{
while(selector.select() > 0){
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
if(key.isValid() && key.isAcceptable()) {
SocketChannel socketchannel = (SocketChannel)key.channel();
socketchannel = channel.accept();
SocketAddress address = socketchannel.getRemoteAddress();
System.out.println("客户端:"+address +" 已上线");
Task task = new Task(socketchannel);
executorService.execute(task);
}
}
}
}
}
class Task implements Runnable{
/**
* 子线程选择器
*/
private Selector selector;
/**
* SocketChannel实例
*/
private SocketChannel channel;
private ByteBuffer buffer;
public Selector getSelector() {
return selector;
}
public void setSelector(Selector selector) {
this.selector = selector;
}
public Task(SocketChannel channel) {
// TODO Auto-generated constructor stub
this.channel = channel;
try {
channel.configureBlocking(false);
selector = Selector.open();
buffer = ByteBuffer.allocate(1024);
channel.register(selector,SelectionKey.OP_READ);
SocketAddress address = channel.getLocalAddress();
System.out.println("当前线程"+Thread.currentThread().getName()+"处理"+address+"的请求");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
while(selector.select() > 0){
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户端:
public class NioClient {
public static void main(String[] args) throws Exception{
for(int i = 0;i < 2;i++) {
new Thread() {
public void run(){
/**
* 1创建SocketChannel 实例
*/
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open();
/**
* 连接服务器
*/
socketChannel.connect(new InetSocketAddress("127.0.0.1",8989));
/**
* 写数据
*/
String msg = "我是客户端!!!";
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(msg.getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
socketChannel.shutdownOutput();
/**
* 读数据
*/
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = 0;
while(true) {
byteBuffer.clear();
len = socketChannel.read(byteBuffer);
if(len == -1) {
break;
}
byteBuffer.flip();
while(byteBuffer.hasRemaining()){
bos.write(byteBuffer.get());
}
System.out.println("服务端说:"+new String(bos.toByteArray()));
socketChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
}
NIO核心部分
1.Channel 通道
channel就是通道,类似于BIO中的stream(流)。
1.2channel和流的区别:
channel既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的; 通道可以异步地读写;
通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入.
1.2.Channel的主要实现类:
1,FileChannel:从文件中读写数据。FileChannel无法设置为非阻塞模式
2,DatagramChannel:能通过UDP读写网络中的数据
3,SocketChannel:能通过TCP读写网络中的数据
4,ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器一样对于每一个新进来的连接都会创建一个SocketChannel
2.Buffer
Buffer是一个缓冲区。它与通道紧密联系。通道是I/O传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。数据从Channel读到Buffer中,也可以从Buffer写到Channel中。所有数据的读写都是通过Buffer进行的。
源码中几个核心的属性:
容量(Capacity):缓冲区能够容纳的数据元素最大数量。这一容量在缓冲区创建时被设定,并且不能被改变。
上界(Limit):缓冲区的第一个不能别读或写的元素。
位置(position):下一个要被读或写的元素的索引。位置会自动由get()、put()方法更新。
标记(Mark):一个备忘位置。调用mark()来设定 mark=postion 调用reset()来设定postion=mark
3.Selector(选择器)
3.1介绍
Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。管理着一个被注册的通道集合的信息和他们的就绪状态。通过是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。
3.2选择器的使用:
1、创建选择器的实例: Selector.open()
2、注册关注的事件 SocketChanel.register()主要是两个参数:选择器实例,关注的事件
3、选择过程 selector.select()
每一个Selector对象维护三个键的集合:
1,已注册的键的集合(Registered key set)
2,已选择的键的集合(Selected key set)
3,已取消的键的集合(Cancelled key set)
打开selector,将channel注册到选择器上。
SelectionKey:选择键封装了特定的通道与特定的选择器的注册关系。选择键对象被SelectableChannel.register()返回一个表示这种病注册关系的标记。选择键包含了两个 比特集(以整数的形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作。
一个selectionKey对象包含两个以整数形式进行编码的比特掩码:一个用于指示哪些通道/选择器组合体所关心的操作(interest集合),另一个表示通道准备好要执行的操作(ready集合)。当前的interest集合可以通过调用interestOps()方法来获取。最初,这应该是通道被注册时传进来的值。
3.3选择过程:
select():会阻塞住,直到有事件发生才会返回,返回值大于0
select(int timeout):在一定时间timeout内阻塞,到达时间还未有事件发生则直接返回,返回可能为0
selectNow():立即返回
SelectionKey :感兴趣事件 ->Seletor底层提供了三个集合,一个是注册事件集合,一个是关注事件集合,一个是取消事件集合
SelectionKey对应的是感兴趣事件的集合
int OP_READ = 1 << 0; 表示读事件
int OP_WRITE = 1 << 2; 表示的写事件
int OP_CONNECT = 1 << 3; 表示的连接事件
int OP_ACCEPT = 1 << 4; 表示的是接收时间
关注的事件底层bit位来表示
2 =》 0010 写事件
3 =》 0011 读和写事件
5 =》 0101 连接和读事件
*****一个通道下可以有多个关注事件(读、写、连接、接收)
提供的方法:
channel() :返回的是当前事件所对应的通道
selector():返回的是当前关注事件所存在的选择器实例
isValid():判断当前的感兴趣事件是否有效
cancel():取消关注的事件
isWritable()
int interestOps():感兴趣事件集合
interestOps()& OP_WRITE ==》是否存在写事件感兴趣
==》