BIO与NIO的区别
NIO 和 BIO 的区别主要体现在三个方面:
NIO | BIO |
---|---|
基于缓冲区( Buffer ) | 基于流( Stream ) |
非阻塞 IO | 阻塞 IO |
选择器( Selector ) | 无 |
- 其中,选择器( Selector )是 NIO 能实现非阻塞的基础。
BIO是阻塞式IO,NIO是同步非阻塞IO。
这是Socket的模拟,基于BIO,可以看出以下IO流的传输是阻塞的,在不考虑多线程的情况下,BIO无法处理并发。
如果每个连接处理都开个线程,将会造成很大的资源浪费,因为有很多的人它只连接不发信息,这种连接视为浪费资源。
/**
* 模拟一个客户端
*/
public class Client {
public static void main(String[] args) throws IOException {
Socket socket=null;
try {
socket=new Socket("127.0.0.1",8080);
socket.getOutputStream().write("111".getBytes());
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
socket.close();
}
}
}
/**
* 通过Socket模拟一个客户端
*/
public class QQServer {
/**
* 定义一个字节数组用于接收客户端内容
*/
static byte[] bytes=new byte[1024];
public static void main(String[] args) {
try {
ServerSocket serverSocket=new ServerSocket();
serverSocket.bind(new InetSocketAddress(8080));
//这是一个阻塞方法,直到有客户端进行通信
Socket socket=serverSocket.accept();
//这是一个阻塞方法
socket.getInputStream().read(bytes);
System.out.println("读到内容打印");
String content=new String(bytes);
System.out.println(content);
}catch (IOException ex){
}
}
}
NIO代码层原理:逻辑代码
List<Socket> list=new ArrayList<>();
public void main() throws IOException {
ServerSocket serverSocket=new ServerSocket();
serverSocket.bind(new InetSocketAddress(8080));
/**
* 设置服务器的accept方法为非阻塞方法,防止没人连接不会一直等待
*/
serverSocket.setConfig(false);
while (true){
//接受一个连接
Socket socket=serverSocket.accept();
//如果没有连接
if(socket==null){
System.out.println("没人来连接");
//查看前面的socket有没有客户端发送消息
for(Socket socket1:list){
int read=socket1.getInputStream().read(bytes);
//如果前面的客户端有发送消息,则处理消息
if(read!=0){
//logic
}
}
}else {//如果有人来连接
//设置socket为非阻塞方法
socket.setConfig(false);
//将当前连接的socket加入List集合
list.add(socket);
//同样处理前面逻辑,看前面的socket是否发送消息以及现在的socket是否发送消息。
for(Socket socket1:list){
int read=socket1.getInputStream().read(bytes);
//如果前面的客户端有发送消息,则处理消息
if(read!=0){
//logic
}
}
}
}
}
实现代码
public class QQNIOServer {
/**
* 字节数组存储读取的数据
*/
static byte[] bytes=new byte[1024];
/**
* 非阻塞Socket对象集合,用于存储所有的socket
*/
static List<SocketChannel> list=new ArrayList<>();
/**
* 申请一个堆外内存,在虚拟机中也称为直接内存
*/
static ByteBuffer byteBuffer=ByteBuffer.allocate(512);
public static void main(String[] args) {
try {
//监听开始
//打开服务端非阻塞Socket
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//绑定监听端口号
serverSocketChannel.bind(new InetSocketAddress(8080));
//设置为非阻塞,默认为阻塞
serverSocketChannel.configureBlocking(false);
while (true){
//拿到客户端的socket流
SocketChannel socketChannel=serverSocketChannel.accept();
//如果没有客户端连接服务器
if (socketChannel==null){
//处理业务逻辑
Thread.sleep(500);
System.out.println("no conn");
//查看前面连接的socket是否有发送消息
for(SocketChannel client:list){
int read=client.read(byteBuffer);
if(read>0){
//该方法表明将从缓存的头开始读,并且读取所读数据的长度,不读取整个缓存,该方法必有!!!
byteBuffer.flip();
System.out.println(byteBuffer.toString());
}
}
}else {//当得到一个连接
System.out.println("has a conn");
socketChannel.configureBlocking(false);
list.add(socketChannel);
//查看前面连接的socket是否有发送消息
for(SocketChannel client:list){
int read=client.read(byteBuffer);
if(read>0){
//该方法表明将从缓存的头开始读,并且读取所读数据的长度,不读取整个缓存,该方法必有!!!
byteBuffer.flip();
System.out.println(byteBuffer.toString());
}
}
}
}
}catch (Exception e){
}
}
}
问题:List集合大部分连接没用,做了没必要的轮询,故此,解决方案是轮询不能交给我的系统,要主动感知有数据的socket,在linux上有epoll函数,它会告诉你当前来的是什么socket,但在windows上使用的select函数,同样在内核做轮询。所以Socket的性能与操作系统有关
基于 Buffer 与基于 Stream
BIO 是面向字节流或者字符流的,而在 NIO 中,它摒弃了传统的 IO 流,而是引入 Channel 和 Buffer 的概念:从 Channel 中读取数据到 Buffer 中,或者将数据从 Buffer 中写到 Channel 中。
① 那么什么是基于 Stream呢?
在一般的 Java IO 操作中,我们以流式的方式,顺序的从一个 Stream 中读取一个或者多个字节,直至读取所有字节。因为它没有缓存区,所以我们就不能随意改变读取指针的位置。
② 那么什么是基于 Buffer 呢?参考http://ifeve.com/buffers/
基于 Buffer 就显得有点不同了。我们在从 Channel 中读取数据到 Buffer 中,这样 Buffer 中就有了数据后,我们就可以对这些数据进行操作了。并且不同于一般的 Java IO 操作那样是顺序操作,NIO 中我们可以随意的读取任意位置的数据,这样大大增加了处理过程中的灵活性。
阻塞与非阻塞 IO
Java IO 的各种流是阻塞的 IO 操作。这就意味着,当一个线程执行读或写 IO 操作时,该线程会被阻塞,直到有一些数据被读取,或者数据完全写入。
Java NIO 可以让我们非阻塞的使用 IO 操作。例如:
当一个线程执行从 Channel 执行读取 IO 操作时,当此时有数据,则读取数据并返回;当此时无数据,则直接返回而不会阻塞当前线程。
当一个线程执行向 Channel 执行写入 IO 操作时,不需要阻塞等待它完全写入,这个线程同时可以做别的事情。
也就是说,线程可以将非阻塞 IO 的空闲时间用于在其他 Channel 上执行 IO 操作。所以,一个单独的线程,可以管理多个 Channel 的读取和写入 IO 操作。
Selector
Java NIO 引入 Selector ( 选择器 )的概念,它是 Java NIO 得以实现非阻塞 IO 操作的最最最关键。
我们可以注册多个 Channel 到一个 Selector 中。而 Selector 内部的机制,就可以自动的为我们不断的执行查询( select )操作,判断这些注册的 Channel 是否有已就绪的 IO 事件( 例如可读,可写,网络连接已完成 )。
通过这样的机制,一个线程通过使用一个 Selector ,就可以非常简单且高效的来管理多个 Channel 了。