热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

nio和bio的原理_理解什么是BIO/NIO/AIO

IO模型IO模型基本说明IO模型简单理解为:就是使用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能。Java支持3种网络编程模型&#x

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.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

{

// 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 <&#61; position <&#61; limit <&#61; capacity

private int mark &#61; -1;

private int position &#61; 0;

private int limit;

private int capacity;属性描述capacity容量&#xff0c;即可以容纳的最大数据量&#xff1b;在缓冲区被创建时候就被指定&#xff0c;无法修改

limit表示缓冲区的当前终点&#xff0c;不能对缓冲区超过极限的位置进行读写操作&#xff0c;但极限是可以修改的

position当前位置&#xff0c;下一个要被读或者写的索引&#xff0c;每次读写缓冲区数据都会改变该值&#xff0c;为下次读写做准备

Mark标记当前 position 位置&#xff0c;当 reset 后回到标记位置。

Channel 的基本介绍

NIO 的通道类似于流&#xff0c;但有如下区别&#xff1a;通道是双向的可以进行读写&#xff0c;而流是单向的只能读&#xff0c;或者写。

通道可以实现异步读写数据。

通道可以从缓冲区读取数据&#xff0c;也可以写入数据到缓冲区。

常用的 Channel 有&#xff1a;FileChannel、DatagramChannel、SocketChannel、SocketServerChannel。

FileChannel 类

FileChannel 主要用来对本地文件进行 IO 操作&#xff0c;常见的方法有&#xff1a;public int read(ByteBuffer dst) &#xff1a;从通道中读取数据到缓冲区中。

public int write(ByteBuffer src)&#xff1a;把缓冲区中的数据写入到通道中。

public long transferFrom(ReadableByteChannel src,long position,long count)&#xff1a;从目标通道中复制数据到当前通道。

public long transferTo(long position,long count,WriteableByteChannel target)&#xff1a;把数据从当前通道复制给目标通道。

使用 FileChannel 写入文本文件public class NIOFileChannel {

public static void main(String[] args) throws IOException {

String str &#61; "Hello,Java菜鸟程序员";

//创建一个输出流

FileOutputStream fileOutputStream &#61; new FileOutputStream("hello.txt");

//获取通道

FileChannel channel &#61; fileOutputStream.getChannel();

//创建缓冲区

ByteBuffer byteBuffer &#61; 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 &#61; new FileInputStream("hello.txt");

FileChannel channel &#61; fileInputStream.getChannel();

ByteBuffer byteBuffer &#61; 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 &#61; new FileInputStream("hello.txt");

FileOutputStream fileOutputStream &#61; new FileOutputStream("world.txt");

FileChannel inChannel &#61; fileInputStream.getChannel();

FileChannel outChannel &#61; fileOutputStream.getChannel();

ByteBuffer byteBuffer &#61; ByteBuffer.allocate(1);

while (inChannel.read(byteBuffer) !&#61; -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 &#61; new FileInputStream("hello.txt");

FileOutputStream fileOutputStream &#61; new FileOutputStream("world.txt");

FileChannel inChannel &#61; fileInputStream.getChannel();

FileChannel outChannel &#61; fileOutputStream.getChannel();

//从哪拷贝,从几开始到几结束 对应的还有transferTo()方法.

outChannel.transferFrom(inChannel, 0, inChannel.size());

outChannel.close();

inChannel.close();

fileOutputStream.close();

fileInputStream.close();

}

}

Channel 和 Buffer 的注意事项ByteBuffer 支持类型化的 put 和 get&#xff0c;put 放入什么数据类型&#xff0c;get 就应该使用相应的数据类型来取出&#xff0c;否则可能会产生 ByteUnderflowException 异常。

可以将一个普通的 Buffer 转换为只读的 Buffer&#xff1a;asReadOnlyBuffer()方法。

NIO 提供了 MapperByteBuffer&#xff0c;可以让文件直接在内存(堆外内存)中进行修改&#xff0c;而如何同步到文件由 NIO 来完成。

NIO 还支持通过多个 Buffer(即 Buffer 数组)完成读写操作&#xff0c;即Scattering(分散)和 Gathering(聚集)。Scattering(分散)&#xff1a;在向缓冲区写入数据时&#xff0c;可以使用 Buffer 数组依次写入&#xff0c;一个 Buffer 数组写满后&#xff0c;继续写入下一个 Buffer 数组。

Gathering(聚集)&#xff1a;从缓冲区读取数据时&#xff0c;可以依次读取&#xff0c;读完一个 Buffer 再按顺序读取下一个。

Selector 的基本介绍Java 的 NIO 使用了非阻塞的 I/O 方式。可以用一个线程处理若干个客户端连接&#xff0c;就会使用到 Selector(选择器)。

Selector 能够检测到多个注册通道上是否有事件发生(多个 Channel 以事件的形式注册到同一个 selector)&#xff0c;如果有事件发生&#xff0c;便获取事件然后针对每个事件进行相应的处理。

只有在连接真正有读写事件发生时&#xff0c;才会进行读写&#xff0c;减少了系统开销&#xff0c;并且不必为每个连接都创建一个线程&#xff0c;不用维护多个线程。

避免了多线程之间上下文切换导致的开销。

Selector 特点

Netty 的 I/O 线程 NioEventLoop 聚合了 Selector(选择器 / 多路复用器)&#xff0c;可以并发处理成百上千个客户端连接。

当线程从某客户端 Socket 通道进行读写时&#xff0c;若没有数据可用&#xff0c;该线程可以进行其他任务。

线程通常将非阻塞 I/O 的空闲时间用于其他通道上执行 I/O 操作&#xff0c;所以单独的线程可以管理多个输入输出通道。

由于读写操作都是非阻塞的&#xff0c;就可以充分提高 I/O 线程的运行效率&#xff0c;避免由于频繁 I/O 阻塞导致的线程挂起。

一个 I/O 线程可以并发处理 N 个客户端连接和读写操作&#xff0c;这从根本上解决了传统同步阻塞 I/O 一连接一线程模型&#xff0c;架构性能、弹性伸缩能力和可靠性都得到极大地提升。

Selector 常用方法public abstract class Selector implement Closeable{

public static Selector open(); //得到一个选择器对象

public int select(long timeout); //监控所有注册的通道&#xff0c;当其中的IO操作可以进行时&#xff0c;将对应的selectionkey加入内部集合并返回&#xff0c;参数设置超时时间

public Set selectionKeys(); //从内部集合中得到所有的SelectionKey

}

Selector 相关方法说明selector.select()&#xff1a;//若未监听到注册管道中有事件&#xff0c;则持续阻塞

selector.select(1000)&#xff1a;//阻塞 1000 毫秒&#xff0c;1000 毫秒后返回

selector.wakeup()&#xff1a;//唤醒 selector

selector.selectNow()&#xff1a; //不阻塞&#xff0c;立即返回

NIO 非阻塞网络编程过程分析当客户端连接时&#xff0c;会通过 SeverSocketChannel 得到对应的 SocketChannel。

Selector 进行监听&#xff0c;调用 select()方法&#xff0c;返回注册该 Selector 的所有通道中有事件发生的通道个数。

将 socketChannel 注册到 Selector 上&#xff0c;public final SelectionKey register(Selector sel, int ops)&#xff0c;一个 selector 上可以注册多个 SocketChannel。

注册后返回一个 SelectionKey&#xff0c;会和该 Selector 关联(以集合的形式)。

进一步得到各个 SelectionKey&#xff0c;有事件发生。

再通过 SelectionKey 反向获取 SocketChannel&#xff0c;使用 channnel()方法。

可以通过得到的 channel&#xff0c;完成业务处理。SelectionKey 中定义了四个操作标志位&#xff1a;OP_READ表示通道中发生读事件&#xff1b;OP_WRITE—表示通道中发生写事件&#xff1b;OP_CONNECT—表示建立连接&#xff1b;OP_ACCEPT—请求新连接。

NIO 非阻塞网络编程代码示例public class Server {

public static void main(String[] args) throws IOException {

//创建serverSocketChannel

ServerSocketChannel serverSocketChannel &#61; ServerSocketChannel.open();

//绑定端口

serverSocketChannel.socket().bind(new InetSocketAddress(6666));

//设置为非阻塞

serverSocketChannel.configureBlocking(false);

//得到Selector对象

try (Selector selector &#61; Selector.open()) {

//把ServerSocketChannel注册到selector&#xff0c;事件为OP_ACCEPT

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

//如果返回的>0&#xff0c;表示已经获取到关注的事件

while (selector.select() > 0) {

Set selectionKeys &#61; selector.selectedKeys();

Iterator iterator &#61; selectionKeys.iterator();

while (iterator.hasNext()) {

//获得到一个事件

SelectionKey next &#61; iterator.next();

//如果是OP_ACCEPT&#xff0c;表示有新的客户端连接

if (next.isAcceptable()) {

//给该客户端生成一个SocketChannel

SocketChannel accept &#61; serverSocketChannel.accept();

accept.configureBlocking(false);

//将当前的socketChannel注册到selector&#xff0c;关注事件为读事件&#xff0c;同时给socket Channel关联一个buffer

accept.register(selector, SelectionKey.OP_READ,ByteBuffer.allocate(1024));

System.out.println("获取到一个客户端连接");

//如果是读事件

} else if (next.isReadable()) {

//通过key 反向获取到对应的channel

SocketChannel channel &#61; (SocketChannel) next.channel();

//获取到该channel关联的buffer

ByteBuffer buffer &#61; (ByteBuffer) next.attachment();

while (channel.read(buffer) !&#61; -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 &#61; SocketChannel.open();

//设置为非阻塞

socketChannel.configureBlocking(false);

//提供服务器端的IP和端口

InetSocketAddress inetSocketAddress &#61; new InetSocketAddress("127.0.0.1", 6666);

//连接服务器

if (!socketChannel.connect(inetSocketAddress)) {

while (!socketChannel.finishConnect()) {

System.out.println("连接需要时间,客户端不会阻塞...先去吃个宵夜");

}

}

//连接成功,发送数据

String str &#61; "hello,Java菜鸟程序员";

ByteBuffer byteBuffer &#61; 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 实现群聊系统实现服务器端与客户端的数据简单通讯(非阻塞)实现多人群聊。

服务器端&#xff1a;可以检测用户上线&#xff0c;离线&#xff0c;并实现消息转发功能。

客户端&#xff1a;通过 Channel 可以无阻塞发送数据给其他所有用户&#xff0c;同时可以接收其他用户发送的消息(由服务器转发得到)。public class GroupChatClient {

private static final String HOST &#61; "127.0.0.1";

private static final int PORT &#61; 6667;

private Selector selector;

private SocketChannel socketChannel;

private String username;

public GroupChatClient() {

try {

selector &#61; Selector.open();

//连接服务器

socketChannel &#61; SocketChannel.open(new InetSocketAddress(HOST, PORT));

//设置非阻塞

socketChannel.configureBlocking(false);

//注册

socketChannel.register(selector, SelectionKey.OP_READ);

username &#61; socketChannel.getLocalAddress().toString().substring(1);

System.out.println("客户端: " &#43; username &#43; ",准备就绪...");

} catch (IOException e) {

e.printStackTrace();

}

}

/**

* 向服务器发送数据

*

* &#64;param info

*/

public void sendInfo(String info) {

info &#61; username &#43; "说: " &#43; info;

try {

socketChannel.write(ByteBuffer.wrap(info.getBytes()));

} catch (IOException e) {

e.printStackTrace();

}

}

/**

* 读取服务端回复的消息

*/

public void readInfo() {

try {

//有可用通道

if (selector.select() > 0) {

Iterator iterator &#61; selector.selectedKeys().iterator();

while (iterator.hasNext()) {

SelectionKey key &#61; iterator.next();

if (key.isReadable()) {

//得到相关的通道

SocketChannel sc &#61; (SocketChannel) key.channel();

//得到一个buffer

ByteBuffer buffer &#61; ByteBuffer.allocate(1024);

//读取

sc.read(buffer);

//把读取到的缓冲区数据转成字符串

String msg &#61; new String(buffer.array());

System.out.println(msg.trim());

}

iterator.remove(); //删除当前的selectionKey&#xff0c;防止重复操作

}

}

} catch (IOException e) {

e.printStackTrace();

}

}

public static void main(String[] args) {

//启动客户端

GroupChatClient chatClient &#61; new GroupChatClient();

//启动一个线程,每隔3秒&#xff0c;读取从服务器端发送的数据

new Thread(() -> {

while (true) {

chatClient.readInfo();

try {

TimeUnit.MILLISECONDS.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}).start();

//发送数据给服务器

Scanner scanner &#61; 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 &#61; 6667;

public GroupChatServer() {

try {

//获得选择器

selector &#61; Selector.open();

//listenChannel

listenChannel &#61; 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 &#61; new GroupChatServer();

//监听

groupChatServer.listen();

}

/**

* 监听

*/

public void listen() {

try {

//如果返回的>0&#xff0c;表示已经获取到关注的事件

while (selector.select() > 0) {

Iterator iterator &#61; selector.selectedKeys().iterator();

//判断是否有事件

while (iterator.hasNext()) {

//获得事件

SelectionKey key &#61; iterator.next();

//如果是OP_ACCEPT&#xff0c;表示有新的客户端连接

if (key.isAcceptable()) {

SocketChannel socketChannel &#61; listenChannel.accept();

//设置为非阻塞

socketChannel.configureBlocking(false);

//注册到Selector

socketChannel.register(selector, SelectionKey.OP_READ);

System.out.println("获取到一个客户端连接 : " &#43; socketChannel.getRemoteAddress() &#43; " 上线!");

} else if (key.isReadable()) {

//如果是读事件,就读取数据

readData(key);

}

iterator.remove();

}

}

} catch (IOException e) {

e.printStackTrace();

} finally {

}

}

/**

* 读取客户端消息

*/

private void readData(SelectionKey key) {

SocketChannel channel &#61; null;

try {

//得到channel

channel &#61; (SocketChannel) key.channel();

//创建buffer

ByteBuffer buffer &#61; ByteBuffer.allocate(1024);

if (channel.read(buffer) !&#61; -1) {

String msg &#61; new String(buffer.array());

System.out.println(msg);

// 转发消息给其它客户端(排除自己)

sendInfoOtherClients(msg, channel);

}

} catch (Exception e) {

try {

System.out.println(channel.getRemoteAddress() &#43; " 下线了!");

// 关闭通道

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 &#61; key.channel();

//排除自身

if (targetChannel instanceof SocketChannel && targetChannel !&#61; self) {

//转型

SocketChannel dest &#61; (SocketChannel) targetChannel;

//将msg存储到buffer中

ByteBuffer buffer &#61; ByteBuffer.wrap(msg.getBytes());

//将buffer中的数据写入通道

dest.write(buffer);

}

}

}

}

AIO 基本介绍

JDK 7 引入了 Asynchronous I/O&#xff0c;即 AIO。在进行 I/O 编程中&#xff0c;通常用到两种模式&#xff1a;Reactor 和 Proactor 。Java 的 NIO 就是 Reactor&#xff0c;当有事件触发时&#xff0c;服务器端得到通知&#xff0c;进行相应的处理。

AIO 叫做异步非阻塞的 I/O&#xff0c;引入了异步通道的概念&#xff0c;采用了 Proactor 模式&#xff0c;简化了程序编写&#xff0c;有效的请求才会启动线程&#xff0c;特点就是先由操作系统完成后才通知服务端程序启动线程去处理&#xff0c;一般用于连接数较多且连接时长较长的应用。

Reactor 与 Proactor两种 IO 多路复用方案:Reactor and Proactor。

Reactor 模式是基于同步 I/O 的&#xff0c;而 Proactor 模式是和异步 I/O 相关的。

由于 AIO 目前应用并不广泛&#xff0c;所以本文只是讲述 AIO 基本介绍。



推荐阅读
  • Java Socket 关键参数详解与优化建议
    Java Socket 的 API 虽然被广泛使用,但其关键参数的用途却鲜为人知。本文详细解析了 Java Socket 中的重要参数,如 backlog 参数,它用于控制服务器等待连接请求的队列长度。此外,还探讨了其他参数如 SO_TIMEOUT、SO_REUSEADDR 等的配置方法及其对性能的影响,并提供了优化建议,帮助开发者提升网络通信的稳定性和效率。 ... [详细]
  • Python 伦理黑客技术:深入探讨后门攻击(第三部分)
    在《Python 伦理黑客技术:深入探讨后门攻击(第三部分)》中,作者详细分析了后门攻击中的Socket问题。由于TCP协议基于流,难以确定消息批次的结束点,这给后门攻击的实现带来了挑战。为了解决这一问题,文章提出了一系列有效的技术方案,包括使用特定的分隔符和长度前缀,以确保数据包的准确传输和解析。这些方法不仅提高了攻击的隐蔽性和可靠性,还为安全研究人员提供了宝贵的参考。 ... [详细]
  • 深入探索HTTP协议的学习与实践
    在初次访问某个网站时,由于本地没有缓存,服务器会返回一个200状态码的响应,并在响应头中设置Etag和Last-Modified等缓存控制字段。这些字段用于后续请求时验证资源是否已更新,从而提高页面加载速度和减少带宽消耗。本文将深入探讨HTTP缓存机制及其在实际应用中的优化策略,帮助读者更好地理解和运用HTTP协议。 ... [详细]
  • RocketMQ在秒杀时的应用
    目录一、RocketMQ是什么二、broker和nameserver2.1Broker2.2NameServer三、MQ在秒杀场景下的应用3.1利用MQ进行异步操作3. ... [详细]
  • HTTP(HyperTextTransferProtocol)是超文本传输协议的缩写,它用于传送www方式的数据。HTTP协议采用了请求响应模型。客服端向服务器发送一 ... [详细]
  • 为什么多数程序员难以成为架构师?
    探讨80%的程序员为何难以晋升为架构师,涉及技术深度、经验积累和综合能力等方面。本文将详细解析Tomcat的配置和服务组件,帮助读者理解其内部机制。 ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 本文详细介绍了在MySQL中如何高效利用EXPLAIN命令进行查询优化。通过实例解析和步骤说明,文章旨在帮助读者深入理解EXPLAIN命令的工作原理及其在性能调优中的应用,内容通俗易懂且结构清晰,适合各水平的数据库管理员和技术人员参考学习。 ... [详细]
  • 本文探讨了如何通过编程手段在Linux系统中禁用硬件预取功能。基于Intel® Core™微架构的应用性能优化需求,文章详细介绍了相关配置方法和代码实现,旨在帮助开发人员有效控制硬件预取行为,提升应用程序的运行效率。 ... [详细]
  • 在处理 XML 数据时,如果需要解析 `` 标签的内容,可以采用 Pull 解析方法。Pull 解析是一种高效的 XML 解析方式,适用于流式数据处理。具体实现中,可以通过 Java 的 `XmlPullParser` 或其他类似的库来逐步读取和解析 XML 文档中的 `` 元素。这样不仅能够提高解析效率,还能减少内存占用。本文将详细介绍如何使用 Pull 解析方法来提取 `` 标签的内容,并提供一个示例代码,帮助开发者快速解决问题。 ... [详细]
  • 线程能否先以安全方式获取对象,再进行非安全发布? ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 【重识云原生】第四章云网络4.8.3.2节——Open vSwitch工作原理详解
    2OpenvSwitch架构2.1OVS整体架构ovs-vswitchd:守护程序,实现交换功能,和Linux内核兼容模块一起,实现基于流的交换flow-basedswitchin ... [详细]
  • 本文总结了在SQL Server数据库中编写和优化存储过程的经验和技巧,旨在帮助数据库开发人员提升存储过程的性能和可维护性。 ... [详细]
author-avatar
LISA_W186
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有