1. 网络阻塞IO与非阻塞IO
-
传统IO是阻塞式的,也就是说,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行 IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理
当服务器端需要处理大量客户端时,性能急剧下降。
-
NIO是非阻塞式的,当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO操作,所以单独的线程可以管理多个输入和输出通道。因此, NIO 可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端
2 选择器
选择器的作用是监控这些通道的IO状况(读,写,连接,接收数据的情况等状况),通道注册到选择器上,选择器监控通道,当某一通道,某一个事件就绪之后,选择器才会将这个通道分配到服务器端的一个或多个线程上,再继续运行。例如客户端需要发送数据给服务器端,只当客户端所有的数据都准备完毕后,选择器才会将这个注册的通道分配到服务器端的一个或多个线程上。而在客户端准备数据的这段时间,服务器端的线程可以执行别的任务
3 使用NIO完成网络通信的三个核心
通道(Channel):负责连接
java.mio.channels.Channel 接口:
|-- SelectableChannel
|--SocketChannel -- TCP
|--ServerSocketChannel -- TCP
|--DatagramChannel -- UDP
|--Pipe.SinkChannel
|--Pipe.sourceChannel
缓冲区(Buffer):负责数据的存取
选择器(Select):是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况
@Test
public void client() throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",7498));
// 切换成非 阻塞模式
socketChannel.configureBlocking(false);
FileChannel inputChannel = FileChannel.open(Paths.get("G:\\notes\\nio\\01_简介\\学习使用NIO.md"), StandardOpenOption.READ);
ByteBuffer clientBuffer = ByteBuffer.allocate(1024);
while (inputChannel.read(clientBuffer) != -1){
clientBuffer.flip();
socketChannel.write(clientBuffer);
clientBuffer.clear();
}
socketChannel.close();
inputChannel.close();
}
@Test
public void server() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 非阻塞
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(7498));
FileChannel outputChannel = FileChannel.open(Paths.get("C:\\Users\\admin\\Desktop\\test.md"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);
// 选择器
Selector selector = Selector.open();
// 将通道注册到选择器上,并制定监听事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 轮巡式获得选择器里的已经准备就绪的事件
while (selector.select() > 0 ){
// 获取已经就绪的监听事件
Iterator<SelectionKey> selectorIterator = selector.selectedKeys().iterator();
// 迭代获取
while (selectorIterator.hasNext()){
// 获取准备就绪的事件
SelectionKey key = selectorIterator.next();
SocketChannel socketChannel = null;
// 判断是什么事件
if (key.isAcceptable()){
// 或接受就绪,,则获取客户端连接
socketChannel = serverSocketChannel.accept();
//切换非阻塞方式
socketChannel.configureBlocking(false);
// 注册到选择器上
socketChannel.register(selector,SelectionKey.OP_READ);
} else if (key.isReadable()){
// 获取读就绪通道
SocketChannel readChannel = (SocketChannel) key.channel();
readChannel.configureBlocking(false);
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int len = 0;
while ( (len = readChannel.read(readBuffer)) != -1){
readBuffer.flip();
System.out.println(new String(readBuffer.array(),0,len));
outputChannel.write(readBuffer);
readBuffer.clear();
}
readChannel.close();
outputChannel.close();
}
}
// 取消选择键
selectorIterator.remove();
/**
* 这里就像评论中所说的那样,
* “serverSocketChannel.close();不用关闭的,这是服务器端”
* 没有及时更正,抱歉
*/
// serverSocketChannel.close();
}
}