Java IO模型:BIO、NIO、AIO

本来是打算直接学习网络框架Netty的,但是先补充了一下自己对Java 几种IO模型的学习和理解。分别是 BIO、NIO、AIO三种IO模型。

IO模型的基本说明

BIO模型图

缺点:

  1. 如果有很多个Client,则会产生很多个线程。压力主要是在服务器端。客户端的压力并不大。

  2. 另外建立连接之后,并不是在时时刻刻的使用。会有空间时间。

  3. 会阻塞。

NIO模型图

特点:

  1. 事件驱动
  2. 多路复用
  3. Netty底层使用的NIO模型

AIO模型

目前还未得到广泛运用。异步非阻塞。先了解就可以。

BIO、NIO、AIO使用场景分析

  1. BOI方式使用与连接数目比较小固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中。JDK1.4以前的唯一选择。但是程序简单容易理解。
  2. NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务期间通讯等。编程比较复杂,JDK1.4开始支持。
  3. AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS操作系统参与并发操作,编程比较复杂,JDK7开始支持。但是目前还未得到广泛运用。

JAVA BIO编程

JAVA BIO 基本介绍

JAVA BIO 工作机制

JAVA BIO 应用实例

package com.dawa.netty.bio;

import com.sun.org.apache.xpath.internal.operations.String;

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
* 功能需求
* 1. 使用BIO模型编写一个服务器,监听6666端口,当有客户连接的时候,就启动一个客户端线程与之连接
* 2. 要求使用县城连接机制,可以连接过个客户端
* 3. 服务器端可以接受客户端发送的数据(TeInet方法即可)
*/
public class TestBIO {
public static void main(String[] args) throws Exception {
//1. 创建一个线程池. 这里 借助 Executors 这个工具类
ExecutorService pool = Executors.newCachedThreadPool();
//2. 建立一个监听服务,用来监听客户端连接
ServerSocket serverSocket = new ServerSocket(6666); while (true) {
final Socket socket = serverSocket.accept();
System.out.println("一个客户端连接");
//就创建一个线程与之通信
pool.execute(new Runnable() {
public void run() {
//编写一个处理方法.
handler(socket);
}
});
}
} public static void handler(Socket socket) {
byte[] bytes = new byte[1024]; try (InputStream inputStream = socket.getInputStream()) {
while (true) {
int read = inputStream.read(bytes);
if (read != -1) {
//注意这里,不能用String转换了.因为String已经不支持有参数的构造方法.
System.out.println(Arrays.toString(bytes));
} else {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("关闭连接");
} }
}

JAVA BIO 问题分析

JAVA NIO编程

NIO 基本介绍

  1. NIO的三个核心,channel相当于IO的Socket
  2. Buffer,Channel,Selector(选择器)三大核心组件。
  3. 通过Buffer实现非阻塞,
  4. 面向缓冲区,或者面向块编程(Buffer就是这样的)。
  5. NIO是事件驱动的

NIO Buffer 基本使用

这没有BooleanBuffer,另外StringBuffer继承自StringBuilder.

一个简单的Buffer子类的使用案例如下

package com.dawa.netty.bio;

import java.nio.IntBuffer;

public class TestNIO {
public static void main(String[] args) {
IntBuffer intBuffer = IntBuffer.allocate(5); for (int i = 0; i < intBuffer.capacity(); i++) {
System.out.println(intBuffer.put(i*5));
} intBuffer.flip(); while (intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
}
}

NIO 和 BIO 的比较

NIO 三大核心原理示意图

Selector、Channel和Buffer的关系图

  1. 每个Channel都会对应一个Buffer。
  2. Selector对应一个线程,一个线程对应多个Channel(连接)。
  3. 该图反应了有三个Channel注册到该Selector
  4. 程序切换到哪个Channel是由事件决定的。Event是一个重要概念。
  5. Select会根据不同的事件,在各个通道上切换。
  6. Buffer就是一个内存块、底层是由一个数组
  7. 数据的读取写入是通过Buffer,这个和BIO、BIO中要么是输入流、或者是输出流,不能双向,但是NIO的Buffer是可以读也可以写,需要flip()方法来进行切换。
  8. Channel也是双向的,可以返回底层操作系统的情况,比如Linux,底层的操作系统通道就是双向的。

三大核心——Buffer缓冲区详解

Buffer的Doc源码(java11)

基本介绍

Buffer的子类

容器对象(函数组),如何理解?从源码中可以看到。Int,Float等,每一个子类Buffer对象,都是[]数组。

具有的四个子类

private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
  1. 缓冲区的容量是它所包含的元素数量。 缓冲区的容量从不为负,从来没有改变。

  2. 缓冲区的限制是不应读取或写入的第一个元素的索引。 缓冲区的限制是从不为负,并且永远不会比它更大的容量。

  3. 缓冲区的位置要被读出或写入的下一个元素的索引。 缓冲区的位置永远不会为负,并且永远不会比它的极限。

  4. // Invariants: mark <= position <= limit <= capacity

代码跟踪-详解

Buffer类及其子类中的重要的方法

如,通过设置position的值,来读取指定位置的值。也可以修改limit的值等。

ByteBuffer

ByteBuffer,是最常用的。二进制数据。

三大核心—— Channel 通道详解

Channel接口的Doc源码(java11)

基本介绍

Channel的子类

FileChannel类

Channel应用实例

Channel应用实例1——本地文件写数据

实例代码如下:

package com.dawa.netty.nio;

import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; public class TestNIOFileChannel01 {
public static void main(String[] args) throws Exception{ //准备字符串
String string = "dawa,大娃,Bigbaby"; //准备输出流.指定输出的文件地址
FileOutputStream fileOutputStream = new FileOutputStream("dawa.txt"); //准备Channel管道. 对输出流进行封装,封装为一个channel管道.
FileChannel fileChannel = fileOutputStream.getChannel(); //准备一个byte数组, 也就是一个 Buffer数组,来缓存数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //读取数据
byteBuffer.put(string.getBytes()); //这里第一次没有反转,文件里面乱码
byteBuffer.flip(); //完成写的操作
fileChannel.write(byteBuffer); //关闭流
fileOutputStream.close();
}
}

注意:

  1. 是FileOutPutStream 包含 NIO FileChannel
  2. FileChannel的具体的实现类是:FileChannelImpl

Channel应用实例2——本地文件读数据

代码案例如下

package com.dawa.netty.nio;

import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; //本地 读文件
public class TestNIOFileChannel02 {
public static void main(String[] args) throws Exception { //读到文件
File file = new File("dawa.txt");
FileInputStream fileInputStream = new FileInputStream(file); //fileInputStream 包装为 Channel
FileChannel fileChannel = fileInputStream.getChannel(); //借助Buffer byte[]缓冲数
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length()); //将Channel的数据读入到byteBuffer
fileChannel.read(byteBuffer); System.out.println(new String(byteBuffer.array())); fileInputStream.close(); }
}

Channel应用案例3——使用Buffer完成文件的读写

类似于拷贝的操作,使用文件Channel+Buffer完成

代码案例如下

package com.dawa.netty.nio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; //使用一个Channel完成文件的读写
public class TestNIOFileChannel03 {
public static void main(String[] args) throws Exception{ FileInputStream fileInputStream = new FileInputStream("dawa.txt");
FileChannel channel01 = fileInputStream.getChannel(); FileOutputStream fileOutputStream = new FileOutputStream("erwa.txt");
FileChannel channel02 = fileOutputStream.getChannel(); //Buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(512); while (true) {
//这里注意使用Clear操作,不然会进入死循环
/**
* public Buffer clear() {
* position = 0;
* limit = capacity;
* mark = -1;
* return this;
* }
*/
byteBuffer.clear(); int read = channel01.read(byteBuffer);
if (read == -1) {
break;
}
//反转,切换流
byteBuffer.flip();
channel02.write(byteBuffer);
}
fileInputStream.close();
fileOutputStream.close();
}
}

这里需要注意的是使用clear操作,重置缓冲区基本参数

public Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}

Channel应用案例4——Transferform拷贝

transferFrom方法

    public long transferFrom(ReadableByteChannel src,
long position, long count)
throws IOException
{
ensureOpen();
if (!src.isOpen())
throw new ClosedChannelException();
if (!writable)
throw new NonWritableChannelException();
if ((position < 0) || (count < 0))
throw new IllegalArgumentException();
if (position > size())
return 0;
if (src instanceof FileChannelImpl)
return transferFromFileChannel((FileChannelImpl)src,
position, count); return transferFromArbitraryChannel(src, position, count);
}

案例如下

package com.dawa.netty.nio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; //使用一个Channel完成文件的读写
public class TestNIOFileChannel03 {
public static void main(String[] args) throws Exception{ FileInputStream fileInputStream = new FileInputStream("dawa.txt");
FileOutputStream fileOutputStream = new FileOutputStream("erwa.txt"); FileChannel sourceCH = fileInputStream.getChannel();
FileChannel destCH = fileOutputStream.getChannel(); //直接通过通道,完成拷贝
destCH.transferFrom(sourceCH, 0, sourceCH.size()); fileInputStream.close();
fileOutputStream.close(); }
}

关于Buffer和Channel的注意事项和注意细节

  1. 存取类型需要保持一致(存取顺序一致)

  2. Buffer可以转为只读Buffer

    byteBuffer.asReadOnlyBuffer();
   ![image-20200325061059067](https://tva1.sinaimg.cn/large/00831rSTly1gd5rck2zicj31ja0tyh7e.jpg)

3. **MappedBuffer 可以让文件直接在内存(堆外内存)修改,操作系统不需要拷贝一次**

   ![image-20200325061421909](https://tva1.sinaimg.cn/large/00831rSTly1gd5rge5zuaj30oi0b4tad.jpg)

   MappedByteBuffer是抽象类,实际能够操作的类型是 DirectByteBuffer

   > 代码案例如下:

   ```java
package com.dawa.netty.nio; import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel; //使用 MappedBuffer 直接完成文件在内存中的数据修改
public class MappedBuffer01 {
public static void main(String[] args) throws Exception { //获取一个读取文件流
RandomAccessFile randomAccessFile = new RandomAccessFile("dawa.txt","rw"); //获取指定的Channel
FileChannel channel = randomAccessFile.getChannel(); //读取模式. 0 代表从0开始, 5代表读取5个字节,也同时意味着只能在内存中操作这5个字节
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5); //对指定位置进行操作
mappedByteBuffer.put(0, (byte) 'A');
mappedByteBuffer.put(2, (byte) 9); randomAccessFile.close();
channel.close();
}
}
  1. Scattering&Gathering的使用

    Scattering:将数据写入到Buffer时,可以采用Buffer数组,依次写入【分散】

    Gathering:从Buffer读取数据时,可以采用Buffer数组,依次读【聚合】

    解决的问题:当一个数组不够用的时候,可以用数组组,来完成类似的操作

    代码案例如下:使用 数组,来完成客户端 - 服务器端 读取操作

    package com.dawa.netty.nio;
    
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Arrays; public class ScatteringGatheringGetPut {
    public static void main(String[] args) throws Exception { //创建服务器端的
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //监听端口号
    InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 7000); //绑定端口号到服务器端的Channel
    serverSocketChannel.socket().bind(inetSocketAddress); //创建Buffer数组
    ByteBuffer[] byteBuffers = new ByteBuffer[2];
    byteBuffers[0] = ByteBuffer.allocate(5);
    byteBuffers[1] = ByteBuffer.allocate(3); // 等待连接,获取连接, 并生成客户端的 Channel
    SocketChannel socketChannel = serverSocketChannel.accept(); //假设从 客户端读取 8个字节
    int messageLength = 8; while (true) {
    //1. 将客户端的数据, 读取
    int byteRead = 0;
    while (byteRead < messageLength) {
    long l = socketChannel.read(byteBuffers);
    System.out.println("byteRead = " + byteRead);
    byteRead += 1;//累积读取的字节数 //使用流打印,看看当前Buffer里面的position和limit
    Arrays.asList(byteBuffers).stream()
    .map(byteBuffer -> "position=" + byteBuffer.position() + ", limit=" + byteBuffer.limit()).forEach(System.out::println);
    } //将所有的 Buffer反转
    Arrays.asList(byteBuffers).forEach(ByteBuffer::flip); //2. 将读取到的数据,写回客户端
    int byteWrite = 0;
    while (byteWrite < messageLength) {
    socketChannel.write(byteBuffers);
    byteWrite += 1;
    } //将所有的Buffer进行Clear操作
    Arrays.asList(byteBuffers).forEach(ByteBuffer::clear); //读完之后,打印出来看看读写文件的长度
    System.out.println("byteRead = " + byteRead + ", byteWrite = " + byteWrite + ", messageLength" + messageLength); } }
    }

三大核心—— Selector 选择器详解

Selector的Doc源码(java11)

基本介绍

  1. 示意图:

Select示意图和特点说明

Selector的子类

Selector类的相关方法

PS:一个线程,对应一个Selector,每个Selector通过调用select()方法,获取不同的能够代表Channel的SelectionKey,得到一个能够被选择的Channel集合。

注意事项

NIO非阻塞网络编程原理分析图

NIO非阻塞网络相关的(Selector、SelectionKey、ServerSocketChannel和SocketChannel)关系图梳理。

对上图的说明

  1. 当客户端生成时,会通过ServerSocketChannel得到SocketChannel。

  2. Selector开始监听...Selector进行监听select方法,返回有事件发生的通道的个数。

  3. 将SocketChannel注册到Selector上.register(Selector sel,int ops).一个Selector上可以注册多个SocketChannel。

    SocketChannel的父类里面有注册方法

    SelectableChannel里面还有一个注册方法,这个用的比较多

  4. 注册后,返回一个SelectionKey,会和该Selector关联(集合)

  5. 进一步得到各个SelectionKey(有事件发生)

  6. 再通过SelectionKey 反向获取SocketChannel。

    SelectionKey类中的channel()方法

  7. 通过得到的Channel,完成业务处理

NIO非阻塞网络编程快速入门

创建服务器端。
1. 创建ServerSocketChannel ,并设置非阻塞
2. 得到一个Selector对象
3. 绑定一个端口6666.在服务器端监听
4. 把servrSocketChannel 注册到 selector 关心事件为 SelectionKey.OP_ACCEPT
5. 循环等待客户端连接
//这里我们等待一秒,如果没有事件发生,返回
1. if(selector.selecct(1000)==0){//没有事件发生
sout("服务器等待了一秒");
continue;
} //如果返回的值>0,就获取到相关的selectionKey集合
// 1. 表示已经获取到关注的事件。
// 2. 通过selectionKeys()返回关注的集合。
// 3. 通过selectionKeys
seletor.selectedKeys().var; //遍历得到的selectionKeys.
//1. 获取SelectionKey
//2. 根据key 对应的通道发生的事件做处理
//3. 如果是 OP_ACCEPT,有新的客户端连接
//1. 给该客户端生成一个SocketChannel
//2. 将SocketChannel 注册到select,关注事件为OP_READ,并关联一个Buffer
//4. 如果是 OP_READ,读取数据
//1. 通过key,反向获取对应的channel
//2. 获取到该channel关联的buffer
//5. 手动从集合中移动单签的SelectionKey,防止重复操作。 创建客户端。
1. 得到一个网络通道SocketChannel.并设置非阻塞
2. 提供服务区的IP和端口,连接服务器

服务器端代码

package com.dawa.netty.nio;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
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.Set; // NIO 服务端
public class TestNIOServer {
public static void main(String[] args) throws Exception { // 1. 创建ServerSocketChannel ,并设置非阻塞
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
// 2. 得到一个Selector对象
Selector selector = Selector.open();
// 3. 绑定一个端口6666.在服务器端监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
// 4. 把serverSocketChannel 注册到 selector 关心事件为 SelectionKey.OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 循环,等待客户端连接
while (true) {
if (selector.select(1000) == 0) {// 没有事件发生
System.out.println("服务器端等待1秒,没有客户端连接");
continue;
}
Set<SelectionKey> selectionKeys = selector.selectedKeys();//得到所有被选中的Key
//循环遍历每一个key,每一个key代表一个事件
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
//根据key对应的事件,做响应的处理
if (selectionKey.isAcceptable()) {//如果是 Accept事件, 连接事件,则生成对应的客户端Channel
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
//将SocketChannel 注册到select,关注事件为OP_READ,并关联一个Buffer
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if (selectionKey.isReadable()) {//如果是读事件
//1. 通过key,反向生成Channel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
//设置非同步NIO
socketChannel.configureBlocking(false);
//2. 获取到该channel关联的buffer
ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
socketChannel.read(buffer);
//打印出获取到的Buffer
System.out.println("from 客户端:" + new String(buffer.array()));
}
//这里一定要记得把处理过的key给移除掉,自己遇到了死循环.
iterator.remove();
}
}
}
}

客户端代码

package com.dawa.netty.nio;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel; //NIO 客户端
public class TestNIOClient {
public static void main(String[] args) throws Exception { SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
//服务器端的IP和端口
InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 6666); if (!socketChannel.connect(socketAddress)) {
System.out.println("连接失败,但是可以干其他事情,非阻塞");
while (!socketChannel.finishConnect()) {
System.out.println("在连接完成之前,我一直干其他的事情");
}
} String string = "hello,dawa";
socketChannel.write(ByteBuffer.wrap(string.getBytes()));
System.in.read();
}
}

SelectionKey

Selector.keys() 是 列出所有的key。

Selector.selectedKeys()是列出所有被选中的key。

这两个是不一样的。

ServerSocketChannel

SocketChannel

NIO网络编程应用实例——群聊系统

功能示意图

  1. 先写服务器端

    1. 服务器端启动并监听6667
    2. 服务器端接收客户端消息,并实现转发[处理上线和离线]
  2. 编写客户端
    1. 连接服务器
    2. 发送消息
    3. 接受服务器消息

服务器端代码

  1. 构造器初始化

  1. 监听方法

    Listen()



    里面循环的写法:

里面读数据的方法:

try catch完成离线处理

里面转发给其他客户端的方法

客户端代码

  1. 构造器初始化

  2. 向服务器发消息

  3. 读取从服务器端回复的消息

启动客户端和服务器端

  1. 启动客户端的方法

  2. 启动服务器端的方法

自己编码:实现群发

客户端代码

package com.dawa.netty.nio.group;

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.Scanner; //客户端
public class GroupCharClient { private SocketChannel socketChannel;
private static final int PORT = 6667;
private static final String HOST = "127.0.0.1";
private Selector selector;
private String userName; public GroupCharClient() throws IOException {
selector = Selector.open();
//连接服务器
socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
userName = socketChannel.getRemoteAddress().toString().substring(1);
} //发送消息
public void sendMessage(String message){
message = userName + "说" + message;
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
try {
socketChannel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
} //读取服务器端发来的消息
public void readMessage() {
try {
int readChannels = selector.select();
if (readChannels > 0) {//有可用的通道
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer); String msg = new String(buffer.array());
System.out.println(msg.trim());
}
}
} else {
//没有可用的通道
} } catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) throws IOException {
GroupCharClient groupCharClient = new GroupCharClient(); new Thread(() -> {
while (true) {
groupCharClient.readMessage();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start(); //发送数据给服务器端
Scanner scanner = new Scanner(System.in); while (scanner.hasNextLine()) {
String message = scanner.nextLine();
groupCharClient.sendMessage(message);
}
} }

服务器端代码

package com.dawa.netty.nio.group;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator; /**
* 服务器端代码
*/
public class GroupChatServer {
private ServerSocketChannel serverSocketChannel;
private Selector selector;
private static final int PORT = 6666; public GroupChatServer() {
try {
//得到选择器
selector = Selector.open();
//绑定端口
serverSocketChannel = ServerSocketChannel.open().bind(new InetSocketAddress(PORT));
} catch (IOException e) {
e.printStackTrace();
}
} //服务器端监听的方法
public void listen() {
try {
//循环监听
while (true) {
int count = selector.select();
if (count > 0) {
Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys().iterator();
while (selectionKeyIterator.hasNext()) {
//取出Key
SelectionKey key = selectionKeyIterator.next(); //判断事件
if (key.isAcceptable()) {//监听访问
//key 转Channel
SocketChannel channel = serverSocketChannel.accept();
SocketAddress remoteAddress = channel.getRemoteAddress();
System.out.println(remoteAddress + ":上线了");
}
if (key.isReadable()) {//读取事件
//处理读
readData(key);
}
//移除已经处理的key
selectionKeyIterator.remove();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
} //读数据
public void readData(SelectionKey key) {
SocketChannel channel = null;
try {
//根据key,取得Channel
channel = (SocketChannel) key.channel();
channel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = channel.read(buffer);
if (read > 0) {
String message = new String(buffer.array());
System.out.println("from: 客户端" + message); // 向其他用户,转发消息
sendMessageToOtherCLient(message,key);
} } catch (IOException e) {
try {
System.out.println(channel.getRemoteAddress() + " :离线了");
key.cancel();
channel.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
} //向其他用户转发消息
public void sendMessageToOtherCLient(String message,SelectionKey self){
System.out.println("服务器转发消息ing"); selector.keys().forEach(key -> {
//根据Key,取出对应的SocketChannel.或者是ServerSocketChannel
Channel targetChannel = key.channel();
//排除自己
if (targetChannel instanceof SocketChannel && targetChannel != self) {
//转型
SocketChannel dest = (SocketChannel) targetChannel;
//Buffer
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
try {
dest.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}); } public static void main(String[] args) {
GroupChatServer groupChatServer = new GroupChatServer();
groupChatServer.listen();
}
}

NIO与零拷贝

什么是零拷贝?

零拷贝,指的是没有CPU拷贝,而不是不拷贝。从操作系统角度看的。

基本介绍

传统IO数据读写

传统IO模型图,状态切换:用户态和内核态的切换:4次拷贝,3次切换

MMAP优化

Mmap优化:3次拷贝,3次切换

DMA拷贝:direct memory accect:直接内存访问

sendFile优化

sendFile优化:三次拷贝,两次切换

零拷贝

零拷贝,指的是没有CPU拷贝,而不是不拷贝。从操作系统角度看的。

这里其实还有一次CPU拷贝的:kernel buffer->socket buffer但是,拷贝的信息很少,比如length,offset,消耗低,可以忽略

零拷贝:2次拷贝,2次切换。

零拷贝是我们在进行网络传输的重要优化手段。

mmap和sendFile的区别:

NIO零拷贝案例

传统IO流案例

传统IO耗费时间:60毫秒

零拷贝案例。(NIO)

transferTo 底层用的就是零拷贝

NIO零拷贝耗时时间:20毫秒

Java AIO编程

Java AIO基本介绍

这里暂时不深入扩展。

BIO、NIO、AIO对比表

Java IO模型:BIO、NIO、AIO的更多相关文章

  1. I/O模型系列之三:IO通信模型BIO NIO AIO

    一.传统的BIO 网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请 ...

  2. JAVA中的BIO,NIO,AIO

    在了解BIO,NIO,AIO之前先了解一下IO的几个概念: 1.同步 用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪, 例如自己亲自出马持银行卡到银行取钱 2.异步 用户触发IO操作以后, ...

  3. 温故知新-java的I/O模型-BIO&NIO&AIO

    文章目录 摘要 传统的BIO编程 伪异步I/O编程 NIO编程 AIO编程 几种IO模型的对比 netty 参考 你的鼓励也是我创作的动力 Posted by 微博@Yangsc_o 原创文章,版权声 ...

  4. JAVA-IO模型(BIO,NIO,AIO)

    基本概念 阻塞和非阻塞 阻塞是进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待, 直到有东西可读或者可写为止 非阻塞是如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等 ...

  5. [转]BIO/NIO/AIO的几个思考

    原文:https://www.jianshu.com/p/ff29e028af07 ----------------------------------------------------- BIO/ ...

  6. Java提供了哪些IO方式?IO, BIO, NIO, AIO是什么?

    IO一直是软件开发中的核心部分之一,而随着互联网技术的提高,IO的重要性也越来越重.纵观开发界,能够巧妙运用IO,不但对于公司,而且对于开发人员都非常的重要.Java的IO机制也是一直在不断的完善,以 ...

  7. (转)也谈BIO | NIO | AIO (Java版)

    原文地址: https://my.oschina.net/bluesky0leon/blog/132361 关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一 ...

  8. 也谈BIO | NIO | AIO (Java版--转)

    关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一个解释: BIO | NIO | AIO,本身的描述都是在Java语言的基础上的.而描述IO,我们需要从两个 ...

  9. IO回忆录之怎样过目不忘(BIO/NIO/AIO/Netty)

    有热心的网友加我微信,时不时问我一些技术的或者学习技术的问题.有时候我回微信的时候都是半夜了.但是我很乐意解答他们的问题.因为这些年轻人都是很有上进心的,所以在我心里他们就是很优秀的,我愿意多和努力的 ...

随机推荐

  1. KMP算法解题模板(更新)

    /* kmp算法的主要作用在于对next数组的运用,所以这里只给出next数组的模板 性质1:对于每一个长度len的子串,该子串的最小循环节为len-next[len] 性质2:kmp的next不断向 ...

  2. Struts-S2-045漏洞利用

    最近也是在看Struts2的漏洞,这里与大家共同探讨一下,本次我复现的是s2-045这个编号的漏洞 漏洞介绍 Apache Struts 2被曝存在远程命令执行漏洞,漏洞编号S2-045,CVE编号C ...

  3. fluem读取文件并写入到hadoop的hdfs

    接上一章,本章介绍使用 crontab 像指定文件定时写入,使用fluem 读取并写入到hadoop的hdfs 前提准备已安装好fluem ,和hadoop(推荐单机即可毕竟做实验) 一.进入终端执行 ...

  4. 《剑指offer》面试题48. 最长不含重复字符的子字符串

    问题描述 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度.   示例 1: 输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串 ...

  5. Cesium入门4 - 创建Cesium Viewer

    Cesium入门4 - 创建Cesium Viewer Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ 任何Ce ...

  6. gin中自定义中间件

    package main import ( "github.com/gin-gonic/gin" "log" "time" ) func L ...

  7. java单例模式(饿汉式和懒汉式)

    1 /* 2 * 设计模式:对问题行之有效的解决方式.其实它是一种思想. 3 * 4 * 1,单例设计模式 5 * 解决的问题:就是可以保证一个类在内容中的对象唯一性. 6 * 7 * 必须对于多个程 ...

  8. 前端HTML基础之form表单

    目录 一:form表单 1.form表单功能 2.表单元素 二:form表单搭建(注册页面) 1.编写input会出现黄色阴影问题 三:完整版,前端代码(注册页面) 四:type属性介绍 1.inpu ...

  9. 线程终止的四种方式,interrupt 方法使用的简单介绍。

    一 正常结束. package com.aaa.threaddemo; /* 一 终止线程的四种方式? * 程序运行结束,线程终止. * */ public class ThreadTerminati ...

  10. 在Linux虚拟机上挂载文件卷

    一 通过跳板机 将卷挂载在ec2 实例上的方法. 1 查询 机器上挂载了那些卷? // lsblk 是否已经是挂载卷 查看后面的目录 如果没有就是未挂载. 2 操作未挂载卷? /* sudo file ...