在本文中,我们将给您介绍关于Javanio空轮询bug的详细内容,并且为您解答java轮询接口的相关问题,此外,我们还将为您提供关于旧IO(java.io.*)和新IO(java.nio.*)的主要区
在本文中,我们将给您介绍关于Java nio 空轮询bug的详细内容,并且为您解答java轮询接口的相关问题,此外,我们还将为您提供关于 旧IO(java.io.*)和新IO(java.nio.*)的主要区别、8.74 NIO 的介绍和 JDK7 下 NIO 的一个案例、AG 极速百家家乐《 787977.tv 飞机 @gb560 》Java NIO 系列教程(一)java NIO 简介、BIO、NIO、AIO系列一:NIO的知识。
本文目录一览:- Java nio 空轮询bug(java轮询接口)
- 旧IO(java.io.*)和新IO(java.nio.*)的主要区别
- 8.74 NIO 的介绍和 JDK7 下 NIO 的一个案例
- AG 极速百家家乐《 787977.tv 飞机 @gb560 》Java NIO 系列教程(一)java NIO 简介
- BIO、NIO、AIO系列一:NIO
Java nio 空轮询bug(java轮询接口)
这个应该是很老的bug了,linux平台,jdk6好像就修复了
bug 描述 :https://bugs.java.com/bugdata...
This is an issue with poll (and epoll) on Linux. If a file descriptor for a connected socket is polled with a request event mask of 0, and if the connection is abruptly terminated (RST) then the poll wakes up with the POLLHUP (and maybe POLLERR) bit set in the returned event set. The implication of this behaviour is that Selector will wakeup and as the interest set for the SocketChannel is 0 it means there aren''t any selected events and the select method returns 0.
之前一直不知道event mask 为0 代表啥,知道翻到了资料
http://man7.org/linux/man-pag...
The field events is an input parameter, a bit mask specifying the
events the application is interested in for the file descriptor fd.
This field may be specified as zero, in which case the only events
that can be returned in revents are POLLHUP, POLLERR, and POLLNVAL
(see below).
大概知道了0代表了POLLHUP, POLLERR, and POLLNVA 这些event
继续查阅https://blog.csdn.net/tilter/...
常量 说明
POLLIN 普通或优先级带数据可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读
POLLPRI 高优先级数据可读
POLLOUT 普通数据可写
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写
POLLERR 发生错误
POLLHUP 对方描述符挂起
POLLNVAL 描述字不是一个打开的文件
大概明白了Bug描述中 abruptly terminated (RST)的含义及发生场景
也就知道什么时候会发生空轮询bug
个人记录下
还有1问题没想明白,为啥先cancel,再select(or selectNow) 还是无法避免这个问题? ref: https://www.cnblogs.com/JAYIT...
网上说,在多线程环境下,selectNow先发生,cancel后发生,这样还是会存在问题
但是,即使cancel后发生,channel也会被移到待移除channel集合了,下次再轮询的时候,select也会出发这个集合里面的channel被清除
这样,最多多一次空轮训。
目前给的方案都是open一个新的selector,把有效的channel全部注册到新的selector,再轮询新的selector
旧IO(java.io.*)和新IO(java.nio.*)的主要区别
旧IO(java.io.*)和新IO(java.nio.*)的主要区别,如下表:
IO | NIO |
面向流 | 面向缓冲 |
阻塞IO | 非阻塞IO |
无 | 选择器 |
java.nio.*是JDK1.4加入的。
下面分别介绍:
一、面向流与面向缓冲
Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。
Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
二、阻塞与非阻塞
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
Java NIO的非阻塞模式,如使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。一个单独的线程可以管理多个输入和输出通道(channel)。
三、选择器(Selectors)
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
概念参考地址:http://ifeve.com/java-nio-vs-io/
四、性能比较(冰山一角)
在此,就拿最简单的文件操作为例,从一个文件读数据并写入另一个文件,简而言之就是copy。代码如下:
public class IOAndNIO {
private static final String sourcePath = "C:/Users/Administrator/Desktop/123.txt";
private static final String destPath = "test-nio.txt";
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
long atime = System.currentTimeMillis();
//nio方式
runWithNIO();
long btime = System.currentTimeMillis();
System.out.println("nio-time:"+(btime-atime));
atime = System.currentTimeMillis();
//传统方式
runWithIO();
btime = System.currentTimeMillis();
System.out.println("io-time:"+(btime-atime));
} catch (Exception e) {
// TODO: handle exception
}
}
@SuppressWarnings("resource")
public static void runWithNIO() throws IOException {
final int BSIZE = 1024;
FileChannel in = new FileInputStream(sourcePath).getChannel();
FileChannel out = new FileOutputStream(destPath).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while (in.read(buffer) != -1) {
buffer.flip(); // Prepare for writing
out.write(buffer);
buffer.clear(); // Prepare for reading
}
}
public static void runWithIO() throws IOException {
String file = "test-io.txt";
BufferedReader in = new BufferedReader(new StringReader(read()));
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
String s = "";
while ((s = in.readLine()) != null)
out.println(s);
out.close();
}
// 读文件
public static String read() throws IOException {
BufferedReader in = new BufferedReader(new FileReader(sourcePath));
String str;
StringBuilder sb = new StringBuilder();
while ((str = in.readLine()) != null)
sb.append(str + "\n");
in.close();
return sb.toString();
}
}
在文件很小的时候看不出区别,所以将测试文件大小增加到2M,测试结果如下:
很明显nio采用的缓冲器,大大提升了速度。
在上述代码nio段中,调用了read()方法后,数据就会输入到缓冲器,所以就必须调用缓冲器的flip()方法,告诉缓冲器即将会有读取字节的操作,write()方法操作后,数据仍在缓冲器中,所以必须要调用clear()方法,对所有内部指针重新安排以便下一次read(),缓冲器接受数据。
除此方法外,还有一个更简单的方法,即调用transferTo()函数,该函数允许一个通道(channel)和另一个通道直接相连。
将runWithNIO()方法改为如下:
public static void runWithNIO() throws IOException {
FileChannel in = new FileInputStream(sourcePath).getChannel();
FileChannel out = new FileOutputStream(destPath).getChannel();
in.transferTo(0, in.size(), out);
}
除了以上区别,nio包下还提供了例如Charset类,提供字符的编码与解码,方便实用。
暂时总结到这,再补充。若有错误,望纠正。
8.74 NIO 的介绍和 JDK7 下 NIO 的一个案例
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
/*
* nio包在JDK4出现,提供了IO流的操作效率。但是目前还不是大范围的使用。
* 有空的话了解下,有问题再问我。
*
* JDK7的之后的nio:
* Path:路径
* Paths:有一个静态方法返回一个路径
* public static Path get(URI uri)
* Files:提供了静态方法供我们使用
* public static long copy(Path source,OutputStream out):复制文件
* public static Path write(Path path,Iterable<? extends CharSequence> lines,Charset cs,OpenOption... options)
*/
public class NIODemo {
public static void main(String[] args) throws IOException {
// public static long copy(Path source,OutputStream out)
Files.copy(Paths.get("array.txt"), new FileOutputStream( "count.txt"));
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("world");
array.add("java");
Files.write(Paths.get("array.txt"), array, Charset.forName("GBK"));
}
}
AG 极速百家家乐《 787977.tv 飞机 @gb560 》Java NIO 系列教程(一)java NIO 简介
这个系列的文章,我们开始玩一玩 IO 方面的知识,对于 IO 和 NIO,我们经常会接触到,了解他们的基本内容,对于我们的工作会有特别大的帮助。这篇博文我们仅仅是介绍 IO 和 NIO 的基本概念,以及一些关键词。
基本概念
IO 是主存和外部设备(硬盘、终端和网络)进行数据拷贝的过程。IO 是操作系统的底层功能实现,底层通过 I/O 指令完成对数据的操作。
java 标准 IO 回顾
Java 标准 IO 类库是 io 面向对象的一种抽象。基于本地方法的底层实现,我们无须关注底层实现。InputStream\OutputStream (面向字节流) :一次传送一个字节。 Reader\Writer ( 面向字符流 ) :一次一个字符。
java NIO 简介
Java NIO (New IO Non Blocking IO) 是从 Java1.4 版本开始引入的一个新的 IO API,可以替代标准的 Java IO API。NIO 与原来的 IO 有同样的作用和目的,但是使用的方式完全不同,NIO 支持面向缓冲区的、基于通道的 IO 操作。NIO 将以更加高效的方式进行文件的读写操作。
Sum 官方标榜许多的特性,如下:
- 为所有的原始类型提供 (Buffer) 缓存支持。
- 字符集编码解码解决方案。
- Channel :一个新的原始 I/O 抽象。
- 支持锁和内存映射文件的文件访问接口。
- 提供多路 (non-bloking) 非阻塞式的高伸缩性网络 I/O 。
看到 NIO 这么多的优点,是不是有点小激动。继续看一下他们的区别。
Java NIO 与 IO 的主要区别
下表总结了 Java NIO 和 IO 之间的主要差别,我会更详细地描述表中每部分的差异。
IO |
NIO |
---|---|
面向流(Stream Oriented) |
面向缓冲区(Buffer Oriented) |
阻塞 IO(Blocking IO) |
非阻塞 IO(Non Blocking IO) |
(无) |
选择器(Selectors) |
面向流与面向缓冲
NIO 与 IO 之间本质的区别是,IO 是面向流的,NIO 是面向缓冲区的。IO 面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。
Java NIO 的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
阻塞与非阻塞 IO
IO 的各种流是阻塞的。这意味着,当一个线程调用 read () 或 write () 时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
NIO 是非阻塞模式的,一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞 IO 的空闲时间用于在其它通道上执行 IO 操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
选择器(Selectors)
NIO 的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来 “选择” 通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
IO 与 NIO 的区别是很大的,但是他们又有自己的适用范围:
- NIO 允许你用一个单独的线程或几个线程管理很多个 channels(网络的或者文件的),代价是程序的处理和处理 IO 相比更加复杂。如果你需要同时管理成千上万的连接,但是每个连接只发送少量数据,此时 NIO 更合适。
- 如果你只有少量的连接但是每个连接都占有很高的带宽,同时发送很多数据,传统的 IO 会更适合。
通道与缓冲区
Java NIO 系统的核心在于:通道(Channel)和缓冲区(Buffer)。
Buffer:表示打开到 IO 设备(例如:文件、套接字)的连接。
- 是一块连续的内存块。
- 是 NIO 数据读或写的中转地。
Channel:
- 数据的源头或者数据的目的地
- 用于向 buffer 提供数据或者读取 buffer 数据,buffer 对象的唯一接口。
- 异步 I/O 支持
基本步骤:
若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区,然后操作缓冲区,对数据进行处理。
Channel 与 Buffer 的关系:

简而言之,Channel 负责传输,Buffer 负责存储 。
总结
本篇博文都是基本概念,内容相对比较苦涩,万事开头难。只要开头,下面会顺风顺水。
下篇博文,我们介绍通道 Channel,敬请期待。
BIO、NIO、AIO系列一:NIO
一、几个基本概念
1.同步、异步、阻塞、非阻塞
同步:用户触发IO操作,你发起了请求就得等着对方给你返回结果,你不能走,针对调用方的,你发起了请求你等
异步:触发触发了IO操作,即发起了请求以后可以做自己的事,等处理完以后会给你返回处理完成的标志,针对调用方的,你发起了请求你不等
阻塞:你调用我,我试图对文件进行读写的时候发现没有可读写的文件,我的程序就会进入等待状态,等可以读写了,我处理完给你返回结果,这里的等待和同步的等待有很大的区别,针对服务提供方的,你调用我我发现服务不可用我等
非阻塞:你调用我,我试图对文件读写的时候发现没有读写的文件,不等待直接返回,等我发现可以读写文件处理完了再给你返回成功标志,针对服务提供方的,你调用我我不等,我处理完了给你返回结果
二、NIO基础
1、传统BIO模型-InputStream、OutputStream
传统BIO是一种同步的阻塞IO,IO在进行读写时,该线程将被阻塞,线程无法进行其它操作。
IO流在读取时,会阻塞。直到发生以下情况:1、有数据可以读取。2、数据读取完成。3、发生异常。
服务端:
BioServer.java
1 import java.io.IOException;
2 import java.net.ServerSocket;
3 import java.net.Socket;
4
5
6 public class BioServer {
7 public static void main(String[] args) {
8 int port=8080; //服务端默认端口
9 if(args != null && args.length>0){
10 try {
11 port = Integer.valueOf(args[0]);
12 } catch (NumberFormatException e) {
13 }
14 }
15 ServerSocket server = null;
16 try {
17 server = new ServerSocket(port);
18 System.out.println("启动了服务端,端口:"+port);
19 Socket socket = null;
20 while(true){
21 socket = server.accept();//阻塞等待客户端连接
22 new Thread(new BioServerHandler(socket)).start();
23 }
24 } catch (Exception e) {
25 e.printStackTrace();
26 } finally {
27 if(server!=null){
28 System.out.println("关闭了服务.");
29 try {
30 server.close();
31 server = null;
32 } catch (IOException e) {
33 e.printStackTrace();
34 }
35 }
36 }
37 }
38 }
BioServerHandler.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class BioServerHandler implements Runnable {
private Socket socket;
public BioServerHandler(Socket socket){
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
String body = null;
while(true){
body = in.readLine(); //阻塞等待数据可以被读取
if(body == null){
break;
}
System.out.println("服务器接收到指令:"+body);
}
} catch (Exception e) {
if(in != null){
try {
in.close();
in = null;//
} catch (IOException e1) {
e1.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
this.socket = null;
}
}
}
}
客户端:
BioServerClient.java
1 import java.io.IOException;
2 import java.io.PrintWriter;
3 import java.net.Socket;
4
5 public class BioServerClient {
6
7 public static void main(String[] args) {
8 int port=8080; //服务端默认端口
9 if(args != null && args.length>0){
10 try {
11 port = Integer.valueOf(args[0]);
12 } catch (NumberFormatException e) {
13 }
14 }
15 Socket socket = null;
16 PrintWriter out = null;
17 try {
18 socket = new Socket("127.0.0.1", port);
19 out = new PrintWriter(socket.getOutputStream(), true);
20 out.println("9527");
21 System.out.println("客户端向服务端发送了指令");
22 } catch (Exception e) {
23 e.printStackTrace();
24 } finally {
25 if(out !=null){
26 out.close();
27 out = null;
28 }
29 if(socket != null){
30 try {
31 socket.close();
32 } catch (IOException e) {
33 e.printStackTrace();
34 }
35 socket = null;
36 }
37 }
38 }
39 }
2、伪异步IO模型
以传统BIO模型为基础,通过线程池的方式维护所有的IO线程,实现相对高效的线程开销及管理。
服务端:
TimeServer.java
1 import java.io.IOException;
2 import java.net.ServerSocket;
3 import java.net.Socket;
4 public class TimeServer {
5 public static void main(String[] args) {
6 int port=8080; //服务端默认端口
7 ServerSocket server = null;
8 try {
9 server = new ServerSocket(port);
10 System.out.println("The time server is start in port:"+port);
11 Socket socket = null;
12 //通过线程池的方式维护所有的IO线程,实现相对高效的线程开销及管理
13 TimeServerHandlerExecutePool singleExecutor = new TimeServerHandlerExecutePool(50, 10000);
14
15 while(true){
16 socket = server.accept();
17 // new Thread(new TimeServerHandler(socket)).start();
18 singleExecutor.execute(new TimeServerHandler(socket));
19 }
20 } catch (Exception e) {
21 e.printStackTrace();
22 } finally {
23 if(server!=null){
24 System.out.println("The time server is close.");
25 try {
26 server.close();
27 server = null;
28 } catch (IOException e) {
29 e.printStackTrace();
30 }
31 }
32 }
33 }
34 }
TimeServerHandler.java
1 import java.io.BufferedReader;
2 import java.io.IOException;
3 import java.io.InputStreamReader;
4 import java.io.PrintWriter;
5 import java.net.Socket;
6 import java.util.Date;
7
8
9 public class TimeServerHandler implements Runnable {
10
11 private Socket socket;
12 public TimeServerHandler(Socket socket){
13 this.socket = socket;
14 }
15
16 @Override
17 public void run() {
18 BufferedReader in = null;
19 PrintWriter out = null;
20 try {
21 in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
22 out = new PrintWriter(this.socket.getOutputStream(), true);
23 String currentTime = null;
24 String body = null;
25 while(true){
26 body = in.readLine();
27 if(body == null){
28 break;
29 }
30 System.out.println("The time server(Thread:"+Thread.currentThread()+") receive order:"+body);
31 currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
32 out.println(currentTime);
33 }
34 } catch (Exception e) {
35 if(in != null){
36 try {
37 in.close();
38 in = null;//
39 } catch (IOException e1) {
40 e1.printStackTrace();
41 }
42 }
43 if(out != null){
44 try {
45 out.close();
46 out = null;
47 } catch (Exception e1) {
48 e1.printStackTrace();
49 }
50 }
51 if(socket != null){
52 try {
53 socket.close();
54 } catch (IOException e1) {
55 e1.printStackTrace();
56 }
57 this.socket = null;
58 }
59 }
60 }
61
62 }
服务端线程池TimeServerHandlerExecutePool.java
1 import java.util.concurrent.ArrayBlockingQueue;
2 import java.util.concurrent.ExecutorService;
3 import java.util.concurrent.ThreadPoolExecutor;
4 import java.util.concurrent.TimeUnit;
5
6 public class TimeServerHandlerExecutePool {
7
8 private ExecutorService executor;
9
10 public TimeServerHandlerExecutePool(int maxPoolSize, int queueSize) {
11 executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), maxPoolSize, 120l, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize));
12 }
13
14 public void execute(Runnable task) {
15 executor.execute(task);
16 }
17
18 }
客户端:
TimeServerClient.java
1 import java.io.BufferedReader;
2 import java.io.IOException;
3 import java.io.InputStreamReader;
4 import java.io.PrintWriter;
5 import java.net.Socket;
6
7 public class TimeServerClient {
8
9 public static void main(String[] args) {
10 int port=8080; //服务端默认端口
11 Socket socket = null;
12 BufferedReader in = null;
13 PrintWriter out = null;
14 try {
15 socket = new Socket("127.0.0.1", port);
16 in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
17 out = new PrintWriter(socket.getOutputStream(), true);
18 out.println("QUERY TIME ORDER");
19 System.out.println("Send order to server succeed.");
20 String resp = in.readLine();
21 System.out.println("Now is : "+resp);
22 } catch (Exception e) {
23 e.printStackTrace();
24 } finally {
25 if(out !=null){
26 out.close();
27 out = null;
28 }
29 if(in != null){
30 try {
31 in.close();
32 } catch (IOException e) {
33 e.printStackTrace();
34 }
35 }
36 if(socket != null){
37 try {
38 socket.close();
39 } catch (IOException e) {
40 e.printStackTrace();
41 }
42 socket = null;
43 }
44 }
45 }
46 }
3、NIO模型
NIO(JDK1.4)模型是一种同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(多路复用器)。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(多路复用器)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
NIO和传统IO(一下简称IO)之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。
IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
NIO优点:
1、通过Channel注册到Selector上的状态来实现一种客户端与服务端的通信。
2、Channel中数据的读取是通过Buffer , 一种非阻塞的读取方式。
3、Selector 多路复用器 单线程模型, 线程的资源开销相对比较小。
NIO缺点:
1. API使用复杂。
2. 需要具备一些多线程编码能力
3. 断线重连问题比较严重
4. NIO还有一些BUG
Channel(通道)
传统IO操作对read()或write()方法的调用,可能会因为没有数据可读/可写而阻塞,直到有数据响应。也就是说读写数据的IO调用,可能会无限期的阻塞等待,效率依赖网络传输的速度。最重要的是在调用一个方法前,无法知道是否会被阻塞。
NIO的Channel抽象了一个重要特征就是可以通过配置它的阻塞行为,来实现非阻塞式的通道。
Channel是一个双向通道,与传统IO操作只允许单向的读写不同的是,NIO的Channel允许在一个通道上进行读和写的操作。
FileChannel:文件
SocketChannel:
ServerSocketChannel:
DatagramChannel: UDP
Buffer(缓冲区)
Bufer顾名思义,它是一个缓冲区,实际上是一个容器,一个连续数组。Channel提供从文件、网络读取数据的渠道,但是读写的数据都必须经过Buffer。
Buffer缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该模块内存。为了理解Buffer的工作原理,需要熟悉它的三个属性:capacity、position和limit。
属性:capacity、position和limit。
position和limit的含义取决于Buffer处在读模式还是写模式。不管Buffer处在什么模式,capacity的含义总是一样的。见下图:
capacity:作为一个内存块,Buffer有固定的大小值,也叫作“capacity”,只能往其中写入capacity个byte、long、char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清楚数据)才能继续写数据。
position:当你写数据到Buffer中时,position表示当前的位置。初始的position值为0,当写入一个字节数据到Buffer中后,position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity-1。当读取数据时,也是从某个特定位置读,将Buffer从写模式切换到读模式,position会被重置为0。当从Buffer的position处读取一个字节数据后,position向前移动到下一个可读的位置。
limit:在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)
Buffer的分配:对Buffer对象的操作必须首先进行分配,Buffer提供一个allocate(int capacity)方法分配一个指定字节大小的对象。
向Buffer中写数据:写数据到Buffer中有两种方式:
1、从channel写到Buffer
int bytes = channel.read(buf); //将channel中的数据读取到buf中
2、通过Buffer的put()方法写到Buffer
buf.put(byte); //将数据通过put()方法写入到buf中
flip()方法:将Buffer从写模式切换到读模式,调用flip()方法会将position设置为0,并将limit设置为之前的position的值。
从Buffer中读数据:从Buffer中读数据有两种方式:
1、从Buffer读取数据到Channel
int bytes = channel.write(buf); //将buf中的数据读取到channel中
2、通过Buffer的get()方法读取数据
byte bt = buf.get(); //从buf中读取一个byte
rewind()方法:Buffer.rewind()方法将position设置为0,使得可以重读Buffer中的所有数据,limit保持不变。Buffer中的数据,读取完成后,依然保存在Buffer中,可以重复读取。
clear()与compact()方法:一旦读完Buffer中的数据,需要让Buffer准备好再次被写入,可以通过clear()或compact()方法完成。如果调用的是clear()方法,position将被设置为0,limit设置为capacity的值。但是Buffer并未被清空,只是通过这些标记告诉我们可以从哪里开始往Buffer中写入多少数据。如果Buffer中还有一些未读的数据,调用clear()方法将被"遗忘 "。compact()方法将所有未读的数据拷贝到Buffer起始处,然后将position设置到最后一个未读元素的后面,limit属性依然设置为capacity。可以使得Buffer中的未读数据还可以在后续中被使用。
mark()与reset()方法:通过调用Buffer.mark()方法可以标记一个特定的position,之后可以通过调用Buffer.reset()恢复到这个position上。
Selector(多路复用器)
Selector与Channel是相互配合使用的,将Channel注册在Selector上之后,才可以正确的使用Selector,但此时Channel必须为非阻塞模式。Selector可以监听Channel的四种状态(Connect、Accept、Read、Write),当监听到某一Channel的某个状态时,才允许对Channel进行相应的操作。
Connect:某一个客户端连接成功后
Accept:准备好进行连接
Read:可读
Write:可写
4、NIO示例:
服务端:
MultiplexerTimeServer.java
1 package com.studyio.demo3;
2
3 import java.io.IOException;
4 import java.net.InetSocketAddress;
5 import java.nio.ByteBuffer;
6 import java.nio.channels.SelectionKey;
7 import java.nio.channels.Selector;
8 import java.nio.channels.ServerSocketChannel;
9 import java.nio.channels.SocketChannel;
10 import java.util.Date;
11 import java.util.Iterator;
12 import java.util.Set;
13
14 /**
15 *
16 * @author lgs
17 *
18 *
19 */
20 public class MultiplexerTimeServer implements Runnable {
21
22 private Selector selector;
23 private ServerSocketChannel serverChannel;
24 private volatile boolean stop;
25
26 public MultiplexerTimeServer(int port) {
27 try {
28 //打开服务端的一个通道channel:ServerSocketChannel
29 serverChannel = ServerSocketChannel.open();
30 //把服务端的通道设置为非阻塞模式
31 serverChannel.configureBlocking(false);
32 //绑定监听的端口地址
33 serverChannel.socket().bind(new InetSocketAddress(port), 1024);
34 //创建Selector(多路复用器)线程
35 selector = Selector.open();
36 //将服务端通道ServerSocketChannel注册到Selector,交给Selector监听,告诉客户端服务端是可以连接的了
37 serverChannel.register(selector, SelectionKey.OP_ACCEPT);
38 System.out.println("The time server is start in port:"+port);
39 } catch (Exception e) {
40 e.printStackTrace();
41 System.exit(1);
42 }
43 }
44
45 public void stop(){
46 this.stop = true;
47 }
48 @Override
49 public void run() {
50 //处理客户端消息
51 while(!stop){
52 try {
53 //通过Selector循环准备就绪的Key,这个key指的是客户端的通道
54 selector.select();
55 //拿到key以后把key放入迭代器iterator
56 Set<SelectionKey> selectionKeys = selector.selectedKeys();
57 Iterator<SelectionKey> iterator = selectionKeys.iterator();
58 SelectionKey selectionKey = null;
59 while(iterator.hasNext()){
60 selectionKey = iterator.next();
61 //取到key以后就移出,避免重复取
62 iterator.remove();
63 try {
64 //处理客户端传递过来的数据
65 handleInput(selectionKey);
66 } catch (Exception e) {
67 if(selectionKey!=null){
68 selectionKey.cancel();
69 if(selectionKey.channel()!=null){
70 selectionKey.channel().close();
71 }
72 }
73 }
74 }
75 } catch (Exception e) {
76 e.printStackTrace();
77 }
78 }
79 if(selector !=null){
80 try {
81 selector.close();
82 } catch (IOException e) {
83 e.printStackTrace();
84 }
85 }
86 }
87
88 /**
89 * 处理客户端传递过来的数据
90 * @param selectionKey
91 * @throws IOException
92 */
93 private void handleInput(SelectionKey selectionKey) throws IOException {
94 if(selectionKey.isValid()){
95 //客户端是可连接的
96 if (selectionKey.isAcceptable()) {
97 ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
98 //多路复用器监听到新的客户端连接,处理连接请求,完成TCP三次握手。
99 SocketChannel client = server.accept();
100 //设置为非阻塞模式
101 client.configureBlocking(false);
102 // 将新连接注册到多路复用器上,监听其读操作,读取客户端发送的消息。
103 client.register(selector, SelectionKey.OP_READ);
104 }
105 //客户端是可读的
106 if(selectionKey.isReadable()){
107 //获取取客户端的通道
108 SocketChannel client = (SocketChannel) selectionKey.channel();
109 ByteBuffer receivebuffer = ByteBuffer.allocate(1024);
110 //读取客户端请求消息到缓冲区
111 int count = client.read(receivebuffer); //非阻塞
112 if (count > 0) {
113 receivebuffer.flip();
114 byte[] bytes = new byte[receivebuffer.remaining()]; //remaining()方法
115 //从缓冲区读取消息到bytes数组里面
116 receivebuffer.get(bytes);
117 String body = new String(bytes, "UTF-8");
118 System.out.println("The time server(Thread:"+Thread.currentThread()+") receive order : "+body);
119 //将currentTime响应给客户端(客户端Channel)
120 String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
121 //服务端向客户端响应数据,通过客户端的通道传递数据
122 doWrite(client, currentTime);
123 }else if(count < 0){
124 selectionKey.channel();
125 client.close();
126 }else{
127
128 }
129 }
130 }
131 }
132
133 /**
134 * 服务端向客户端响应数据,通过客户端的通道传递数据
135 * @param client
136 * @param currentTime
137 * @throws IOException
138 */
139 private void doWrite(SocketChannel client, String currentTime) throws IOException {
140 if(currentTime != null && currentTime.trim().length()>0){
141 ByteBuffer sendbuffer = ByteBuffer.allocate(1024);
142 sendbuffer.put(currentTime.getBytes());
143 sendbuffer.flip();
144 //将客户端响应消息写入到客户端Channel中。
145 client.write(sendbuffer);
146 System.out.println("服务器端向客户端发送数据--:" + currentTime);
147 }else{
148 System.out.println("没有数据");
149 }
150 }
151
152 }
服务端入口程序TimeServer.java
public class TimeServer {
public static void main(String[] args) {
int port=8080; //服务端默认端口
MultiplexerTimeServer timeServer=new MultiplexerTimeServer(port);
new Thread(timeServer, "NIO-MultiplexerTimeServer-001").start();
}
}
客户端:
TimeClientHandler.java
1 package com.studyio.demo3;
2
3 import java.io.IOException;
4 import java.net.InetSocketAddress;
5 import java.nio.ByteBuffer;
6 import java.nio.channels.SelectionKey;
7 import java.nio.channels.Selector;
8 import java.nio.channels.SocketChannel;
9 import java.util.Iterator;
10 import java.util.Set;
11
12 /**
13 *
14 * @author lgs
15 *
16 */
17 public class TimeClientHandler implements Runnable {
18
19 private String host;
20 private int port;
21 private SocketChannel socketChannel;
22 private Selector selector;
23 private volatile boolean stop;
24
25 public TimeClientHandler(String host, int port) {
26 this.host = host;
27 this.port = port;
28 try {
29 //客户端打开一个通道SocketChannel
30 socketChannel = SocketChannel.open();
31 //创建Selector(多路复用器)线程
32 selector = Selector.open();
33 //设置为非阻塞模式
34 socketChannel.configureBlocking(false);
35 } catch (Exception e) {
36 e.printStackTrace();
37 System.exit(1);
38 }
39 }
40
41 @Override
42 public void run() {
43 try {
44 //连接服务端并发送数据
45 doConnect();
46 } catch (Exception e) {
47 e.printStackTrace();
48 System.exit(1);
49 }
50 //处理服务端响应的数据,和服务端处理客户端发送的数据一样
51 while(!stop){
52 //轮训通道的状态
53 try {
54 selector.select(1000);
55 Set<SelectionKey> selectionKeys = selector.selectedKeys();
56 Iterator<SelectionKey> iterator = selectionKeys.iterator();
57 SelectionKey selectionKey = null;
58 while(iterator.hasNext()){
59 selectionKey = iterator.next();
60 //取到key以后就移出,避免重复取
61 iterator.remove();
62 try {
63 //处理服务端响应的数据
64 handleInput(selectionKey);
65 } catch (Exception e) {
66 if(selectionKey!=null){
67 selectionKey.cancel();
68 if(selectionKey.channel()!=null){
69 selectionKey.channel().close();
70 }
71 }
72 }
73 }
74 } catch (Exception e) {
75 e.printStackTrace();
76 System.exit(1);
77 }
78 }
79 if(selector !=null){
80 try {
81 selector.close();
82 } catch (IOException e) {
83 e.printStackTrace();
84 }
85 }
86 }
87
88 /**
89 * 处理服务端响应的数据
90 * @param selectionKey
91 * @throws Exception
92 */
93 private void handleInput(SelectionKey selectionKey) throws Exception {
94 if(selectionKey.isValid()){
95 SocketChannel client = (SocketChannel) selectionKey.channel();
96 if (selectionKey.isConnectable()){
97 if(client.finishConnect()){
98 client.register(selector, SelectionKey.OP_READ);
99 doWrite(client);
100 }else{
101 System.exit(1);
102 }
103 }
104 if (selectionKey.isReadable()) {
105 ByteBuffer receivebuffer = ByteBuffer.allocate(1024);
106 int count = client.read(receivebuffer);
107 if (count > 0) {
108 receivebuffer.flip();
109 byte[] bytes = new byte[receivebuffer.remaining()]; //remaining()方法
110 receivebuffer.get(bytes);
111 String body = new String(bytes, "UTF-8");
112 System.out.println("Now is "+body);
113 this.stop = true;
114 }else if(count < 0){
115 selectionKey.channel();
116 client.close();
117 }else{
118
119 }
120 }
121 }
122 }
123
124 /**
125 * 连接服务端并发送数据
126 * @throws Exception
127 */
128 private void doConnect() throws Exception {
129 //连接服务端
130 boolean connect = socketChannel.connect(new InetSocketAddress(host, port));
131 //判断是否连接成功,如果连接成功,则监听Channel的读状态。
132 if(connect){
133 //连接成功就把客户端的通道注册到多路复用器上,并设置通道状态为可读
134 socketChannel.register(selector, SelectionKey.OP_READ);
135 //写数据 写给服务端
136 doWrite(socketChannel);
137 }else{
138 //如果没有连接成功,则向多路复用器注册Connect(可连接)状态
139 socketChannel.register(selector, SelectionKey.OP_CONNECT);
140 }
141
142 }
143
144 /**
145 * 写数据 写给服务端
146 * @param channel
147 * @throws IOException
148 */
149 private void doWrite(SocketChannel channel) throws IOException {
150 ByteBuffer sendbuffer = ByteBuffer.allocate(1024);
151 sendbuffer.put("QUERY TIME ORDER".getBytes());
152 sendbuffer.flip();
153 //向Channel中写入客户端的请求指令 写到服务端 写到通道里面
154 channel.write(sendbuffer);
155 if(!sendbuffer.hasRemaining()){
156 System.out.println("Send order to server succeed.");
157 }
158 }
159 }
客户端程序入口:TimeServerClient.java
public class TimeServerClient {
public static void main(String[] args) {
int port=8080; //服务端默认端口
new Thread(new TimeClientHandler("127.0.0.1", port), "NIO-TimeServerClient-001").start();
}
}
关于Java nio 空轮询bug和java轮询接口的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于 旧IO(java.io.*)和新IO(java.nio.*)的主要区别、8.74 NIO 的介绍和 JDK7 下 NIO 的一个案例、AG 极速百家家乐《 787977.tv 飞机 @gb560 》Java NIO 系列教程(一)java NIO 简介、BIO、NIO、AIO系列一:NIO等相关知识的信息别忘了在本站进行查找喔。
本文标签: