BIO
BIO即阻塞式IO,阻塞式IO的交互方式是同步阻塞方式,当一个Java线程在读入输入流或者写入输出流时,在读写动作完成之前,线程一直会被阻塞。
BIO是非常消耗资源的:
- 服务端:服务端有一个接收器一直处于阻塞状态等待新的客户端连接请求,每当有新的客户端请求连接时,都需要创建新的线程或者从线程池中获取线程来处理请求。当请求量增大,线程数过高时,线程的频繁切换会带来严重的问题,它会导致系统负载偏高。
- 客户端:服务端需要一个线程一直阻塞等待客户端的连接请求,若服务端没有及时返回请求结果而会一直等待。
NIO
Java的非阻塞IO是通过Channel、Buffer和Selector三个组件实现,通过这些组件,可以实现同步非阻塞的多路复用I/O应用程序。
NIO的三大组件:
-
Buffer(缓冲区):Buffer本质上是一个可读写数据的内存块,可以理解成是一个数据容器对象(含数组),Buffer提供了一组方法,可以轻松地使用内存块。Java中有多个Buffer实现类,其中最核心的ByteBuffer。
-
Channel(通道):Channel是一个双向的数据读写通道,可以对Buffer进行读写操作,是数据的来源和数据写入的目的地。Channel读操作
channel.read(buffer)
是将Channel中的数据填充到Buffer中,而写操作channel.write(buffer)
是将Buffer中的数据写入到Channel中。Java中有四种Channel,分别为FileChannel(文件通道,用于文件的读和写)、DatagramChannel(用于 UDP 连接的接收和发送)、SocketChannel(TCP 客户端通道)、ServerSocketChannel(TCP服务端通道,用于监听某个端口进来的请求)。 -
Selector(选择器):Channel可以注册到Selector上,Selector通过不断轮询注册的Channel来选择并分发已处理就绪的事件,一共有四种事件,SelectionKey.OP_READ(读事件,通道中可以读取数据)、SelectionKey.OP_WRITE(写事件,可以向通道中写入数据)、SelectionKey.OP_CONNECT(连接事件,成功建立连接)、SelectionKey.OP_ACCEPT(接收连接事件,接受到连接请求)
整个NIO的过程为:当一个客户端连接到来时,服务端会为这个新的连接创建一个Channel,并将这个Channel注册到Selector上,Selector会监听所有注册到自己身上的Channel,一旦某个Channel的数据状态发生变化,比如数据读取完毕,则会触发相关的事件,Channel的数据读取和写入都只与Buffer进行交互。
NIO服务端实现:
public class ServerExample {
private Selector selector;
private static final int SOCKET_PORT = 5555;
private final int port;
public ServerExample(){
this(SOCKET_PORT);
}
public ServerExample(int port){
this.port = port;
}
public void start() throws Exception{
// TCP服务端通道,用于监听某个端口进来的请求
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置Socket为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 获取与该Channel关联的Socket,并绑定监听端口
serverSocketChannel.socket().bind(new InetSocketAddress(port));
// 获取一个Selector
selector = Selector.open();
// 注册Channel到Selector,并对建立连接OP_ACCEPT事件监听
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
// 阻塞等待,获取就绪的事件集合
int readyChannels = selector.select();
if (readyChannels == 0){
continue;
}
// 获取就绪的事件集合
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
iterator.remove();
handleEvent(selectionKey);
}
}
}
private void handleEvent(SelectionKey selectionKey) throws Exception{
SocketChannel socketChannel;
// 连接时间处理
if (selectionKey.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel)selectionKey.channel();
socketChannel = server.accept();
if (socketChannel == null){
return;
}
// 设置该socketChannel为非阻塞模式
socketChannel.configureBlocking(false);
// 有新的连接并不代表这个通道就有数据,将Channel注册到Selector上,并监听读事件
socketChannel.register(selector, SelectionKey.OP_READ);
}else if (selectionKey.isReadable()){
socketChannel = (SocketChannel) selectionKey.channel();
// 初始化一个大小为1024的ByteBuffer
ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
// 将channel中的数据写入到buffer中
int count = socketChannel.read(receiveBuffer);
if (count > 0){
// 打印buffer中的数据
String content = new String(receiveBuffer.array()).trim();
System.out.println("收到数据:" + content);
// 初始化一个byteBuffer
ByteBuffer sendBuffer = ByteBuffer.wrap(("Server已收到:" + content).getBytes());
// 返回给客户端数据
socketChannel.write(sendBuffer);
}else if (count == -1){
// -1 代表连接已经关闭
System.out.println("客户端已断开链接");
socketChannel.close();
}
}
}
public static void main(String[] args) throws Exception{
ServerExample serverExample = new ServerExample();
serverExample.start();
}
}
NIO客户端实现
public class ClientExample {
private final String serverHost;
private final int serverPort;
private Selector selector;
private SelectionKey selectionKey;
private SocketChannel client;
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
private volatile boolean isOver = false;
public ClientExample(String serverHost, int serverPort) {
this.serverHost = serverHost;
this.serverPort = serverPort;
}
public void connect() {
try {
// 获取一个客户端Socket通道
SocketChannel socketChannel = SocketChannel.open();
// 设置非阻塞模式
socketChannel.configureBlocking(false);
// 获取一个Selector
selector = Selector.open();
// 注册客户端到Selector,并监听连接完成事件
selectionKey = socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 发起连接
socketChannel.connect(new InetSocketAddress(serverHost, serverPort));
// 异步执行事件
executorService.execute(this::handleEvent);
} catch (IOException e) {
e.printStackTrace();
}
}
private void handleEvent() {
try {
while (true) {
if (isOver){
break;
}
// 阻塞等待,获取就绪的事件集合
int readyChannels = selector.select();
if (readyChannels == 0) {
continue;
}
// 获取就绪的事件集合
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
if (selectionKey.isConnectable()) {
client = (SocketChannel) selectionKey.channel();
} else if (selectionKey.isReadable()) {
client = (SocketChannel) selectionKey.channel();
// 初始化一个大小为1024的ByteBuffer
ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
// 将channel中的数据写入到buffer中
int count = client.read(receiveBuffer);
if (count > 0) {
// 打印buffer中的数据
String content = new String(receiveBuffer.array()).trim();
System.out.println("收到数据:" + content);
}
isOver = true;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void request() throws Exception {
// 等待客户端Socket
while (client == null || !client.finishConnect()){
Thread.sleep(1000);
}
// 初始化一个byteBuffer
ByteBuffer sendBuffer = ByteBuffer.wrap(("hello server!").getBytes());
// 返回给客户端数据
client.write(sendBuffer);
// 设置读监听感兴趣
selectionKey.interestOps(SelectionKey.OP_READ);
}
public void close()throws Exception{
while (!isOver){
Thread.sleep(1000);
}
client.close();
executorService.shutdownNow();
System.out.println("exit");
}
public static void main(String[] args) throws Exception{
ClientExample clientExample = new ClientExample("127.0.0.1", 5555);
clientExample.connect();
clientExample.request();
clientExample.close();
}
}
AIO
AIO提供了异步非阻塞I/O操作方式,异步I/O是基于事件和回调机制实现的,也就是应用程序发起请求之后会直接返回,不会阻塞,当后台处理完成时,操作系统会通知相应的线程执行后续操作。
AIO提供了两种使用方式:
- 将来式:Java提供Future类实现将来式,和JDK的FutureTask使用方式一样,将执行任务交给线程池执行后,执行任务的线程不会阻塞,而是获得一个Future对象,可以使用get()方法阻塞等待任务完成并获取结果。
- 回调式:Java提供了CompletionHandler作为回调接口,在调用read()、write()等方法时,可以传入CompletionHandler的实现作为事件完成的回调接口。
AsynchronousServerSocketChannel对应的是非阻塞IO的ServerSocketChannel,AIO服务端实现为:
public class ServerExample {
private static final int SOCKET_PORT = 5555;
private final int port;
public ServerExample() {
this(SOCKET_PORT);
}
public ServerExample(int port) {
this.port = port;
}
public void start() throws Exception {
// 实例化,并监听端口
AsynchronousServerSocketChannel server =
AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(SOCKET_PORT));
// accept、read和write方法都有一个attachment参数,该参数可以作为上下文传递一些自定义参数
server.accept(new Object(), new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel client, Object object) {
try {
SocketAddress clientAddr = client.getRemoteAddress();
System.out.println("收到新的连接:" + clientAddr);
// 收到新的连接后,server重新调用accept方法等待新的连接进来
server.accept(object, this);
// read和write方法都可以设置CompletionHandler
ByteBuffer buffer = ByteBuffer.allocate(2048);
client.read(buffer, true, new CompletionHandler<Integer, Boolean>() {
public void completed(Integer result, Boolean isReadMode) {
if (isReadMode) {
// 读取来自客户端的数据
buffer.flip();
byte bytes[] = new byte[buffer.limit()];
buffer.get(bytes);
String msg = new String(buffer.array()).trim();
System.out.println("收到来自客户端的数据: " + msg);
// 响应客户端请求,返回数据
buffer.clear();
buffer.put("Response from server!".getBytes(Charset.forName("UTF-8")));
buffer.flip();
// 写数据到客户端
client.write(buffer, false, this);
} else {
// 到这里,说明往客户端写数据也结束了,有以下两种选择:
// 1. 继续等待客户端发送新的数据过来
// buffer.clear();
// client.read(buffer, true, this);
// 2. 断开这次的连接
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void failed(Throwable t, Boolean att) {
System.out.println("连接断开");
}
});
} catch (IOException ex) {
ex.printStackTrace();
}
}
@Override
public void failed(Throwable t, Object object) {
System.out.println("accept failed");
}
});
// 防止main线程退出
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
ServerExample serverExample = new ServerExample();
serverExample.start();
}
}
AsynchronousSocketChannel对应的是非阻塞IO的SocketChannel,AIO客户端实现为:
public class ClientExample {
private final String serverHost;
private final int serverPort;
public ClientExample(String serverHost, int serverPort) {
this.serverHost = serverHost;
this.serverPort = serverPort;
}
public void connect() throws Exception {
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
// Future形式
Future<?> future = client.connect(new InetSocketAddress(serverHost, serverPort));
// 阻塞等待连接成功
future.get();
ByteBuffer buffer = ByteBuffer.allocate(2048);
byte[] data = "I am client!".getBytes();
buffer.put(data);
buffer.flip();
// 异步发送数据到服务端
client.write(buffer, false, new CompletionHandler<Integer, Boolean>() {
public void completed(Integer result, Boolean isReadMode) {
if (isReadMode) {
// 读取来自服务端的数据
buffer.flip();
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
String msg = new String(bytes, Charset.forName("UTF-8"));
System.out.println("收到来自服务端的响应数据: " + msg);
// 有以下两种选择:
// 1. 向服务端发送新的数据
// buffer.clear();
// String newMsg = "new message from client";
// byte[] data = newMsg.getBytes(Charset.forName("UTF-8"));
// buffer.put(data);
// buffer.flip();
// client.write(buffer, false, this);
// 2. 关闭连接
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
} else {
// 写操作完成后,读取数据
buffer.clear();
client.read(buffer, true, this);
}
}
public void failed(Throwable t, Boolean isReadMode) {
System.out.println("服务器无响应");
}
});
// 这里休息一下再退出,给出足够的时间处理数据
Thread.sleep(2000);
}
public static void main(String[] args) throws Exception {
ClientExample clientExample = new ClientExample("127.0.0.1", 5555);
clientExample.connect();
}
}