NIO:新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O,同时支持阻塞与非阻塞模式
I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
·通道channel:对原 I/O 包中的流的模拟,可以通过它读取和写入数据,通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者
OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。通道包括以下类型:
|- FileChannel:从文件中读写数据
|- DatagramChannel:通过 UDP 读写网络中数据
|- SocketChannel:通过 TCP 读写网络中数据
|- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel
·选择器Selector:NIO 现了IO多路复用中的Reactor模型,一个线程Thread使用一个选择器Selector通过轮询的方式去监听多个通道 Channel 上的事件,从
而让一个线程就可以处理多个事件,通过配置监听的通道Channel为非阻塞,那么当Channel上的IO事件还未到达时,就不会进入阻塞状态一直等待,而
是继续轮询其它Channel,找到IO事件已经到达的Channel执行。因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一
个事件,对于IO密集型的应用具有很好地性能。
·缓冲区:提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程,实际是一个数组,类型有ByteBuffer、CharBuffer、ShortBuffer、
InterBuffer、LongBuffer、FloatBuffer、DoubleBuffer,状态量有:
|-mark:标记
|-capacity:最大容量
|-position:位置
|-limit:限制
·关系:0 <= 标记 <= 位置 <= 限制 <= 容量
|-flip()方法:反转,切换读写模式
NIO编程过程
·客户端:
|-通过SocketChannel连接到远程服务器
|-创建读数据/写数据缓冲区对象来读取服务端数据或向服务端发送数据
|-关闭SocketChannel
·服务端:
|-通过ServerSocketChannel 绑定ip地址和端口号
|-通过ServerSocketChannelImpl的accept()方法创建一个SocketChannel对象用户从客户端读/写数据
|-创建读数据/写数据缓冲区对象来读取客户端数据或向客户端发送数据
|-关闭SocketChannel和ServerSocketChannel
看一下简单多线程的例子:
1.服务器:
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
public class Server {
public static void main(String[] args) throws IOException {
// 这里使用Apache的线程池工厂来实现线程池
ScheduledExecutorService pool = new ScheduledThreadPoolExecutor(3,
new BasicThreadFactory.Builder().namingPattern("mypool-%d").daemon(false).build());
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//将serverSocketChannel设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
//serverSocketChannel的socket绑定服务端口
serverSocketChannel.socket().bind(new InetSocketAddress(8888));
//将serverSocketChannel 注册到selector上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器开始");
while (true) {
//当注册的事件到达时,方法返回
selector.select();
// 获得selector中选中的项的迭代器,选中的项为注册的事件
Iterator ite = selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
ite.remove();
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
// 获得和客户端连接的通道
SocketChannel channel = server.accept();
// 设置成非阻塞
channel.configureBlocking(false);
//在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
channel.register(selector, SelectionKey.OP_READ);
// 获得了可读的事件
} else if (key.isReadable()) {
//取消可读触发标记
key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
//加入线程池
pool.execute(new ThreadHandlerChannel(key));
}
}
}
}
}
2.客户端:
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
class ClientThread implements Runnable {
private String ip;
private int port;
private Selector selector;
private SocketChannel socketChannel;
public ClientThread(String ip, int port) {
this.ip = ip;
this.port = port;
try {
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
System.out.println("客户端" + ip + "开始");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
socketChannel.connect(new InetSocketAddress(8888));
socketChannel.register(selector, SelectionKey.OP_WRITE);
} catch (IOException e) {
e.printStackTrace();
}
while (true) {
try {
//获取注册在selector上的所有的就绪状态的serverSocketChannel中发生的事件
Set set = selector.selectedKeys();
Iterator iterator = set.iterator();
SelectionKey key;
while (iterator.hasNext()) {
key = iterator.next();
if (!key.isValid()) {
key.cancel();
continue;
}
handleInput(key);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
doWrite(sc);
} else {
System.out.println("error");
}
}
if (key.isReadable()) {
ByteBuffer bf = ByteBuffer.allocate(1024);
int bytes = sc.read(bf);
if (bytes > 0) {
bf.flip();
byte[] byteArray = new byte[bf.remaining()];
bf.get(byteArray);
String msg = new String(byteArray);
System.out.println("服务器 :" + msg);
} else if (bytes < 0) {
key.cancel();
sc.close();
}
}
}
}
private void doWrite(SocketChannel sc) throws IOException {
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
ByteBuffer bf = ByteBuffer.allocate(sin.readLine().length());
bf.put(sin.readLine().getBytes());
bf.flip();
sc.write(bf);
}
}
3.启动客户端
public class Client {
public static void main(String[] args) {
// 这里使用Apache的线程池工厂来实现线程池
ScheduledExecutorService pool = new ScheduledThreadPoolExecutor(3,
new BasicThreadFactory.Builder().namingPattern("mypool-%d").daemon(false).build());
pool.execute(new ClientThread("127.0.0.1", 8888));
}
}