BIOJava 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。
同步阻塞I/O模式:因为socket.accept()、 socket.read()、 socket.write() 是同步阻塞的。举例:当服务器用read去客户端的的数据时,是无法预知对方是否已经发送数据的。因此在收到数据之前,当前线程会被挂起。无法做其他的工作。直到客户端把数据发过来。此时如果有另一个客户端要请求连接服务器,服务器必须开启另一个线程来处理客户端请求。每有一个客户端,服务器就要开启一个线程来处理。处理完成之后,线程才销毁。如果有大量的客户端连接请求,就要创建大量的线程,而线程的创建和销毁成本很高。并且可能会导致线程堆栈溢出、创建新线程失败等问题。不过可以通过线程池来优化,线程使用完后,不再是销毁而是放回线程池,供下一个请求使用。减少了线程的创建和销毁成本。线程池可以设置最大线程数量,因此它占用的资源是可控的。但也带来了一个问题,它的并发量有限,最大并发量为线程池的最大线程数量。
NIO同步非阻塞的I/O模型:当服务器用read去客户端的的数据时,如果客户端没有发送数据,直接返回0,不会阻塞线程。因此在收到数据之前,该线程可以继续做其他的事情。 这段时间通常被用于执行其它通道上的IO操作,实现一个线程管理多个通道。
NIO有三大组件:Buffer(缓冲区),Channel(通道),Selector(选择器)
Buffer(缓冲区):NIO是面向缓冲区的。NIO中数据的读和写都必须经过Buffer。 Buffer可读可写,使用flip()方法切换读,使用clear()方法切换到写。若Buffer中有未读且后续还需要的数据,使用compact()方法切换到写(在未读的数据后继续写)。
Channel(通道):Channel和流非常相似,区别是:通道是双向的(可读可写),流是单向的(只能读或写)。NIO通道本身不存储数据,只是打开与IO设备之间的连接,所以通道必须要和缓冲区配合使用。NIO的强大功能部分来自于Channel的非阻塞特性,可以手动设置为非阻塞(注意:文件通道总是阻塞式的,因此不能设置为非阻塞)。
Selector(选择器)
Selector能够轮询检测多个注册的通道上是否有事件发生(事件:读就绪/写就绪/有新连接到来)。如果有事件发生,会通知Selector,然后针对每个事件进行相应的处理。实现一个线程管理多个通道。
客户端代码
public static void client(){
//设置缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = null;
try
{
// 获取客户端的通道
socketChannel = SocketChannel.open();
// 手动设置为非阻塞
socketChannel.configureBlocking(false);
// 发起连接
socketChannel.connect(new InetSocketAddress("10.10.195.115",8080));
// 需要判断是否连接连接完成
if(socketChannel.finishConnect())
{
int i=0;
while(true)
{
TimeUnit.SECONDS.sleep(1);
String info = "I'm "+i+++"-th information from client";
//切换到写模式
buffer.clear();
//将数据写入缓冲区
buffer.put(info.getBytes());
//切换到读模式
buffer.flip();
//将缓冲区的数据写入通道。
while(buffer.hasRemaining()){
System.out.println(buffer);
socketChannel.write(buffer);
}
}
}
}
catch (IOException | InterruptedException e)
{
e.printStackTrace();
}
finally{
//关闭资源
try{
if(socketChannel!=null){
socketChannel.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
服务端代码
public class ServerConnect
{
private static final int BUF_SIZE=1024;
private static final int PORT = 8080;
private static final int TIMEOUT = 3000;
public static void main(String[] args)
{
selector();
}
public static void handleAccept(SelectionKey key) throws IOException{
//先从事件身上获取到通道
ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();
//建立连接
SocketChannel sc = ssChannel.accept();
// 手动设置为非阻塞
sc.configureBlocking(false);
// 注册一个read事件
sc.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocateDirect(BUF_SIZE));
}
public static void handleRead(SelectionKey key) throws IOException{
SocketChannel sc = (SocketChannel)key.channel();
ByteBuffer buf = (ByteBuffer)key.attachment();
long bytesRead = sc.read(buf);
while(bytesRead>0){
buf.flip();
while(buf.hasRemaining()){
System.out.print((char)buf.get());
}
System.out.println();
buf.clear();
bytesRead = sc.read(buf);
}
if(bytesRead == -1){
sc.close();
}
}
public static void handleWrite(SelectionKey key) throws IOException{
ByteBuffer buf = (ByteBuffer)key.attachment();
buf.flip();
SocketChannel sc = (SocketChannel) key.channel();
while(buf.hasRemaining()){
sc.write(buf);
}
buf.compact();
}
public static void selector() {
Selector selector = null;
ServerSocketChannel ssc = null;
try{
// 开启选择器
selector = Selector.open();
// 开启服务器端的通道
ssc= ServerSocketChannel.open();
// 绑定端口
ssc.socket().bind(new InetSocketAddress(PORT));
// 设置为非阻塞
ssc.configureBlocking(false);
// 将通道注册到选择器上
ssc.register(selector, SelectionKey.OP_ACCEPT);
//死循环
while(true){
//轮询获取选择器上已经“准备就绪”的事件。阻塞TIMEOUT时间,退出本次循环。
if(selector.select(TIMEOUT) == 0){
System.out.println("==");
continue;
}
//获取选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator iter = selector.selectedKeys().iterator();
while(iter.hasNext()){
// 获取单个事件
SelectionKey key = iter.next();
//可能是accept
if(key.isAcceptable()){
//处理事件
handleAccept(key);
}
// 可能是read
if(key.isReadable()){
handleRead(key);
}
// 可能是write
if(key.isWritable() && key.isValid()){
handleWrite(key);
}
//判断是否连接,没有移除事件。
if(key.isConnectable()){
System.out.println("isConnectable = true");
}
iter.remove();
}
}
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(selector!=null){
selector.close();
}
if(ssc!=null){
ssc.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
}



