s1.NIO概述
首先大家形成一个共识,那就是在我们讨论IO模型的时候,目前来说都是针对网络编程而言,因为网络编程才有大规模的连接,IO输入输出,NIO出现的目的也正是为了解决BIO网络编程模型中大量连接产生大量线程的痛点。
IO模型一般包含三种
BIO,同步阻塞IO,参考下图BIO的线程模型
NIO,同步非阻塞IO
AIO,异步非阻塞IO
2.NIO核心API
2.1Channel
channel与IO流的区别:
channel即可以读,也可以写,IO流输入输出是不同的流,且输入后必须同步的输出
channel读写可以在不同的线程执行,即异步执行,这是最大的区别
公共API
//从通道读数据到buffer channel->buffer
int read(ByteBuffer var1);
//从buffer写数据到通道 buffer->channel
int write(ByteBuffer var1);
//关闭通道
final void close() throws IOException;
1.ServerSocketChannel
//打开一个server通道
static ServerSocketChannel open() throws IOException;
//给当前server通道绑定一个网络地址监听
final ServerSocketChannel bind(SocketAddress var1) throws IOException;
//监听客户端的socket连接, 此方法默认为阻塞,configureBlocking设置为false,则不阻塞
SocketChannel accept();
//设置false则为NIO非阻塞模式,默认true即是与BIO一样为阻塞模式
final SelectableChannel configureBlocking(boolean var1) throws IOException;
//把当前通道注册到selecor上,var2表示注册事件类型,var3可以附加一个任意对象
final SelectionKey register(Selector var1, int var2, Object var3);
//获取当前server通道监听的网络地址
SocketAddress getLocalAddress() throws IOException;
//是否阻塞模式
boolean isBlocking();
//是否已注册到某个selector上
boolean isRegistered();
代码示例:
public static void main(String[] args) throws Exception {
String host = "127.0.0.1";
int port = 9991;
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open().bind(new InetSocketAddress(host, port));
serverSocketChannel.configureBlocking(false);
SocketAddress localAddress = serverSocketChannel.getLocalAddress();
System.out.println(serverSocketChannel.isBlocking());
System.out.println(serverSocketChannel.isRegistered());
System.out.println(localAddress);
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println(socketChannel);
}
2.SocketChannel
//打开一个通道
static SocketChannel open() throws IOException;
//为当前通道绑定一个网络地址监听
SocketChannel bind(SocketAddress local) throws IOException;
//与服务端通道建立连接,此方法默认为阻塞,configureBlocking设置为false,则不阻塞
boolean connect(SocketAddress remote) throws IOException;
//非阻塞模式下调用此方法来判断连接是否建立成功,此方法内部为阻塞模式,此方法的意义在于发起建立连接请求以后,有一定的延时,这段时间可以做一些事情,当事情做完后,再调用此方法来判断连接是否建立完成
boolean finishConnect() throws IOException;
//此方法内部只是返回一个是否连接中的状态标识
boolean isConnected();
//设置false则为NIO非阻塞模式,默认true即是与BIO一样为阻塞模式
final SelectableChannel configureBlocking(boolean var1) throws IOException;
//打开一个通道并与服务端通道建立连接,建立连接的过程为阻塞模式
static SocketChannel open(SocketAddress var0) throws IOException;
//获取当前通道监听的网络地址
SocketAddress getLocalAddress() throws IOException;
//获取服务端的网络地址
SocketAddress getRemoteAddress() throws IOException;
代码示例:
public static void main(String[] args) throws IOException {
String host = "127.0.0.1";
int port = 9992;
String serverHost = "127.0.0.1";
int serverPort = 18001;
SocketChannel socketChannel = SocketChannel.open();
socketChannel.bind(new InetSocketAddress(host, port));
socketChannel.configureBlocking(false);
boolean connected = socketChannel.connect(new InetSocketAddress(serverHost, serverPort));
System.out.println(connected);
System.out.println(socketChannel.isConnected());
System.out.println(socketChannel.finishConnect());
}
3.FileChannel
//打开一个磁盘文件的IO通道,OpenOption为对该通道的操作类型,包括读、写、删除
static FileChannel open(Path var0, OpenOption... var1);
//通道中数据的字节大小
long size();
//把通道中的数据映射到buffer中
MappedByteBuffer map(MapMode var1, long var2, long var4);
//把当前通道中的数据copy到目标通道var5
long transferTo(long var1, long var3, WritableByteChannel var5);
//把目标通道var1中的数据copy到当前通道
long transferFrom(ReadableByteChannel var1, long var2, long var4);
代码示例:
public static void copyFile() {
FileChannel source = null;
FileChannel target = null;
try {
source = FileChannel.open(Paths.get("C:\\Users\\Lenovo\\Desktop\\shell.txt"));
target = FileChannel.open(Paths.get("C:\\Users\\Lenovo\\Desktop\\shell2.txt"), StandardOpenOption.WRITE,
StandardOpenOption.CREATE);
source.transferTo(0, source.size(), target);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
source.close();
target.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2ByteBuffer
Buffer中核心的几个属性
private int position = 0; //当前操作位,无论读写都会+1
//limit表示当前操作无论读写操作的次数,初次构建buffer的时候,该值<=capacity
private int limit;
private int capacity; //容量,创建一个buffer时,必须指定容量,即每次读写最大上限
//创建一个容量大小为capacity的字节缓冲
static ByteBuffer allocate(int capacity);
//首先创建一个等容的缓存,再把字节数组写到缓冲
static ByteBuffer wrap(byte[] var0);
//获取一个字节
byte get();
//添加一个字节
ByteBuffer put(byte var1);
//把缓冲中的字节数组直接返回,注意不是深clone
final byte[] array();
//buffer中是否还有未读取的数据
final boolean hasRemaining();
//每次从buffer中读数据之前,先flip一下,因为读之前,肯定发生过写,因此需要把position置为0,limit置为写的时候写到的位置
public final Buffer flip() {
this.limit = this.position;
this.position = 0;
this.mark = -1;
return this;
}
//每次从buffer中读完数据之后,必须clear一下,把position再次置为0,limit置为capacity,接下来可能要继续向buffer中写数据了
public final Buffer clear() {
this.position = 0;
this.limit = this.capacity;
this.mark = -1;
return this;
}
代码示例:
注意:以下代码中当read==-1,即表示提供数据的channel通道已断开,read==0表示通道数据已读完,read > 0表示可以继续从通道读取数据
public static String readBuffer(SocketChannel channel, ByteBuffer buffer) throws IOException {
List<Byte> list = new ArrayList<>();
int read = channel.read(buffer);
while (read > 0) {
buffer.flip();
while (buffer.hasRemaining()) {
list.add(buffer.get());
}
buffer.clear();
read = channel.read(buffer);
}
if (read == -1) {
throw new ClosedChannelException();
}
byte[] bytes = new byte[list.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = list.get(i);
}
return new String(bytes);
}
2.3Selector
// 获取所有的事件key
public abstract Set<SelectionKey> keys();
//获取已经准备好的所有事件key,注意与keys方法区别
public abstract Set<SelectionKey> selectedKeys();
//非阻塞返回就绪的事件个数
public abstract int selectNow() throws IOException;
//阻塞var1毫秒返回就绪的事件个数
public abstract int select(long var1) throws IOException;
//阻塞到有事件就绪后返回
public abstract int select() throws IOException;
2.4SelectionKey
//在事件上注册的通道
SelectableChannel channel();
//当前事件所在的选择器
Selector selector();
//取消事件,注意该方法与从selector的keys中remove都要执行才会真正的取消事件
void cancel();
//附加一个对象
final Object attach(Object var1);
//获取附加的对象
final Object attachment();
final boolean isReadable(); //读事件
final boolean isWritable(); //写事件
final boolean isAcceptable(); //接收连接事件
final boolean isConnectable(); //连接事件
3. NIO模拟向服务端发起请求并接收一个响应代码示例
1.buffer工具类
public class BufferUtils {
public static String readBuffer(SocketChannel channel, ByteBuffer buffer) throws IOException {
return readBuffer(channel, buffer, false);
}
/**
* @Description: 从通道读取数据
* @param channel 一个SocketChannel通道
* @param buffer 缓冲对象
* @param checkChannelClosed 是否检测通道是否断开
* @return 返回读取的数据字符串
* @throws IOException
*/
public static String readBuffer(SocketChannel channel, ByteBuffer buffer, boolean checkChannelClosed)
throws IOException {
List<Byte> list = new ArrayList<>();
int read = channel.read(buffer);
while (read > 0) {
buffer.flip();
while (buffer.hasRemaining()) {
list.add(buffer.get());
}
buffer.clear();
read = channel.read(buffer);
}
if (read == -1 && checkChannelClosed) {
throw new ClosedChannelException();
}
byte[] bytes = new byte[list.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = list.get(i);
}
return new String(bytes);
}
}
2.Server端
public static void main(String[] args) throws Exception {
String host = "127.0.0.1";
int port = 9991;
Server server = Server.createServer(host, port);
server.start(1000, 64);
}
// NIOServer对象
static class Server {
// serverSocketChannel
private ServerSocketChannel serverSocketChannel;
/**
* @Description: 创建一个NIOServer实例
* @param host
* @param port
* @return
* @throws Exception
*/
public static Server createServer(String host, int port) throws IOException {
Server server = new Server();
server.init(host, port);
return server;
}
private Server() {
}
// 初始化serverSocketChannel
private void init(String host, int port) throws IOException {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(host, port));
serverSocketChannel.configureBlocking(false);
}
/**
* @Description: 启动Server
* @param selectTimeout 选择器获取就绪事件的阻塞时长
* @param bufferCapacity buffer的容量
* @throws IOException
*/
public void start(long selectTimeout, int bufferCapacity) throws IOException {
final long timeout = selectTimeout <= 0 ? 1000 : selectTimeout;
final int capacity = bufferCapacity <= 12 ? 12 : bufferCapacity;
System.out.println("........NIOServer.start..........");
// 打开选择器
Selector selector = Selector.open();
// 注册
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
try {
while (true) {
// 循环监听就绪事件
if (selector.select(timeout) > 0) {
// 获取就绪事件
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
// 迭代处理就绪事件
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
try {
if (key.isAcceptable()) { // 接收到连接事件
SocketChannel channel = serverSocketChannel.accept();
InetSocketAddress address = (InetSocketAddress) channel.getRemoteAddress();
String hostName = address.getHostName();
int port = address.getPort();
System.out.println(hostName + ":" + port + "->建立了连接事件...");
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(capacity));
} else if (key.isReadable()) { // 读事件
this.handler(key);
}
} catch (IOException e) {
e.printStackTrace();
}
iterator.remove(); // 删除处理完的事件
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
serverSocketChannel.close();
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("连接已关闭");
}
}
// 处理读事件
private void handler(SelectionKey key) throws IOException {
try {
SocketChannel channel = (SocketChannel) key.channel();
InetSocketAddress remoteAddress = (InetSocketAddress) channel.getRemoteAddress();
ByteBuffer buffer = (ByteBuffer) key.attachment();
String message = BufferUtils.readBuffer(channel, buffer, true);
message = remoteAddress.getHostName() + ":" + remoteAddress.getPort() + " message -> " + message;
System.out.println(message);
Thread.sleep(50); // 模拟业务处理耗时
// 构建响应消息
String response = "status=200;server已收到来自 " + message;
channel.write(ByteBuffer.wrap(response.getBytes()));
} catch (Exception e) {
e.printStackTrace();
} finally {
key.channel().close();
}
}
}
3. client端
public static void main(String[] args) throws IOException {
String host = "127.0.0.1";
int port = 9991;
Client client = Client.createClient();
String message = "client call";
for (int i = 0; i < 10; i ++) {
new Thread(() -> {
String response = client.call(host, port, message);
System.out.println(response);
}).start();
}
}
static class Client {
// 创建Client客户端
public static Client createClient() {
Client client = new Client();
return client;
}
private Client() {
}
/**
*
* @Description: 向服务器端发送请求
* @param host 服务器host
* @param port 服务器port
* @param message 发送的消息
* @return
*/
public String call(String host, int port, String message) {
SocketChannel channel = null;
try {
//打开通道并以阻塞模式连接服务器端
channel = SocketChannel.open(new InetSocketAddress(host, port));
//把消息存入缓冲
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
//从缓冲写数据到通道
channel.write(buffer);
ByteBuffer byteBuffer = ByteBuffer.allocate(64);
//从通道读服务端响应的数据
String response = BufferUtils.readBuffer(channel, byteBuffer);
return response;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
4. NIO模拟长链接群聊系统示例:
需求:
服务端监听各个客户端上线、下线,并且转发任意客户端的消息给所有的客户端
设计思路:
首先抽象出以下几个对象
Commond 指令对象
指令对象又分为服务端指令和客户端指令
Server 对象
client 对象
最终根据当下的时间还是没用GUI,简单的用了一下控制台输入扫描Scaner
代码如下:(累了,注释后面补充)
1.BufferUtils
参考3.1BufferUtils代码片段
2. commond
package cn.qu.scanner;
import java.util.Map;
import java.util.function.Function;
public interface Commond {
/**
* @Description: 发起停止指令
*/
void stop();
/**
* @Description: 轮询监听
*/
void listen();
/**
* @Description: 构建指令集
* @param commondContainer 存放指令的容器
*/
void buildCommondContainer(Map<String, Function<String, Object>> commondContainer);
}
3.ScannerCommond
package cn.qu.scanner;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
//控制台命令组件
public abstract class ScannerCommond implements Commond {
private AtomicBoolean flag = new AtomicBoolean(true);
private Scanner scaner;
private final Map<String, Function<String, Object>> commondContainer = new HashMap<>();
private List<String> commondKeyList;
protected ScannerCommond() {
this.init();
}
@Override
public void stop() {
flag.compareAndSet(true, false);
}
//监听控制台输入命令的task
@Override
public void listen() {
while (flag.get()) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (scaner.hasNext()) {
String commond = scaner.nextLine();
if (checkCommond(commond)) {
String commondKey, commondContent;
if (commond.contains(":")) {
commondKey = commond.substring(0, commond.indexOf(":"));
commondContent = commond.substring(commond.indexOf(":") + 1);
} else {
commondKey = commond;
commondContent = commond;
}
Function<String, Object> function = commondContainer.get(commondKey);
if (function != null) {
function.apply(commondContent);
}
}
}
}
}
//检查输入的命令是否合法
private boolean checkCommond(String commond) {
for (String commondKey : commondKeyList) {
if (commond.startsWith(commondKey)) {
return true;
}
}
return false;
}
private void init() {
this.buildCommondContainer(commondContainer);
this.commondKeyList = commondContainer.keySet().stream().collect(Collectors.toList());
scaner = new Scanner(System.in);
}
}
4.Server
package cn.qu.nio.groupchat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import org.springframework.util.StringUtils;
import cn.qu.scanner.ScannerCommond;
import cn.qu.util.BufferUtils;
public class GroupChatServer {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 18001;
Server server = Server.createServer(host, port);
new Thread(server).start();
GroupChatServerScaner.start(server);
}
//服务端的控制台命令组件
static class GroupChatServerScaner extends ScannerCommond {
private GroupChatServerScaner() {
}
private final static GroupChatServerScaner scaner = new GroupChatServerScaner();
private Server server;
public static void start(Server server) {
scaner.server = server;
scaner.listen();
}
@Override
public void buildCommondContainer(Map<String, Function<String, Object>> commondContainer) {
commondContainer.put("stop", (param) -> {
try {
this.server.stop();
this.stop();
} catch (Exception e) {
e.printStackTrace();
}
return null;
});
}
}
//NioServer
static class Server implements Runnable {
private AtomicBoolean flag;
private ServerSocketChannel serverSocketChannel;
Selector selector = null;
private InetSocketAddress address;
//创建一个Server对象
public static Server createServer(String host, int port) {
Server server = new Server();
try {
server.init(host, port);
return server;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//server工作task
@Override
public void run() {
try {
while (flag.get()) {
if (selector.select(1000) > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
try {
if (key.isAcceptable()) {
this.onLine();
} else if (key.isReadable()) {
this.readMessage(key);
}
} catch (IOException e) {
key.cancel();
try {
key.channel().close();
} catch (IOException e1) {
e1.printStackTrace();
}
// e.printStackTrace();
}
iterator.remove(); // 删除处理完的事件
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
this.close();
}
}
//停止服务端server
public void stop() {
this.flag.set(false);
}
private Server() {
}
private void init(String host, int port) throws IOException {
address = new InetSocketAddress(host, port);
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(address);
serverSocketChannel.configureBlocking(false);
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
this.flag = new AtomicBoolean(true);
String current = address.getHostName() + ":" + address.getPort();
System.out.println("-----------------------" + current + " server is started-----------------------");
}
//上线,接收accept事件,并注册客户端channel到selector
private void onLine() throws IOException {
String user = "";
try {
SocketChannel channel = serverSocketChannel.accept();
InetSocketAddress clientAddress = (InetSocketAddress) channel.getRemoteAddress();
String hostName = clientAddress.getHostName();
int port = clientAddress.getPort();
user = hostName + ":" + port;
System.out.println(user + " -> 已上线...");
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ, user);
} catch (IOException e) {
System.out.println("远程服务建立连接异常... clientAddress=" + user);
throw e;
}
}
//发送消息给所有客户端channel,writer事件
private void sendMessage(Selector selector, String message) throws IOException {
try {
Iterator<SelectionKey> iterator = selector.keys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
SelectableChannel selectableChannel = key.channel();
if (!(selectableChannel instanceof ServerSocketChannel)) {
SocketChannel channel = (SocketChannel) selectableChannel;
channel.write(ByteBuffer.wrap(message.getBytes()));
}
}
} catch (IOException e) {
System.out.println("转发消息异常... message=" + message);
throw e;
}
}
//读事件
private void readMessage(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
String user = (String) key.attachment();
String message = "";
try {
message = BufferUtils.readBuffer(channel, ByteBuffer.allocate(1024), true);
if (!StringUtils.isEmpty(message)) {
message = user + " -> " + message;
this.sendMessage(selector, message);
}
} catch (IOException e) {
System.out.println(user + " -> " + "已下线...");
throw e;
}
}
//关闭服务端channel
private void close() {
try {
serverSocketChannel.close();
selector.close();
String current = address.getHostName() + ":" + address.getPort();
System.out.println("-----------------------" + current + " server is stoped-----------------------");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
5.client
package cn.qu.nio.groupchat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import cn.qu.scanner.ScannerCommond;
import cn.qu.util.BufferUtils;
public class GroupChatClient {
public static void main(String[] args) {
String serverHost = "127.0.0.1";
int serverPort = 18001;
Client client = Client.create(serverHost, serverPort);
new Thread(client).start();
GroupChatClientScaner.start(client);
}
//客户端命令组件
static class GroupChatClientScaner extends ScannerCommond {
private GroupChatClientScaner() {
}
private final static GroupChatClientScaner scaner = new GroupChatClientScaner();
public static void start(Client client) {
scaner.client = client;
scaner.listen();
}
private Client client;
@Override
public void buildCommondContainer(Map<String, Function<String, Object>> commondContainer) {
commondContainer.put("stop", (commond) -> {
try {
this.client.stop();
this.stop();
} catch (Exception e) {
e.printStackTrace();
}
return null;
});
commondContainer.put("say", (commond) -> {
this.client.sendMessage(commond);
return null;
});
}
}
//Nio客户端
static class Client implements Runnable {
private SocketChannel socketChannel;
private AtomicBoolean flag;
private Selector selector;
private InetSocketAddress serverAddress;
private InetSocketAddress address;
//创建client对象
public static Client create(String host, int port) {
Client client = new Client();
try {
client.init(host, port);
return client;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private Client() {
}
//初始化
public void init(String host, int port) throws IOException {
selector = Selector.open();
serverAddress = new InetSocketAddress(host, port);
socketChannel = SocketChannel.open(serverAddress);
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
flag = new AtomicBoolean(true);
address = (InetSocketAddress) socketChannel.getLocalAddress();
String current = address.getHostName() + ":" + address.getPort();
System.out.println("-----------------------" + current + " client is started-----------------------");
}
//停止客户端
public void stop() {
flag.compareAndSet(true, false);
}
//客户端工作task
@Override
public void run() {
try {
while (flag.get()) {
if (selector.select(1000) > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isReadable()) {
this.readMessage(key);
}
iterator.remove();
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
this.close();
}
}
//关闭客户端
private void close() {
try {
selector.close();
socketChannel.close();
String current = address.getHostName() + ":" + address.getPort();
System.out.println("-----------------------" + current + " client is stoped-----------------------");
} catch (IOException e) {
e.printStackTrace();
}
}
//发送消息,写事件
public void sendMessage(String message) {
try {
socketChannel.write(ByteBuffer.wrap(message.getBytes()));
} catch (IOException e) {
e.printStackTrace();
}
}
//读取消息,读事件,注意key.cancel
private void readMessage(SelectionKey key) {
SocketChannel channel = (SocketChannel) key.channel();
String message = "";
try {
message = BufferUtils.read(channel, ByteBuffer.allocate(1024), true);
} catch (IOException e) {
message = serverAddress.getHostName() + ":" + serverAddress.getPort() + " -> 服务器已掉线...";
key.cancel();
try {
channel.close();
} catch (IOException e1) {
e1.printStackTrace();
}
// e.printStackTrace();
}
message = "[" + address.getHostName() + ":" + address.getPort() + "] ==>> " + message;
System.out.println(message);
}
}
}
5.Nio 多线程模型实例
此处只演示了NioServer端代码
主要涉及如下几个对象:
MainReactor:主reactor用于处理所有accept事件
Reactor:其实是对selector事件的封装,这里的reactor其实指的就是subReactor
PollingPool:轮询池,持有多个reactor的集合,内部get方法会轮询获取一个reactor
InHandler:封装reactor的读
OutHandler:封装reactor的写
代码如下:
package cn.qu.nio.reactor;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class NioServerMultiThreadDemo {
final static int nThreads = 10;
public static void main(String[] args) throws IOException {
start();
}
// 主线程启动Server
public static void start() throws IOException {
String host = "127.0.0.1";
int port = 18001;
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open().bind(new InetSocketAddress(host, port));
serverSocketChannel.configureBlocking(false);
ThreadPoolExecutor threadPool = ThreadPoolUtils.create(nThreads, 10000);
PollingPool<Reactor> pollingPool = PollingPool.create(new Reactor(threadPool), new Reactor(threadPool),
new Reactor(threadPool), new Reactor(threadPool), new Reactor(threadPool));
new MainReactor(pollingPool).register(serverSocketChannel);
new MainReactor(pollingPool).register(serverSocketChannel);
}
// 出站执行器
static class OutHandler implements Runnable {
private SocketChannel channel;
private InetSocketAddress remoteAddress;
public OutHandler(SocketChannel channel) {
this.channel = channel;
}
@Override
public void run() {
try {
remoteAddress = (InetSocketAddress) channel.getRemoteAddress();
String message = "收到消息来自 -> " + remoteAddress.getHostName() + ":" + remoteAddress.getPort();
channel.write(ByteBuffer.wrap(message.getBytes()));
} catch (IOException e) {
e.printStackTrace();
} finally {
this.close();
}
}
private void close() {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 入站执行器
static class InHandler implements Runnable {
private SocketChannel channel;
private InetSocketAddress remoteAddress;
private final static int capacity = 64;
public InHandler(SocketChannel channel) {
this.channel = channel;
}
@Override
public void run() {
try {
ByteBuffer buffer = ByteBuffer.allocate(capacity);
remoteAddress = (InetSocketAddress) channel.getRemoteAddress();
String message = BufferUtils.read(channel, buffer);
this.beforeFilter(message);
this.handler(message);
this.afterFilter(message);
} catch (IOException e) {
e.printStackTrace();
}
}
private void handler(String message) {
System.out.println(remoteAddress.getHostName() + ":" + remoteAddress.getPort() + " ==>> " + message);
}
private void beforeFilter(String message) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
message = "before<<" + message;
}
private void afterFilter(String message) {
message += ">>after";
}
}
//主reactor,注意主accept事件不要cancel
static class MainReactor {
public MainReactor(PollingPool<Reactor> pollingPool) {
this.pollingPool = pollingPool;
}
private PollingPool<Reactor> pollingPool;
private AtomicBoolean flag;
private Selector selector;
public void register(ServerSocketChannel channel) throws IOException {
if (channel == null) {
return;
}
if (selector == null) {
this.listen();
}
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);
}
private void listen() throws IOException {
this.flag = new AtomicBoolean(true);
selector = Selector.open();
new Thread(() -> {
try {
while (flag.get()) {
if (selector.selectNow() > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
System.out.println("main-react -> " + this.hashCode());
SocketChannel channel = serverSocketChannel.accept();
pollingPool.get().register(channel);
}
iterator.remove();
}
} else {
Thread.sleep(1);
}
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
public void destory() {
if (flag != null) {
flag.compareAndSet(true, false);
}
}
}
// 一个reactor持有一个selector,各个reactor共有一个线程池
static class Reactor {
public Reactor(ThreadPoolExecutor threadPool) {
this.threadPool = threadPool;
}
private ThreadPoolExecutor threadPool;
private AtomicBoolean flag;
private Selector selector;
// 注册channel给selecor
public void register(SocketChannel channel) throws IOException {
if (channel == null) {
return;
}
if (selector == null) {
this.listen();
}
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
}
// 当前reactor的监听task,监听selector的读、写事件封装进对应Handler,并交由线程池处理
private void listen() throws IOException {
this.flag = new AtomicBoolean(true);
selector = Selector.open();
new Thread(() -> {
try {
while (flag.get()) {
if (selector.selectNow() > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isReadable()) {
// 开始交由handler读
SocketChannel channel = (SocketChannel) key.channel();
channel.register(selector, SelectionKey.OP_WRITE);
threadPool.execute(new InHandler(channel));
} else if (key.isWritable()) {
// 开始交由handler写
SocketChannel channel = (SocketChannel) key.channel();
key.cancel();
threadPool.execute(new OutHandler(channel));
}
iterator.remove();
}
} else {
Thread.sleep(1);
}
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
// 停止当前reactor的监听task
public void destory() {
if (flag != null) {
flag.compareAndSet(true, false);
}
}
}
// 自定义的轮询池
static class PollingPool<T> extends ArrayList<T> {
/**
* @Fields serialVersionUID :
*/
private static final long serialVersionUID = -7865320985484338340L;
private PollingPool() {
}
@SafeVarargs
public static <T> PollingPool<T> create(T... t) {
PollingPool<T> pool = new PollingPool<>();
for (T e : t) {
pool.add(e);
}
return pool;
}
private int position = 0;
public int position() {
return position;
}
public T get() {
if (position >= this.size()) {
position = 0;
}
T t = this.get(position);
position++;
return t;
}
}
static class ThreadPoolUtils {
/**
* @Description: 创建一个fixed线程池,并指定queue的容量
* @param nThreads 线程个数
* @param capacity queue最大容量
* @return
*/
public static ThreadPoolExecutor create(int nThreads, int capacity) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(capacity));
}
}
static class BufferUtils {
public static String read(SocketChannel channel, ByteBuffer buffer) throws IOException {
return read(channel, buffer, false);
}
/**
* @Description: 从通道读取数据
* @param channel 一个SocketChannel通道
* @param buffer 缓冲对象
* @param checkChannelClosed 是否检测通道是否断开
* @return 返回读取的数据字符串
* @throws IOException
*/
public static String read(SocketChannel channel, ByteBuffer buffer, boolean checkChannelClosed)
throws IOException {
List<Byte> list = new ArrayList<>();
int read = channel.read(buffer);
while (read > 0) {
buffer.flip();
while (buffer.hasRemaining()) {
list.add(buffer.get());
}
buffer.clear();
read = channel.read(buffer);
}
if (read == -1 && checkChannelClosed) {
throw new ClosedChannelException();
}
byte[] bytes = new byte[list.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = list.get(i);
}
return new String(bytes);
}
}
}