I/O 模型
I/O 模型根本阐明
- I/O 模型简略了解为:就是应用什么样的通道进行数据的发送和接管,很大水平上决定了程序通信的性能。
- Java 反对 3 种网络编程模型:BIO、NIO、AIO。
Java BIO:
同步并阻塞
(传统阻塞型),服务器实现模式为一个连贯一个线程,即客户端有连贯申请时服务器端就须要启动一个线程进行解决,如果这个连贯不作任何事件会造成不必要的线程开销。Java NIO:
同步非阻塞
,服务器实现模式为一个线程解决多个申请(连贯),即客户端发送的连贯申请会被注册到多路复用器上,多路复用器轮询到有 I/O 申请就会进行解决。Java AIO:
异步非阻塞
,AIO 引入了异步通道的概念,采纳了 Proactor 模式,简化了程序编写,无效的申请才启动线程,它的特点是先由操作系统实现后才告诉服务端程序启动线程去解决,个别实用于连接数较多且连接时间较长的利用。
同步阻塞
:你到饭馆点餐,而后在那等着,还要一边喊:好了没啊!同步非阻塞
:在饭馆点完餐,就去遛狗了。不过溜一会儿,就回饭馆喊一声:好了没啊!异步阻塞
:遛狗的时候,接到饭馆电话,说饭做好了,让您亲自去拿。异步非阻塞
:饭馆打电话说,咱们晓得您的地位,一会给你送过来,安心遛狗就能够了。
BIO、NIO、AIO 应用场景剖析
- BIO 形式实用于
连接数比拟小且固定
的架构,这种形式对服务器资源要求比拟高,并发局限于利用中,JDK1.4 之前惟一的抉择,程序较为简单容易了解。 - NIO 形式实用于
连贯数目多且连贯比拟短
的架构,比方聊天服务器,弹幕零碎,服务器间通信等,编程比较复杂,JDK1.4 开始反对。 - AIO 形式实用于
连贯数目多且连贯比拟长
的架构,比方相册服务器,充沛调用 OS 参加并发操作,变成比较复杂,JDK7 开始反对。
BIO 根本介绍
- Java BIO 就是传统的 Java IO 编程,其相干的类和接口在 java.io 包下。
- BIO(Blocking I/O):
同步阻塞
,服务器实现模式为一个连贯一个线程,即客户端有连贯申请时,服务器就会须要启动一个线程来进行解决。如果这个连贯不作任何事件就会造成不必要的开销,能够通过线程池机制改善。
BIO 编程简要流程
- 服务器驱动一个 ServerSocket。
- 客户端启动 Socket 对服务器进行通信,默认状况下服务器端须要对每一个客户端建设一个线程进行通信。
- 客户端发出请求后,先征询服务器时候否线程响应,如果没有则会期待,或者被回绝。
- 如果有响应,客户端线程会期待申请完结后,再继续执行。
BIO 服务端代码案例
public class Server {public static void main(String[] args) throws IOException {
// 创立线程池
ExecutorService executorService = Executors.newCachedThreadPool();
// 创立 serverSocket
ServerSocket serverSocket = new ServerSocket(6666);
for (; ;) {System.out.println("期待连贯中...");
// 监听, 期待客户端连贯
Socket socket = serverSocket.accept();
System.out.println("连贯到一个客户端");
executorService.execute(() -> handler(socket));
}
}
// 编写一个 handler 办法, 和客户端通信
public static void handler(Socket socket) {byte[] bytes = new byte[1024];
System.out.println("以后线程信息:" + Thread.currentThread().getName());
try {
// 通过 socket 获取输出流
InputStream inputStream = socket.getInputStream();
// 循环读取客户端发送的数据
while (inputStream.read(bytes) != -1) {System.out.println(Thread.currentThread().getName()+ ": 发送信息为 :"+ new String(bytes, 0, bytes.length));
}
} catch (IOException e) {e.printStackTrace();
} finally {System.out.println("敞开连贯");
try {socket.close();
} catch (IOException e) {e.printStackTrace();
}
}
}
}
运行后果
应用终端命令
telnet 127.0.0.1 6666
BIO 问题剖析
- 每个申请都须要创立独立的线程,与对应的客户端进行数据处理。
- 当并发数大时,须要
创立大量线程来解决连贯
,系统资源占用较大。 - 连贯建设后,如果以后线程临时没有数据可读,则以后线程会始终阻塞在 Read 操作上,造成线程资源节约。
NIO 根本介绍
- Java NIO 全称 Java non-blocking IO,指的是 JDK 提供的新 API。从 JDK 1.4 开始,Java 提供了一系列改良的输出 / 输入的新个性,被统称为 NIO,即 New IO,是
同步非阻塞
的。 - NIO 相干类都放在 java.nio 包下,并对原 java.io 包中很多类进行了改写。
- NIO 有 三大外围 局部:
Channel(管道)
、Buffer(缓冲区)
、Selector(选择器)
。 - NIO 是面向
缓冲区
编程的。数据读取到了一个它略微解决的缓冲区,须要时可在缓冲区中前后挪动,这就减少了处理过程中的灵活性,应用它能够提供非阻塞的高伸缩性网络。 - Java NIO 的非阻塞模式,使一个线程从某通道发送申请读取数据,然而它仅能失去目前可用数据,如果目前没有可用数据时,则阐明都不会获取,而不是放弃线程阻塞,所以直到数据变为能够读取之前,该线程能够做其余事件。非阻塞写入同理。
NIO Buffer 的根本应用
public class BufferTest {public static void main(String[] args) {
// 同理对应的还有:ByteBuffer,IntBuffer,FloatBuffer,CharBuffer,ShortBuffer,DoubleBuffer,LongBuffer
// 创立一个 Buffer, 大小为 5
IntBuffer buffer = IntBuffer.allocate(5);
// 存放数据
for (int i = 0; i < buffer.capacity(); i++) {buffer.put(i);
}
// 切换成读模式. 读写切换
buffer.flip();
while (buffer.hasRemaining()) {System.out.println(buffer.get()); // 0 1 2 3 4
}
}
}
NIO 和 BIO 比照
- BIO 以流的形式解决数据,而 NIO 以块的形式解决数据,块 I/O 的效率比流 I/O 高很多。
- BIO 是阻塞的,而 NIO 是非阻塞的。
- BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道事件(比方连贯申请,数据达到等),因而
应用单个线程就能够监听多个客户端通道
。
NIO 三大外围组件关系
阐明:
- 每个 Channel 对应一个 Buffer。
- Selector 对应一个线程,一个线程对应多个 Channel。
- 该图反馈了有三个 Channel 注册到该 Selector。
- 程序切换到那个 Channel 是由
事件
决定的(Event)。 - Selector 会依据不同的事件,在各个通道上切换。
- Buffer 就是一个内存块,底层是有一个数组。
- 数据的读取和写入是通过 Buffer,然而须要
flip()
切换读写模式。而 BIO 是单向的,要么输出流要么输入流。
NIO 三大外围了解
Buffer 的机制及子类
Buffer(缓冲区)根本介绍
缓冲区实质上是一个能够读写数据的内存块,能够了解为是一个 容器对象(含数组)
,该对象提供了 一组办法
,能够更轻松地应用内存块,缓冲区对象内置了一些机制,可能跟踪和记录缓冲区的状态变动状况。
Channel 提供从文件、网络读取数据的渠道,然而读取或者都必须通过 Buffer。
在 Buffer 子类中保护着一个对应类型的数组,用来存放数据:
public abstract class IntBuffer
extends Buffer
implements Comparable<IntBuffer>
{
// These fields are declared here rather than in Heap-X-Buffer in order to
// reduce the number of virtual method invocations needed to access these
// values, which is especially costly when coding small buffers.
//
final int[] hb; // Non-null only for heap buffers
final int offset;
boolean isReadOnly; // Valid only for heap buffers
// Creates a new buffer with the given mark, position, limit, capacity,
// backing array, and array offset
//
IntBuffer(int mark, int pos, int lim, int cap, // package-private
int[] hb, int offset)
{super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
// Creates a new buffer with the given mark, position, limit, and capacity
//
IntBuffer(int mark, int pos, int lim, int cap) { // package-private
this(mark, pos, lim, cap, null, 0);
}
Buffer 罕用子类 | 形容 |
---|---|
ByteBuffer | 存储字节数据到缓冲区 |
ShortBuffer | 存储字符串数据到缓冲区 |
CharBuffer | 存储字符数据到缓冲区 |
IntBuffer | 存储整数数据据到缓冲区 |
LongBuffer | 存储长整型数据到缓冲区 |
DoubleBuffer | 存储浮点型数据到缓冲区 |
FloatBuffer | 存储浮点型数据到缓冲区 |
Buffer 中定义了四个属性来提供所其蕴含的数据元素。
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
属性 | 形容 |
---|---|
capacity | 容量,即能够包容的最大数据量;在缓冲区被创立时候就被指定,无奈批改 |
limit | 示意缓冲区的以后起点,不能对缓冲区超过极限的地位进行读写操作,但极限是能够批改的 |
position | 以后地位,下一个要被读或者写的索引,每次读写缓冲区数据都会扭转该值,为下次读写做筹备 |
Mark | 标记以后 position 地位,当 reset 后回到标记地位。 |
Channel 的根本介绍
NIO 的通道相似于流,但有如下区别:
- 通道是双向的能够进行读写,而流是单向的只能读,或者写。
- 通道能够实现异步读写数据。
- 通道能够从缓冲区读取数据,也能够写入数据到缓冲区。
罕用的 Channel 有:FileChannel、DatagramChannel、SocketChannel、SocketServerChannel。
FileChannel 类
FileChannel 次要用来对本地文件进行 IO 操作,常见的办法有:
- public int read(ByteBuffer dst):从通道中读取数据到缓冲区中。
- public int write(ByteBuffer src):把缓冲区中的数据写入到通道中。
- public long transferFrom(ReadableByteChannel src,long position,long count):从指标通道中复制数据到以后通道。
- public long transferTo(long position,long count,WriteableByteChannel target):把数据从以后通道复制给指标通道。
应用 FileChannel 写入文本文件
public class NIOFileChannel {public static void main(String[] args) throws IOException {
String str = "Hello,Java 菜鸟程序员";
// 创立一个输入流
FileOutputStream fileOutputStream = new FileOutputStream("hello.txt");
// 获取通道
FileChannel channel = fileOutputStream.getChannel();
// 创立缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
// 写入 byteBuffer
byteBuffer.put(str.getBytes());
// 切换模式
byteBuffer.flip();
// 写入通道
channel.write(byteBuffer);
// 敞开
channel.close();
fileOutputStream.close();}
}
应用 FileChannel 读取文本文件
public class NIOFileChannel {public static void main(String[] args) throws IOException {FileInputStream fileInputStream = new FileInputStream("hello.txt");
FileChannel channel = fileInputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
channel.read(byteBuffer);
System.out.println(new String(byteBuffer.array(), 0, byteBuffer.limit())); //Hello,Java 菜鸟程序员
channel.close();
fileInputStream.close();}
}
应用 FileChannel 复制文件
public class NIOFileChannel03 {public static void main(String[] args) throws IOException {FileInputStream fileInputStream = new FileInputStream("hello.txt");
FileOutputStream fileOutputStream = new FileOutputStream("world.txt");
FileChannel inChannel = fileInputStream.getChannel();
FileChannel outChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1);
while (inChannel.read(byteBuffer) != -1) {byteBuffer.flip();
outChannel.write(byteBuffer);
// 清空重置
byteBuffer.clear();}
fileOutputStream.close();
fileInputStream.close();}
}
应用 transferFrom 复制文件
public class NIOFileChannel04 {public static void main(String[] args) throws IOException {FileInputStream fileInputStream = new FileInputStream("hello.txt");
FileOutputStream fileOutputStream = new FileOutputStream("world.txt");
FileChannel inChannel = fileInputStream.getChannel();
FileChannel outChannel = fileOutputStream.getChannel();
// 从哪拷贝, 从几开始到几完结 对应的还有 transferTo()办法.
outChannel.transferFrom(inChannel, 0, inChannel.size());
outChannel.close();
inChannel.close();
fileOutputStream.close();
fileInputStream.close();}
}
Channel 和 Buffer 的注意事项
- ByteBuffer 反对 类型化 的 put 和 get,put 放入什么数据类型,get 就应该应用相应的数据类型来取出,否则可能会产生 ByteUnderflowException 异样。
- 能够将一个一般的 Buffer 转换为只读的 Buffer:asReadOnlyBuffer()办法。
- NIO 提供了 MapperByteBuffer,能够让文件间接在 内存(堆外内存)中进行批改,而如何同步到文件由 NIO 来实现。
-
NIO 还反对通过多个 Buffer(即 Buffer 数组)实现读写操作,即Scattering(扩散)和 Gathering(汇集)。
Scattering(扩散)
:在向缓冲区写入数据时,能够应用 Buffer 数组顺次写入,一个 Buffer 数组写满后,持续写入下一个 Buffer 数组。Gathering(汇集)
:从缓冲区读取数据时,能够顺次读取,读完一个 Buffer 再按程序读取下一个。
Selector 的根本介绍
- Java 的 NIO 应用了非阻塞的 I/O 形式。能够用一个线程解决若干个客户端连贯,就会应用到 Selector(选择器)。
- Selector 可能检测到多个注册通道上是否有事件产生(多个 Channel 以事件的模式注册到同一个 selector),如果有事件产生,便获取事件而后针对每个事件进行相应的解决。
- 只有在连贯真正有读写事件产生时,才会进行读写,缩小了零碎开销,并且不用为每个连贯都创立一个线程,不必保护多个线程。
- 防止了多线程之间上下文切换导致的开销。
Selector 特点
Netty 的 I/O 线程 NioEventLoop 聚合了 Selector(选择器 / 多路复用器),能够并发解决成千盈百个客户端连贯。
当线程从某客户端 Socket 通道进行读写时,若没有数据可用,该线程能够进行其余工作。
线程通常将非阻塞 I/O 的闲暇工夫用于其余通道上执行 I/O 操作,所以独自的线程能够治理多个输入输出通道。
因为读写操作都是非阻塞的,就能够充沛进步 I/O 线程的运行效率,防止因为频繁 I/O 阻塞导致的线程挂起。
一个 I/O 线程能够并发解决 N 个客户端连贯和读写操作,这从根本上解决了传统同步阻塞 I/O 一连贯一线程模型,架构性能、弹性伸缩能力和可靠性都失去极大地晋升。
Selector 罕用办法
public abstract class Selector implement Closeable{public static Selector open(); // 失去一个选择器对象
public int select(long timeout); // 监控所有注册的通道,当其中的 IO 操作能够进行时,将对应的 selectionkey 退出外部汇合并返回,参数设置超时工夫
public Set<SelectionKey> selectionKeys(); // 从外部汇合中失去所有的 SelectionKey}
Selector 相干办法阐明
selector.select()
:// 若未监听到注册管道中有事件,则继续阻塞selector.select(1000)
:// 阻塞 1000 毫秒,1000 毫秒后返回selector.wakeup()
:// 唤醒 selectorselector.selectNow()
:// 不阻塞,立刻返回
NIO 非阻塞网络编程过程剖析
- 当客户端连贯时,会通过 SeverSocketChannel 失去对应的 SocketChannel。
- Selector 进行监听,调用 select()办法,返回注册该 Selector 的所有通道中有事件产生的通道个数。
- 将 socketChannel 注册到 Selector 上,public final SelectionKey register(Selector sel, int ops),一个 selector 上能够注册多个 SocketChannel。
- 注册后返回一个 SelectionKey,会和该 Selector 关联 (以 汇合 的模式)。
- 进一步失去各个 SelectionKey,有事件产生。
- 再通过 SelectionKey 反向获取 SocketChannel,应用 channnel()办法。
- 能够通过失去的 channel,实现业务解决。
SelectionKey 中定义了四个操作标记位:
OP_READ
示意通道中产生读事件;OP_WRITE
—示意通道中产生写事件;OP_CONNECT
—示意建设连贯;OP_ACCEPT
—申请新连贯。
NIO 非阻塞网络编程代码示例
public class Server {public static void main(String[] args) throws IOException {
// 创立 serverSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
// 设置为非阻塞
serverSocketChannel.configureBlocking(false);
// 失去 Selector 对象
try (Selector selector = Selector.open()) {
// 把 ServerSocketChannel 注册到 selector,事件为 OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 如果返回的 >0,示意曾经获取到关注的事件
while (selector.select() > 0) {Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
// 取得到一个事件
SelectionKey next = iterator.next();
// 如果是 OP_ACCEPT,示意有新的客户端连贯
if (next.isAcceptable()) {
// 给该客户端生成一个 SocketChannel
SocketChannel accept = serverSocketChannel.accept();
accept.configureBlocking(false);
// 将以后的 socketChannel 注册到 selector,关注事件为读事件,同时给 socket Channel 关联一个 buffer
accept.register(selector, SelectionKey.OP_READ,ByteBuffer.allocate(1024));
System.out.println("获取到一个客户端连贯");
// 如果是读事件
} else if (next.isReadable()) {
// 通过 key 反向获取到对应的 channel
SocketChannel channel = (SocketChannel) next.channel();
// 获取到该 channel 关联的 buffer
ByteBuffer buffer = (ByteBuffer) next.attachment();
while (channel.read(buffer) != -1) {buffer.flip();
System.out.println(new String(buffer.array(), 0, buffer.limit()));
buffer.clear();}
}
iterator.remove();}
}
}
}
}
public class Client {public static void main(String[] args) throws IOException {
// 失去一个网络通道
SocketChannel socketChannel = SocketChannel.open();
// 设置为非阻塞
socketChannel.configureBlocking(false);
// 提供服务器端的 IP 和端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
// 连贯服务器
if (!socketChannel.connect(inetSocketAddress)) {while (!socketChannel.finishConnect()) {System.out.println("连贯须要工夫, 客户端不会阻塞... 先去吃个宵夜");
}
}
// 连贯胜利, 发送数据
String str = "hello,Java 菜鸟程序员";
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
socketChannel.write(byteBuffer);
socketChannel.close();
System.out.println("客户端退出");
}
}
运行后果
SelectionKey 的相干办法
办法 | 形容 |
---|---|
public abstract Selector selector(); | 失去与之关联的 Selector 对象 |
public abstract SelectableChannel channel(); | 失去与之关联的通道 |
public final Object attachment() | 失去与之关联的共享数据 |
public abstract SelectionKey interestOps(int ops); | 设置或扭转监听的事件类型 |
public final boolean isReadable(); | 通道是否可读 |
public final boolean isWritable(); | 通道是否可写 |
public final boolean isAcceptable(); | 是否能够建设连贯 ACCEPT |
NIO 实现群聊零碎
- 实现服务器端与客户端的数据简略通信(非阻塞)实现多人群聊。
- 服务器端:能够检测用户上线,离线,并实现音讯转发性能。
- 客户端:通过 Channel 能够无阻塞发送数据给其余所有用户,同时能够接管其余用户发送的音讯(由服务器转发失去)。
public class GroupChatClient {
private static final String HOST = "127.0.0.1";
private static final int PORT = 6667;
private Selector selector;
private SocketChannel socketChannel;
private String username;
public GroupChatClient() {
try {selector = Selector.open();
// 连贯服务器
socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));
// 设置非阻塞
socketChannel.configureBlocking(false);
// 注册
socketChannel.register(selector, SelectionKey.OP_READ);
username = socketChannel.getLocalAddress().toString().substring(1);
System.out.println("客户端:" + username + ", 准备就绪...");
} catch (IOException e) {e.printStackTrace();
}
}
/**
* 向服务器发送数据
*
* @param info
*/
public void sendInfo(String info) {
info = username + "说:" + info;
try {socketChannel.write(ByteBuffer.wrap(info.getBytes()));
} catch (IOException e) {e.printStackTrace();
}
}
/**
* 读取服务端回复的音讯
*/
public void readInfo() {
try {
// 有可用通道
if (selector.select() > 0) {Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {SelectionKey key = iterator.next();
if (key.isReadable()) {
// 失去相干的通道
SocketChannel sc = (SocketChannel) key.channel();
// 失去一个 buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取
sc.read(buffer);
// 把读取到的缓冲区数据转成字符串
String msg = new String(buffer.array());
System.out.println(msg.trim());
}
iterator.remove(); // 删除以后的 selectionKey,避免反复操作}
}
} catch (IOException e) {e.printStackTrace();
}
}
public static void main(String[] args) {
// 启动客户端
GroupChatClient chatClient = new GroupChatClient();
// 启动一个线程, 每隔 3 秒,读取从服务器端发送的数据
new Thread(() -> {while (true) {chatClient.readInfo();
try {TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {e.printStackTrace();
}
}
}).start();
// 发送数据给服务器
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {chatClient.sendInfo(scanner.nextLine());
}
}
}
public class GroupChatServer {
// 定义属性
private Selector selector;
private ServerSocketChannel listenChannel;
private static final int PORT = 6667;
public GroupChatServer() {
try {
// 取得选择器
selector = Selector.open();
//listenChannel
listenChannel = ServerSocketChannel.open();
// 绑定端口
listenChannel.socket().bind(new InetSocketAddress(PORT));
// 设置非阻塞模式
listenChannel.configureBlocking(false);
// 将该 listenChannel 注册到 Selector
listenChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {e.printStackTrace();
}
}
public static void main(String[] args) {
// 创立一个服务器对象
GroupChatServer groupChatServer = new GroupChatServer();
// 监听
groupChatServer.listen();}
/**
* 监听
*/
public void listen() {
try {
// 如果返回的 >0,示意曾经获取到关注的事件
while (selector.select() > 0) {Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
// 判断是否有事件
while (iterator.hasNext()) {
// 取得事件
SelectionKey key = iterator.next();
// 如果是 OP_ACCEPT,示意有新的客户端连贯
if (key.isAcceptable()) {SocketChannel socketChannel = listenChannel.accept();
// 设置为非阻塞
socketChannel.configureBlocking(false);
// 注册到 Selector
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("获取到一个客户端连贯 :" + socketChannel.getRemoteAddress() + "上线!");
} else if (key.isReadable()) {
// 如果是读事件, 就读取数据
readData(key);
}
iterator.remove();}
}
} catch (IOException e) {e.printStackTrace();
} finally {}}
/**
* 读取客户端音讯
*/
private void readData(SelectionKey key) {
SocketChannel channel = null;
try {
// 失去 channel
channel = (SocketChannel) key.channel();
// 创立 buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
if (channel.read(buffer) != -1) {String msg = new String(buffer.array());
System.out.println(msg);
// 转发音讯给其它客户端(排除本人)
sendInfoOtherClients(msg, channel);
}
} catch (Exception e) {
try {System.out.println(channel.getRemoteAddress() + "下线了!");
// 敞开通道
key.cancel();
channel.close();} catch (IOException ioException) {ioException.printStackTrace();
}
} finally {}}
/**
* 转发音讯给其它客户端(排除本人)
*/
private void sendInfoOtherClients(String msg, SocketChannel self) throws IOException {
// 服务器转发音讯
System.out.println("服务器转发音讯中...");
// 遍历所有注册到 selector 的 socketChannel 并排除本身
for (SelectionKey key : selector.keys()) {
// 反向获取通道
Channel targetChannel = key.channel();
// 排除本身
if (targetChannel instanceof SocketChannel && targetChannel != self) {
// 转型
SocketChannel dest = (SocketChannel) targetChannel;
// 将 msg 存储到 buffer 中
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
// 将 buffer 中的数据写入通道
dest.write(buffer);
}
}
}
}
AIO 根本介绍
JDK 7 引入了 Asynchronous I/O,即 AIO。在进行 I/O 编程中,通常用到两种模式:Reactor 和 Proactor。Java 的 NIO 就是 Reactor,当有事件触发时,服务器端失去告诉,进行相应的解决。
AIO 叫做 异步非阻塞
的 I/O,引入了异步通道的概念,采纳了 Proactor 模式,简化了程序编写,无效的申请才会启动线程,特点就是先由操作系统实现后才告诉服务端程序启动线程去解决,个别用于连接数较多且连贯时长较长的利用。
Reactor 与 Proactor
- 两种 IO 多路复用计划:Reactor and Proactor。
- Reactor 模式是基于同步 I/O 的,而 Proactor 模式是和异步 I/O 相干的。
因为 AIO 目前利用并不宽泛,所以本文只是讲述 AIO 根本介绍。