NIo(Non-Block IO)是java1.4以后提供的API,它是一个异步,面向缓冲区的IO,遵循React线程模型。
NIO与BIO的区别
-
NIO是非阻塞的IO。NIO在进行读操作时,如果当前没有可读数据,则不会堵塞在这里,而是继续向下执行,进行写操作时也不会等待数据全部写入,而是可以继续执行。BIO即我们以前使用的IO流,它是一个同步阻塞的IO流。它在进行读操作时,必须读取到有效数据才会继续执行,否则就堵塞,同样的进行写操作时也必须等待数据全部写入,才能袭击执行。
-
NIO是通过缓冲区读写数据,BIO则是通过流来读写数据。
-
NIO只使用一个Channel来读写数据,BIO则必须通过两个读写流来读写数据。
NIO基本概念
-
Channel:IO通道,配合缓冲区可以进行读写操作。它与流的不同之处在于它是全双工的,可读可写,因此它比流更好映射底层系统的API
-
Selector:多路复用器,用于监听读写连接事件,是NIO 的核心组件之一。它会不断轮询已注册的channel,一旦发生监听的事件就会将事件放入selectorkey中,
-
Buffer:缓冲区,NIO中都是通过缓冲区来进行读取写入的。它本质上是一个数组,内部除了数组外还维护了读写位置的信息。
ByteBuffer的重要属性
属性 | 作用 |
---|---|
Capacity | 容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变 |
Limit | 在写模式下表示最多能写入多少数据,此时和capacity相同。在读模式下表示最多能读多少数据,此时和缓存区的实际数据大小相同。 |
Position | 位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变改值,为下次读写作准备 |
Mark | 标记,调用mark()来设置mark=position,再调用reset()可以让position恢复到标记的位置 |
NIO服务器客户端通信例子
NIO 服务端
package FrameWork.com.nio.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
/**
* Created by forget on 2019/11/26.
*/
public class NioServer implements Runnable{
private ServerSocketChannel serverSocketChannel;
private String host="localhost";
private Selector selector;
private int port=8011;
public NioServer() {
}
public NioServer(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws IOException {
selector=Selector.open();
serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(host,port));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
@Override
public void run() {
try {
start();
System.out.println("开启服务器,地址是"+host+":"+port);
} catch (IOException e) {
e.printStackTrace();
}
while (true){
try {
if(selector.select(1000)==0)
{
System.out.print(".");
continue;
}
Iterator<SelectionKey> keys=selector.selectedKeys().iterator();
SelectionKey key=null;
while (keys.hasNext()){
key=keys.next();
keys.remove();
if(key.isValid())
{
if(key.isReadable())
{
SocketChannel socketChannel= (SocketChannel) key.channel();
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
int l=socketChannel.read(byteBuffer);
if(l>0)
{
byteBuffer.flip();
byte[] bytes=new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
String body=new String(bytes,"UTF-8");
System.out.println("消息内容:"+body);
write("你好,客户端,收到消息:"+body,socketChannel);
}
else if(l<0)
{
key.cancel();
socketChannel.close();
}
}
if(key.isAcceptable())
{
ServerSocketChannel socketChannel= (ServerSocketChannel) key.channel();
SocketChannel sc=socketChannel.accept();
sc.configureBlocking(false);
sc.register(selector,SelectionKey.OP_READ);
}
}
}
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
}
public void write(String message,SocketChannel socketChannel) throws IOException {
byte [] bytes=message.getBytes();
ByteBuffer byteBuffer=ByteBuffer.allocate(bytes.length);
byteBuffer.put(bytes);
byteBuffer.flip();
socketChannel.write(byteBuffer);
}
}
NIO的ServerSocketChannel 对应的就是BIO的ServerSocket,此处我们先获取一个ServerSocketChannel 对象,绑定地址,将channel配置为非阻塞模式,register方法将channel注册到selector多路复用器上监听客户端的连接请求。Selector不断轮询Channel查看是否有就绪的事件。处理连接事件,配置客户端Socket参数,注册到Selector监听可读事件,处理读取事件,通过缓冲区异步读取数据,调用flip方法切换缓冲区至读模式。
package FrameWork.com.nio.client;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
/**
* Created by forget on 2019/11/26.
*/
public class Client implements Runnable{
private Selector selector;
private SocketChannel socketChannel;
public Client() throws IOException {
selector=Selector.open();
socketChannel=SocketChannel.open();
}
public void start() throws IOException {
socketChannel.configureBlocking(false);
boolean res=socketChannel.connect(new InetSocketAddress("localhost",8011));
if(res)
{
System.out.println("连接就绪");
socketChannel.register(selector, SelectionKey.OP_READ);
}
else {
socketChannel.register(selector,SelectionKey.OP_CONNECT);
}
}
@Override
public void run() {
try {
start();
} catch (IOException e) {
e.printStackTrace();
}
while(true){
try {
selector.select(1000);
Iterator<SelectionKey> keys=selector.selectedKeys().iterator();
SelectionKey key=null;
while (keys.hasNext())
{
key=keys.next();
keys.remove();
if(key.isValid())
{
if(key.isReadable())
{
SocketChannel socketChannel= (SocketChannel) key.channel();
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
int l=socketChannel.read(byteBuffer);
if(l>0)
{
byteBuffer.flip();
byte [] bytes=new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
String body=new String(bytes,"UTF-8");
System.out.println("消息大小:"+bytes.length);
System.out.println("收到服务器消息:"+body);
}
else if(l<0)
{
key.cancel();
socketChannel.close();
}
}
if(key.isConnectable())
{
SocketChannel sc= (SocketChannel) key.channel();
if(sc.finishConnect())
{
sc.register(selector,SelectionKey.OP_READ);
System.out.println("连接服务器成功");
write("hello world!",socketChannel);
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
}
public void write(String message, SocketChannel serverSocketChannel) throws IOException {
byte [] bytes=message.getBytes();
ByteBuffer byteBuffer=ByteBuffer.allocate(bytes.length);
byteBuffer.put(bytes);
byteBuffer.flip();
serverSocketChannel.write(byteBuffer);
}
public Selector getSelector() {
return selector;
}
public void setSelector(Selector selector) {
this.selector = selector;
}
public SocketChannel getSocketChannel() {
return socketChannel;
}
public void setSocketChannel(SocketChannel socketChannel) {
this.socketChannel = socketChannel;
}
}
NIO的SocketChannel 代表BIO的Socket,首先打开SocketChannel ,配置为非阻塞模式,连接服务器,判断是否连接成功,成功则向Selector注册读取事件,连接尚未成功则注册连接事件。Selector轮询Channel,查看是否有就绪的事件,如果是连接事件则获取SocketChannel注册读取事件到Selector,读取事件则通过缓冲区异步读取数据。
启动服务端和客户端测试效果
public class App
{
public static void main( String[] args )
{
Thread thread=new Thread(new NioServer());
thread.start();
}
}
public class TestClient {
public static void main( String[] args ) throws IOException {
Client client=new Client();
Thread thread=new Thread(client);
thread.start();
Scanner scanner=new Scanner(System.in);
String message=null;
while(true)
{
message=scanner.nextLine();
ByteBuffer byteBuffer=ByteBuffer.allocate(message.getBytes().length);
byteBuffer.put(message.getBytes());
byteBuffer.flip();
client.getSocketChannel().write(byteBuffer);
}
}
}
结果如下: