NIO的介绍

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 ==》是否存在写事件感兴趣

==》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值