`
gengu
  • 浏览: 86743 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

java NIO学习笔记 非阻塞IO编程

阅读更多

        我们都知道TCP是面向连接的传输层协议,一个socket必定会有绑定一个连接,在普通的BIO(阻塞式IO)中,需要有三次握手,然后一般的socket编程就是这样的形式。

Socket服务器端流程如下:加载套接字->创建监听的套接字->绑定套接字->监听套接字->处理客户端相关请求。

Socket客户端同样需要先加载套接字,然后创建套接字,不过之后不用绑定和监听了,而是直接连接服务器,发送相关请求。

 

       他们一直就占用这个连接,如果有信息发送,那么就响应,否则就一直阻塞着。如果有多连接,那么就要使用多线程,一个线程处理一个连接,在连接还少的情况下,是允许的,但如果同时处理的连接过多比如说1000,那么在win平台上就会遇到瓶颈了如果2000,那么在linux上就遇到瓶颈了,因为在不同的平台上每一个进程能够创建的线程数是有限度的,并且过多的线程必将会引起系统对线程调度的效率问题,再怎么也要保证线程优先队列,阻塞队列;假设一千个线程,一个线程最少一兆的栈大小,对内存也是一个很大的消耗。

       总之阻塞式的IO是:一连接<一一一>一线程

 

       然后出现了NIO,在java1.4引入了java.nio包,java new I/O。引入了操作系统中常用的缓冲区和通道等概念。

 

       缓冲区: 在操作系统中缓冲区是为了解决CPU的计算速度和外设输入输出速度不匹配的问题,因为外设太慢了,如果没有缓冲区,那么CPU在外设输入的时候就要一直等着,就会造成CPU处理效率的低下,引入了缓冲之后,外设直接把数据放到缓冲中,当数据传输完成之后,给CPU一个中断信号,通知CPU:“我的数据传完了,你自己从缓冲里面去取吧”。如果是输出也是一样的道理。

       通道: 那么通道用来做什么呢?其实从他的名字就可以看出,它就是一条通道,您想传递出去的数据被放置在缓冲区中,然后缓冲区中怎么从哪里传输出去呢?或者外设怎么把数据传输到缓冲中呢?这里就要用到通道。它可以进一步的减少CPU的干预,同时更有效率的提高整个系统的资源利用率,例如当CPU要完成一组相关的读操作时,只需要向I/O通道发送一条指令,以给出其要执行的通道程序的首地址和要访问的设备,通道执行通道程序便可以完成CPU指定的I/O任务。

      选择器: 另外一项创新是选择器,当我们使用通道的时候也许通道没有准备好,或者有了新的请求过来,或者线程遇到了阻塞,而选择器恰恰可以帮助CPU了解到这些信息,但前提是将这个通道注册到了这个选择器。

 

 

 

下面一个例子是我看过的一个讲述的很贴切的例子:

一辆从 A 开往 B 的公共汽车上,路上有很多点可能会有人下车。司机不知道哪些点会有哪些人会下车,对于需要下车的人,如何处理更好?

1. 司机过程中定时询问每个乘客是否到达目的地,若有人说到了,那么司机停车,乘客下车。 ( 类似阻塞式 )

2. 每个人告诉售票员自己的目的地,然后睡觉,司机只和售票员交互,到了某个点由售票员通知乘客下车。 ( 类似非阻塞 )

很显然,每个人要到达某个目的地可以认为是一个线程,司机可以认为是 CPU 。在阻塞式里面,每个线程需要不断的轮询,上下文切换,以达到找到目的地的结果。而在非阻塞方式里,每个乘客 ( 线程 ) 都在睡觉 ( 休眠 ) ,只在真正外部环境准备好了才唤醒,这样的唤醒肯定不会阻塞。
 

在非阻塞式IO中实现的是:一请求<一一一>一线程

 

     下面这个例子实现了一个线程监听两个ServerSocket,只有等到请求的时候才会有处理。

Server

package com.gengu;

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.nio.channels.spi.SelectorProvider;
import java.util.Iterator;

/** 
 * TCP/IP的NIO非阻塞方式
 * 服务器端
 * */

public class Server implements Runnable{

	//第一个端口
	private Integer port1 = 8099;
	//第二个端口
	private Integer port2 = 9099;
	//第一个服务器通道 服务A
	private ServerSocketChannel serversocket1 ;
	//第二个服务器通道 服务B
	private ServerSocketChannel serversocket2 ;
	//连接1
	private SocketChannel clientchannel1 ;
	//连接2
	private SocketChannel clientchannel2 ;
	//缓冲区
	private ByteBuffer buf = ByteBuffer.allocate(512);

        public Server(){
		init();
	}
	
	//选择器,主要用来监控各个通道的事件
	private Selector selector ;
	
	/**
	 * 这个method的作用1:是初始化选择器
	 * 2:打开两个通道
	 * 3:给通道上绑定一个socket
	 * 4:将选择器注册到通道上
	 * */
	public  void init(){
		try{
			//创建选择器
			this.selector = SelectorProvider.provider().openSelector();
			//打开第一个服务器通道
			this.serversocket1 = ServerSocketChannel.open();
			//告诉程序现在不是阻塞方式的
			this.serversocket1.configureBlocking(false);
			//获取现在与该通道关联的套接字
			this.serversocket1.socket().bind(new InetSocketAddress("localhost",this.port1));
			//将选择器注册到通道上,返回一个选择键
			//OP_ACCEPT用于套接字接受操作的操作集位
			this.serversocket1.register(this.selector, SelectionKey.OP_ACCEPT);
			
			//然后初始化第二个服务端
			this.serversocket2 = ServerSocketChannel.open();
			this.serversocket2.configureBlocking(false);
			this.serversocket2.socket().bind(new InetSocketAddress("localhost",this.port2));
			this.serversocket2.register(this.selector, SelectionKey.OP_ACCEPT);
			
		}catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	
	/**
	 * 这个方法是连接
	 * 客户端连接服务器
	 * @throws IOException 
	 * */
	public void accept(SelectionKey key) throws IOException{
		ServerSocketChannel server = (ServerSocketChannel) key.channel(); 	
		if(server.equals(serversocket1)){
			clientchannel1 = server.accept();
			clientchannel1.configureBlocking(false);
			//OP_READ用于读取操作的操作集位
			clientchannel1.register(this.selector, SelectionKey.OP_READ);
		}else {
			clientchannel2 = server.accept();
			clientchannel2.configureBlocking(false);
			//OP_READ用于读取操作的操作集位
			clientchannel2.register(this.selector, SelectionKey.OP_READ);
		}
	}
	
	/**
	 * 从通道中读取数据
	 * 并且判断是给那个服务通道的
	 * @throws IOException 
	 * */
	public void read(SelectionKey key) throws IOException{
			
			this.buf.clear();
			//通过选择键来找到之前注册的通道
			//但是这里注册的是ServerSocketChannel为什么会返回一个SocketChannel??
			SocketChannel channel = (SocketChannel) key.channel();
			//从通道里面读取数据到缓冲区并返回读取字节数
			int count = channel.read( this.buf);
			
			if(count == -1){
				//取消这个通道的注册
	            key.channel().close();
	            key.cancel();
	            return;
			}
			
			//将数据从缓冲区中拿出来
			String input = new String(this.buf.array()).trim();
			//那么现在判断是连接的那种服务
			if(channel.equals(this.clientchannel1)){
				System.out.println("欢迎您使用服务A");
				System.out.println("您的输入为:"+input);
			}else{
				System.out.println("欢迎您使用服务B");
				System.out.println("您的输入为:"+input);
			}
		
	}
	
	@Override
	public void run() {
		while(true){
			try{
				//选择一组键,其相应的通道已为 I/O 操作准备就绪。
				this.selector.select();
				
				//返回此选择器的已选择键集
				//public abstract Set<SelectionKey> selectedKeys()
				Iterator selectorKeys = this.selector.selectedKeys().iterator();
				while(selectorKeys.hasNext()){
					//这里找到当前的选择键
					SelectionKey key = (SelectionKey) selectorKeys.next();
					//然后将它从返回键队列中删除
					selectorKeys.remove();
					if(!key.isValid()){
						continue;
					}
					if(key.isAcceptable()){
						//如果遇到请求那么就响应
						this.accept(key);
					}else if(key.isReadable()){
						//读取客户端的数据
						this.read(key);
					}
				}
			}catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
		
	public static void main(String[] args) {
		Server server = new Server();
		Thread thread = new Thread(server);
		thread.start();
	}
}

 Client

package nio.asyn;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.net.InetAddress;

/**
 * TCP/IP的NIO非阻塞方式
 * 客户端
 * */
public class Client {

	//创建缓冲区
	private ByteBuffer buffer = ByteBuffer.allocate(512);
	//访问服务器
	public void query(String host,int port) throws IOException{
		InetSocketAddress address = new InetSocketAddress(InetAddress.getByName(host),port);
		SocketChannel socket = null;
	    byte[] bytes = new byte[512];
		while(true){
			try{
				System.in.read(bytes);
				socket = SocketChannel.open();
				socket.connect(address);
				buffer.clear();
				buffer.put(bytes);
				buffer.flip();
				socket.write(buffer);
				buffer.clear();
			}catch (Exception e) {
				e.printStackTrace();
			}finally{
				if(socket!=null){
					socket.close();
				}
			}
		}
	}
	
	public static void main(String[] args) throws IOException{
		new Client().query("localhost", 8099);
		
	}
}

 

以上的服务端一个线程监听两个服务,整个服务端只有一个阻塞的方法:

//选择一组键,其相应的通道已为 I/O 操作准备就绪。
				this.selector.select();

当客户请求服务器的时候,那么这造成了TCP没有面向连接的假象,其实至少在传输数据的时候是连接的,只是在一次I/O请求结束之后服务器端就把连接给断开,继而继续去处理更多的请求。而在客户端,可以看到也是遇到一次请求的时候就connect服务端一次。所以TCP还是面向连接的。

     现在终于知道了为什么叫非阻塞式IO了,大概就是这个意思。

2
1
分享到:
评论

相关推荐

    JAVA NIO学习笔记.docx

    Java NIO(New Input/Output)是Java标准库在JDK 1.4引入的一组新的I/O API,它提供了一种不同于传统IO的高效、非阻塞的I/O操作方式。NIO的核心概念包括Channel、Buffer和Selector,它们共同构建了一个与操作系统...

    java NIO学习系列 笔记

    6. **非阻塞IO**: - NIO引入了非阻塞模式,使得在等待数据就绪时,线程不会被阻塞,而是可以处理其他任务,提高了系统资源的利用率。Selector类允许程序监控多个通道,当某个通道准备好进行读写操作时,Selector会...

    java-demo:java学习笔记,包含多线程,java IO流,java 阻塞IO,非阻塞IO,netty demo

    在这个"java-demo"项目中,我们可以深入学习Java技术,特别是关于多线程、IO流以及两种不同的IO模型——阻塞IO(BIO)和非阻塞IO。此外,还涉及到Netty框架的应用,这是一个高性能、异步事件驱动的网络应用框架,常...

    nio demo for nio学习笔记(体系结构以及模块介绍)

    而NIO则采用了非阻塞模式,允许程序在等待数据准备就绪的过程中去做其他事情,提高了程序的效率和并发性。 **NIO的核心组件:** 1. **Channel(通道)**:Channel是数据传输的双向管道,可以同时进行读写操作。例如...

    java学习笔记之Java-IO操作共19页.pdf.zi

    【Java IO操作详解】 在Java编程中,IO(Input/Output)操作是处理数据输入与输出的核心技术。Java-IO操作共19页的笔记详细介绍了这一关键领域,旨在帮助...这19页的学习笔记将是你掌握Java IO操作的重要参考资料。

    java超强学习笔记

    5. **IO流与NIO**:Java的IO流和NIO(非阻塞I/O)用于读写数据,笔记会介绍文件操作、网络通信、数据传输等场景下的使用方法。 6. **多线程**:Java提供了内置的多线程支持,笔记会讲解线程的创建、同步、通信等,...

    《java学习》-java学习笔记.zip

    此外,NIO(非阻塞I/O)和文件操作也是这部分的重要内容。了解并熟练运用各种流类,能有效地处理文件读写、网络通信等问题。 4. **SQL(SQL.md)**: 虽然SQL不是Java语言的一部分,但Java开发者经常需要与数据库...

    IO-黑马程序员Java学习笔记.rar

    Java的输入/输出(IO)系统是编程中的一个重要部分,特别是在开发服务器端应用程序、文件处理以及...通过阅读"IO-黑马程序员Java学习笔记",开发者可以系统地学习并实践这些知识点,从而提升自己的Java IO编程能力。

    JAVA学习笔记

    最后,JAVA NIO(New IO)是JAVA从1.4版本引入的,提供了一种非阻塞I/O模型,显著提高了处理大量并发连接的能力。NIO包括通道(Channels)、缓冲区(Buffers)和选择器(Selectors),通过选择器,一个线程可以同时...

    学习笔记 十分有用 java

    笔记中会详细介绍InputStream、OutputStream、Reader、Writer类族,以及BufferedReader、PrintWriter等实用类的用法,还会讲解NIO(非阻塞IO)和File类的使用,这些都是日常开发中不可或缺的部分。 网络编程在现代...

    java学习笔记JDK6.0课件和代码

    JDK 6.0引入了非阻塞I/O(NIO),提升了处理大量并发I/O请求的效率。 7. **多线程**:Java支持多线程编程,通过Thread类和Runnable接口可以实现并发执行。学习如何同步和通信,避免线程安全问题,对于编写高并发...

    非常详细javaSE学习笔记.rar

    11. **IO与NIO**:对比传统IO的阻塞模式,非阻塞式I/O(New IO,Java NIO)的使用和优势。 12. **Lambda表达式**:Java 8引入的新特性,用于简化函数式编程,包括函数接口,lambda语法,流(Stream API)及并行流。 ...

    java全套笔记 来自狂神说java笔录.zip

    11. **Java IO/NIO**:深入理解流的分类,学习非阻塞I/O模型NIO(New Input/Output)。 12. **Java 8及更高版本新特性**:包括Lambda表达式、Stream API、Optional类、日期时间API的改进等。 这些笔记内容全面覆盖...

    j2se学习笔记和java学习笔记

    NIO(New IO)是Java 1.4引入的,提供了一种非阻塞的I/O模型,可以处理大量并发连接,特别适合于网络编程。 五、多线程 Java内置了对多线程的支持,可以同时执行多个任务,提高程序效率。了解如何创建和管理线程,...

    李兴华java笔记

    笔记会讲解流的分类、缓冲流、字符流与字节流的区别,以及NIO(非阻塞I/O)的相关知识。 5. **多线程**:Java支持多线程编程,使得程序能同时执行多个任务。笔记将介绍线程的创建方式、同步机制(如synchronized...

    Java 学习笔记.zip

    这份"Java学习笔记.zip"文件显然包含了一些关于Java编程的基础到高级的学习资料,特别聚焦于JDK 6版本。JDK(Java Development Kit)是Java开发环境的核心组件,包括了Java编译器、Java运行时环境、以及丰富的API库...

    Javajdk5学习笔记

    9. **NIO(New IO)**:JDK 5引入了非阻塞I/O模型,通过`java.nio`包提供了更高效的数据传输方式,尤其适合处理大量并发连接的情况。 10. **静态导入(Static Import)**:允许将类的静态成员作为顶级名称引用,...

    java学习笔记.rar

    9. **NIO(非阻塞I/O)**:Java NIO提供了非阻塞的I/O操作,包括选择器(Selector)和通道(Channel),在处理大量并发连接时更高效。 10. **反射**:Java反射机制允许程序在运行时检查类的信息(如类名、方法、...

    java io流学习笔记1

    Java 1.4引入了NIO(Non-blocking I/O)框架,提供了更高效、非阻塞的IO操作方式。NIO的核心组件包括Channel、Buffer和Selector,它们极大地提升了多路复用IO的能力。 总结,Java IO流是Java平台中处理输入输出的...

    java整个学习笔记带图片实现思路等

    NIO(New IO)是Java 1.4引入的新特性,提供了非阻塞I/O操作,提高了效率。 这份笔记通过图片和文字相结合的方式,将抽象的概念具象化,帮助学习者更好地理解和记忆。无论是初学者还是有一定经验的开发者,都能从中...

Global site tag (gtag.js) - Google Analytics