课程《一站式学习Java网络编程 全面理解BIO/NIO/AIO》的学习笔记(五):
异步调用机制 & AIO编程模型 & 基于AIO的多人聊天室实现
源码地址:https://github.com/NoxWang/web-program
【Java网络编程】基于BIO/NIO/AIO的多人聊天室(一):java IO与内核IO
【Java网络编程】基于BIO/NIO/AIO的多人聊天室(二):BIO聊天室
【Java网络编程】基于BIO/NIO/AIO的多人聊天室(三):NIO概述与实践
【Java网络编程】基于BIO/NIO/AIO的多人聊天室(四):NIO聊天室
【Java网络编程】基于BIO/NIO/AIO的多人聊天室(六):思维导图
一、异步调用机制
1.1 AIO中的异步操作
AsynchronousSocketChannel
:客户端Socket通道AsynchronousServerSocketChannel
:服务端Socket通道connect / accept
:建立连接(支持异步操作)read / write
:读写操作(支持异步操作)
1.2 异步调用机制
1.2.1 Future
Channel异步调用connect / accept,read / write等方法,方法返回一个Future
对象。获得Future对象后,我们可以对它进行如下操作:
- 调用Future的
get()
方法:该方法是阻塞式调用,它会一直阻塞,直到Future所对应的任务完成并返回。 - 调用Future的
isDone()
方法:再一个循环中调用该方法,不停询问任务是否已完成,可设置超时时间。
1.2.2 CompletionHandler
Channel异步调用connect / accept,read / write等方法时,将一个CompletionHandler
作为参数传入。
CompletionHandler是一个接口,它提供两个回调函数(callback),completed()
和failed()
,我们可以自己实现这两个方法。当异步操作完成后,系统会调用回调函数(操作成功则调用completed(),发生异常则调用failed()),完成相关业务功能。
二、AIO编程模型
2.1 AsynchronousChannelGroup
我们使用的异步通道属于一个AsynchronousChannelGroup,即异步通道组,是一组可以被多个异步通道共享的资源群组。Group可以自定义,如果不特别指定,系统会使用默认群组。
需要ChannelGroup的原因:AIO机制下,操作系统帮我们做了很多事,比如当调用完成时,操作系统会自动回调函数。操作系统完成这些功能需要一定的系统资源,比如线程池。Group中会包含线程池等资源,操作系统可以复用线程池中的线程来实现回调函数等功能。
2.2 异步实现
- 创建AsynchronousServerSocketChannel后,需要创建一个
AcceptHandler
,用于处理accept()的异步调用,Client1连接后,会触发该Handler; - AcceptHandler会获得连接后返回的AsynrounousSocketChannel。服务端Channel需要进行读写操作,这些操作也是异步调用的,因此需要再创建一个
ClientHandler
用于处理客户端的读写调用; - 每当有新客户端连接,都会再次触发AcceptHandler,完成相应操作(即为新的客户端Channel注册一个ClientHandler)
- 当某个客户端有IO操作,会触发对应的ClientHandler,完成特定的业务操作
三、基于AIO的多人聊天室实现
3.1 服务端
在服务端,我们使用CompletionHandler
来实现异步调用
package server;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ChatServer {
private static final String LOCALHOST = "localhost";
private static final int DEFAULT_PORT = 8888;
private int port;
private static final int BUFFER_SIZE = 1024;
private static final int THREADPOOL_SIZE = 8;
private static final String QUIT = "\\quit";
private Charset charset = Charset.forName("UTF-8");
private AsynchronousChannelGroup channelGroup;
private AsynchronousServerSocketChannel serverChannel;
private List<ClientHandler> connectedClients;
public ChatServer() {
this(DEFAULT_PORT);
}
public ChatServer(int port) {
this.port = port;
this.connectedClients = new ArrayList<>();
}
private boolean readyToQuit(String msg) {
return QUIT.equals(msg);
}
private void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 服务端主逻辑
*/
private void start() {
try {
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(THREADPOOL_SIZE);
// 创建自定义线程池的ChannelGroup
channelGroup = AsynchronousChannelGroup.withThreadPool(executorService);
// 创建自定义ChannelGroup的异步服务端Channel
serverChannel = AsynchronousServerSocketChannel.open(channelGroup);
serverChannel.bind(new InetSocketAddress(LOCALHOST, port));
System.out.println("启动服务器,监听端口: " + port + "...");
while (true) {
serverChannel.accept(null, new AcceptHandler());
System.in.read();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
close(serverChannel);
}
}
private class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Object> {
@Override
public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {
// 服务端持续监听客户端请求
if (serverChannel.isOpen()) {
serverChannel.accept(null, this);
}
if (clientChannel != null && clientChannel.isOpen()) {
ClientHandler handler = new ClientHandler(clientChannel);
// 添加用户至在线列表
addClient(handler);
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
clientChannel.read(buffer, buffer, handler);
}
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("连接失败: " + exc);
}
}
private synchronized void addClient(ClientHandler handler) {
connectedClients.add(handler);
System.out.println(getClientName(handler.clientChannel) + "已连接");
}
private synchronized void removeClient(ClientHandler handler) {
connectedClients.remove(handler);
System.out.println(getClientName(handler.clientChannel) + "已断开");
close(handler.clientChannel);
}
private class ClientHandler implements CompletionHandler<Integer, Object> {
AsynchronousSocketChannel clientChannel;
public ClientHandler (AsynchronousSocketChannel channel) {
this.clientChannel = channel;
}
@Override
public void completed(Integer result, Object attachment) {
ByteBuffer buffer = (ByteBuffer) attachment;
if (buffer != null) { // 说明此时需要处理的是读
if (result <= 0) {
// 客户端异常,移出在线列表
removeClient(this);
} else {
// 获取并打印客户端发送来的信息
buffer.flip();
String fwdMsg = receive(buffer);
// 用户准备退出
if (readyToQuit(fwdMsg)) {
removeClient(this);
return;
}
System.out.println(getClientName(clientChannel) + ": " + fwdMsg);
// 给其他客户端发送消息
forwardMessage(clientChannel, fwdMsg);
buffer.clear();
// 持续监听该客户端channel的输入
clientChannel.read(buffer, buffer, this);
}
}
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("读写失败:" + exc);
}
}
private synchronized void forwardMessage(AsynchronousSocketChannel clientChannel, String fwdMsg) {
for (ClientHandler handler : connectedClients) {
if (!clientChannel.equals(handler.clientChannel)) {
try {
ByteBuffer buffer = charset.encode(getClientName(clientChannel) + ": " + fwdMsg);
handler.clientChannel.write(buffer, null, handler);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private String getClientName(AsynchronousSocketChannel clientChannel) {
int clientPort = -1;
try {
InetSocketAddress address = (InetSocketAddress) clientChannel.getRemoteAddress();
clientPort = address.getPort();
} catch (IOException e) {
e.printStackTrace();
}
return "客户端[" + clientPort + "]";
}
private String receive(ByteBuffer buffer) {
CharBuffer charBuffer = charset.decode(buffer);
return String.valueOf(charBuffer);
}
public static void main(String[] args) {
ChatServer chatServer = new ChatServer();
chatServer.start();
}
}
3.2 客户端
在客户端,我们使用Future
来实现异步调用
ChatClient.java
package client;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class ChatClient {
private static final String LOCALHOST = "localhost";
private String host;
private static final int DEFAULT_PORT = 8888;
private int port;
private static final int BUFFER_SIZE = 1024;
private static final String QUIT = "\\quit";
private Charset charset = Charset.forName("UTF-8");
private AsynchronousSocketChannel clientChannel;
public ChatClient() {
this(LOCALHOST, DEFAULT_PORT);
}
public ChatClient (String host, int port) {
this.host = host;
this.port = port;
}
public boolean readyToQuit(String msg) {
return QUIT.equals(msg);
}
public void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void start() {
try {
// 创建客户端channel
clientChannel = AsynchronousSocketChannel.open();
// 连接服务端,异步调用
Future<Void> future = clientChannel.connect(new InetSocketAddress(host, port));
future.get();
// 处理用户输入
new Thread(new UserInputHandler(this)).start();
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
while (true) {
Future<Integer> readResult = clientChannel.read(buffer);
int result = readResult.get();
if (result <= 0) {
// 说明出现异常,无法再从服务器得到有效信息
System.out.println("服务器断开");
close(clientChannel);
System.exit(1);
} else {
buffer.flip();
String msg = String.valueOf(charset.decode(buffer));
buffer.clear();
System.out.println(msg);
}
}
} catch (IOException | InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
public void send(String msg) {
if (msg.isEmpty()) {
return;
}
ByteBuffer buffer = charset.encode(msg);
Future<Integer> writeResult = clientChannel.write(buffer);
try {
writeResult.get();
} catch (InterruptedException | ExecutionException e) {
System.out.println("发送消息失败");
e.printStackTrace();
}
}
public static void main(String[] args) {
ChatClient client = new ChatClient();
client.start();
}
}
UserInputHandler.java
package client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class UserInputHandler implements Runnable {
private ChatClient chatClient;
public UserInputHandler (ChatClient client) {
this.chatClient = client;
}
@Override
public void run() {
try {
BufferedReader consoleReader = new BufferedReader(
new InputStreamReader(System.in)
);
String msg = null;
while ((msg = consoleReader.readLine()) != null) {
chatClient.send(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}