Java NIO(三)阻塞与非阻塞

   阻塞与非阻塞

阻塞   传统的 IO 流都是阻塞式的。也就是说,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行 IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降。

非阻塞  Java NIO 是非阻塞模式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。因此,NIO 可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。

1. 通道(Channel):负责连接
        
       java.nio.channels.Channel 接口:
            |--SelectableChannel
                |--SocketChannel
                |--ServerSocketChannel
                |--DatagramChannel


                |--Pipe.SinkChannel
                |--Pipe.SourceChannel


2. 缓冲区(Buffer):负责数据的存取

3. 选择器(Selector):是 SelectableChannel 的多路复用器。用于监控 SelectableChannel 的 IO 状况

阻塞式

    @Test
	public void client() throws Exception{
		//创建通道
		FileChannel open = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.READ);
		//分配缓冲区
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		//创建socket通道
		SocketChannel open2 = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9988));
		//读取本地文件,并发送到服务端
		while(open.read(buffer)!=-1){
			buffer.flip();
		open2.write(buffer);
		buffer.clear();
		}
		open2.shutdownOutput();
		//获取服务端反馈
		int len=0;
		while((len=open2.read(buffer))!=-1){
			buffer.flip();
			System.out.println(new String (buffer.array(),0,len));
			buffer.clear();
		}
		open.close();
		open2.close();
	};
	
	@Test
	public void server() throws IOException{
		// 获取通道
		FileChannel open = FileChannel.open(Paths.get("11.jpg"), StandardOpenOption.WRITE,
				StandardOpenOption.CREATE);
		ServerSocketChannel open2 = ServerSocketChannel.open();
		//分配指定大小的缓冲区
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		//绑定连接
		open2.bind(new InetSocketAddress(9988));
		//获取客户端连接的通道
		SocketChannel accept = open2.accept();
		//接收客户端的数据,并保存到本地
		while(accept.read(byteBuffer)!=-1){
			byteBuffer.flip();
			open.write(byteBuffer);
			byteBuffer.clear();
		}
		
 
		//发送反馈给客户端
		byteBuffer.put("图片收到了".getBytes());
		byteBuffer.flip();
		accept.write(byteBuffer);
		accept.close();
		open.close();
		open2.close();
	}

非阻塞式

@Test
	public void client() throws Exception{
		//1. 获取通道
		SocketChannel clientSoc=SocketChannel.open(new InetSocketAddress("127.0.0.1", 9988));
		//2. 切换非阻塞模式
		clientSoc.configureBlocking(false);
		//3. 分配指定大小的缓冲区
		ByteBuffer allocate = ByteBuffer.allocate(1024);
		//4. 发送数据给服务端
		Scanner sc=new Scanner(System.in);
		while(sc.hasNext()){
			String next = sc.next();
			allocate.put((LocalDate.now()+"--客户端说"+"\n"+next).getBytes());
			allocate.flip();
			clientSoc.write(allocate);
			allocate.clear();
		}
		sc.close();
		//5. 关闭通道
		clientSoc.close();
	}
	
	
	@Test
	public void Server() throws Exception{
		//1. 获取通道
		ServerSocketChannel serSoc=ServerSocketChannel.open();
		//2. 切换非阻塞模式
		serSoc.configureBlocking(false);
		//3. 绑定连接
		serSoc.bind(new InetSocketAddress(9988));
		//4. 获取选择器
		Selector selector = Selector.open();
		//5. 将通道注册到选择器上, 并且指定“监听接收事件”
		serSoc.register(selector,  SelectionKey.OP_ACCEPT);
		//6. 轮询式的获取选择器上已经“准备就绪”的事件
		while(selector.select()>0){
			//7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
			Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
			while(iterator.hasNext()){
				//8. 获取准备“就绪”的是事件
				SelectionKey sk = iterator.next();
				//9. 判断具体是什么事件准备就绪
				if(sk.isAcceptable()){
					//10. 若“接收就绪”,获取客户端连接
					SocketChannel sChannel = serSoc.accept();
					//11. 切换非阻塞模式
					sChannel.configureBlocking(false);
					//12. 将该通道注册到选择器上
					sChannel.register(selector, SelectionKey.OP_READ);
				}else if(sk.isReadable()){
					//13. 获取当前选择器上“读就绪”状态的通道
					SocketChannel sChannel = (SocketChannel) sk.channel();
					//14. 读取数据
					ByteBuffer buf = ByteBuffer.allocate(1024);
					int len = 0;
					while((len = sChannel.read(buf)) > 0 ){
						buf.flip();
						System.out.println(new String(buf.array(), 0, len));
						buf.clear();
					}
				}
			}
			//15. 取消选择键 SelectionKey
			iterator.remove();
		}
		
	}

注册Selector

serSoc.register(selector,  SelectionKey.OP_ACCEPT);

当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器

对通道的监听事件,需要通过第二个参数 ops 指定。
可以监听的事件类型(用 可使用 SelectionKey  的四个常量 表示):
    读 : SelectionKey.OP_READ (1)
    写 : SelectionKey.OP_WRITE (4)
    连接 : SelectionKey.OP_CONNECT (8)
    接收 : SelectionKey.OP_ACCEPT (16)
  若注册时不止监听一个事件,则可以使用“位或”操作符连接。

int interestKey=SelectionKey.OP_ACCEPT|SelectionKey.OP_WRITE;

SelectionKey:表示 SelectableChannel 和 Selector 之间的注册关系。每次向
选择器注册通道时就会选择一个事件(选择键)。选择键包含两个表示为整
数值的操作集。操作集的每一位都表示该键的通道所支持的一类可选择操作。

方 法 描 述

int interestOps()                                 获取感兴趣事件集合
int readyOps()                                    获取通道已经准备就绪的操作的集合
SelectableChannel channel()             获取注册通道
Selector selector()                              返回选择器
boolean isReadable()                         检测 Channal 中读事件是否就绪
boolean isWritable()                           检测 Channal 中写事件是否就绪
boolean isConnectable()                    检测 Channel 中连接是否就绪
boolean isAcceptable()                       检测 Channel 中接收是否就绪
选择 器(Selector )的应用

Selector  的常用方法描述
Set<SelectionKey> keys()     所有的 SelectionKey 集合。代表注册在该Selector上的Channel
selectedKeys()                        被选择的 SelectionKey 集合。返回此Selector的已选择键集
int select()                           监控所有注册的Channel,当它们中间有需要处理的 IO 操作时,该方法返回,并将对应得的 SelectionKey 加入被选择的SelectionKey 集合中,该方法返回这些 Channel 的数量。
int select(long timeout)         可以设置超时时长的 select() 操作
int selectNow()                      执行一个立即返回的 select() 操作,该方法不会阻塞线程
Selector wakeup()                  使一个还未返回的 select() 方法立即返回
void close()                            关闭该选择器

UDP的发送

    @Test
	public void send() throws IOException{
		DatagramChannel dc = DatagramChannel.open();
		dc.configureBlocking(false);
		ByteBuffer buf = ByteBuffer.allocate(1024);
		Scanner scan = new Scanner(System.in);
		while(scan.hasNext()){
			String str = scan.next();
			buf.put((new Date().toString() + ":\n" + str).getBytes());
			buf.flip();
			dc.send(buf, new InetSocketAddress("127.0.0.1", 9898));
			buf.clear();
		}
		
		dc.close();
	}
	
	@Test
	public void receive() throws IOException{
		DatagramChannel dc = DatagramChannel.open();
		dc.configureBlocking(false);
		dc.bind(new InetSocketAddress(9898));
		Selector selector = Selector.open();
		dc.register(selector, SelectionKey.OP_READ);
		while(selector.select() > 0){
			Iterator<SelectionKey> it = selector.selectedKeys().iterator();
			while(it.hasNext()){
				SelectionKey sk = it.next();
				if(sk.isReadable()){
					ByteBuffer buf = ByteBuffer.allocate(1024);
					dc.receive(buf);
					buf.flip();
					System.out.println(new String(buf.array(), 0, buf.limit()));
					buf.clear();
				}
			}
			it.remove();
		}
	}

单向管道

    @Test
	public void test01() throws IOException{
		//1. 获取管道
		Pipe pipe = Pipe.open();
		
		//2. 将缓冲区中的数据写入管道
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		Pipe.SinkChannel sinkChannel = pipe.sink();
		buf.put("通过单向管道发送数据".getBytes());
		buf.flip();
		sinkChannel.write(buf);
		
		//3. 读取缓冲区中的数据
		Pipe.SourceChannel sourceChannel = pipe.source();
		buf.flip();
		int len = sourceChannel.read(buf);
		System.out.println(new String(buf.array(), 0, len));
		
		sourceChannel.close();
		sinkChannel.close();
	}

原文地址: https://blog.csdn.net/yjaspire/article/details/80518517

猜你喜欢

转载自blog.csdn.net/baidu_36327010/article/details/85033144