Socket-IO 系列(三)基于 NIO 的同步非阻塞式编程

  • 缓冲区(Buffer) 用于存储数据

  • 通道(Channel) 用于传输数据

  • 多路复用器(Selector) 用于轮询 Channel 状态,四种状态:Connect(连接),Accept(阻塞),Read(读),Write(写)

一、Buffer(缓冲区)

不同于面向流的 IO 中将数据直接写入或读取到 Stream 对象中,在 NIO 中,所有数据都是用缓冲区处理(读写)。缓冲区通常是一个字节数组(ByteBuffer),这个数组提供了数据的访问读写操作属性,如 position、limit、capacity、mark 等。

Buffer 类型:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。

  • capacity: 容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。

  • limit: 界限,表示缓冲区中可以操作数据的大小。(limit后的数据不能读)

  • position: 位置,表示缓冲区中正在操作数据的位置

  • mark: 标记,表示记录当前 postion 的位置。可以通过 reset 恢复到时 mark 的位置。

注:mark <= postion <= limit <= capacity

import java.nio.ByteBuffer;

public class BufferTest {

   public static void main(String[] args) {
//1. 分配一个非直接缓冲区(用户地址空间,即JVM)
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println(buf);//java.nio.HeapByteBuffer[pos=0 lim=1024 cap=1024] 堆空间 //2. 向buf中写入数据
buf.put("abcd".getBytes());
System.out.println(buf); //[pos=4 lim=1024 cap=1024] //3. 切换成读模式
buf.flip(); //limit = position; position = 0; mark = -1;
System.out.println(buf); //[pos=0 lim=4 cap=1024] //4. buf.get()读数据
for (int i = 0; i < buf.limit(); i++) {
System.out.println((char) buf.get());
}
System.out.println(buf); //[pos=4 lim=4 cap=1024] //5. buf.rewind()再读数据
buf.rewind(); //position = 0; mark = -1;
System.out.println(buf); //[pos=0 lim=4 cap=1024] //6. buf.get(bytes)读一个字节数组
byte[] bytes = new byte[buf.limit()];
buf.get(bytes);
System.out.println(new String(bytes)); //[pos=4 lim=4 cap=1024] //7. buf.reset()复位到上一个标记位
buf.position(1).mark();
System.out.println(buf);
buf.get();
System.out.println(buf);
buf.reset();
System.out.println(buf); //8. buf.hasRemaining()
if (buf.hasRemaining()) {
System.out.println(buf.remaining());
} //9. buf.duplicate()
ByteBuffer buf2 = buf.duplicate(); //10. buf.clear() 清空缓冲区,但是缓冲区依旧存在,只是处于“遗忘”状态
buf.clear();
}
}

1.1 直接缓冲区和非直接缓冲区

  • 非直接缓冲区:将缓冲区开辟在用户地址空间(JVM)。

    ByteBuffer.allocate(1024);

JDK 源码:

public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
//在堆中开辟空间
return new HeapByteBuffer(capacity, capacity);
}
  • 直接缓冲区(少用):将缓冲区直接开辟在内核地址空间(OS),减少了将数据从内核地址空间复制到用户地址空间的过程,提高了效率。但直接操作内核地址不安全,分配销毁开销大,不易操作。一般情况下,用于本机IO操作影响的大型、持久的缓冲区,并且性能有明显提升的情况。

    ByteBuffer.allocateDirect(1024);

JDK 源码:

public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}

二、Channel(通道)

通道:IO 中源与目标之前的连接,负责传输数据。缓冲区用于存储数据。

java.nio.channels.Channel 接口:
|--FileChannel
|--SocketChannel
|--ServerSocketChannel
|--DatagramChannel

2.1 获取通道的有有三种方法

  1. Java 针对支持通道的类提供了 getChannel() 方法。

    • 本地IO FileInputStream/FileOutputStream/RandomAccessFile

    • 网络IO Socket/ServerSocket/DatagramSocket

  2. 在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法 open()

  3. 在 JDK 1.7 中的 NIO.2 的 Files 工具类的 newByteChannel()

// 通过通道传输数据
public void test1() throws Exception {
FileInputStream fis = new FileInputStream("1.png");
FileOutputStream fos = new FileOutputStream("2.png"); //1. 获取通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel(); //2. 分配缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024); //3. 用通道传输数据
while (inChannel.read(buf) != -1) {
buf.flip();
outChannel.write(buf);
buf.clear();
} //4. 关闭
outChannel.close();
inChannel.close();
fis.close();
fos.close();
} // 内存映射文件,直接缓冲区
public void test2 () throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("3.png"),
StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); //内存映射文件,直接缓冲区
MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size()); byte[] bytes = new byte[inMappedBuf.limit()];
inMappedBuf.get(bytes);
outMappedBuf.put(bytes); inChannel.close();
outChannel.close();
} // transferTo
public void test3 () throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("4.png"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); //内存映射文件,直接缓冲区
inChannel.transferTo(0, inChannel.size(), outChannel);
//outChannel.transferFrom(inChannel, 0, inChannel.size()); inChannel.close();
outChannel.close();
}

2.2 分散(Scatter)与聚集(Gather)

  • 分散读取(Scatter Read):按照缓冲区的顺序,从 Channel 中读取的依次将缓冲区填满

  • 聚集写入(Gather Write):将多个缓冲区的数据依次写入 Channel

public static void main(String[] args) throws IOException {
RandomAccessFile raf = new RandomAccessFile("1.txt", "rw");
FileChannel inChannel = raf.getChannel(); //1. 获取通道
ByteBuffer buf1 = ByteBuffer.allocate(10);
ByteBuffer buf2 = ByteBuffer.allocate(20); // Gather
ByteBuffer[] bufs = {buf1, buf2};
inChannel.read(bufs); for (ByteBuffer buf : bufs) {
buf.flip();
} System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
System.out.println(new String(bufs[1].array(), 0, bufs[1].limit())); // Scanner
RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
FileChannel outChannel = raf2.getChannel(); outChannel.write(bufs); }

2.2 补充:编码与解码

// 输出有效的字符集
public void test1 () {
SortedMap<String, Charset> charsets = Charset.availableCharsets();
for (Map.Entry<String, Charset> me : charsets.entrySet()) {
System.out.println(me.getKey() + ": " + me.getValue());
}
} // 编码与解码
public void test2 () throws CharacterCodingException {
Charset cs = Charset.forName("gbk"); //编码器和解码器
CharsetEncoder encoder = cs.newEncoder();
CharsetDecoder decoder = cs.newDecoder(); CharBuffer cBuf = CharBuffer.allocate(1024);
cBuf.put("中华人民共和国");
cBuf.flip(); //编码
ByteBuffer bBuf = encoder.encode(cBuf); try {
System.out.println(new String(bBuf.array(), "gbk"));
} catch (UnsupportedEncodingException e) {
;
} //解码
CharBuffer cBuf2 = decoder.decode(bBuf);
System.out.println(cBuf2.limit());
System.out.println(cBuf2.toString());
}

三、Selector(多路复用器)


每天用心记录一点点。内容也许不重要,但习惯很重要!

Socket-IO 系列(三)基于 NIO 的同步非阻塞式编程的更多相关文章

  1. 基于NIO的同步非阻塞编程完整案例,客户端发送请求,服务端获取数据并返回给客户端数据,客户端获取返回数据

    这块还是挺复杂的,挺难理解,但是多练几遍,多看看研究研究其实也就那样,就是一个Selector轮询的过程,这里想要双向通信,客户端和服务端都需要一个Selector,并一直轮询, 直接贴代码: Ser ...

  2. NIO非阻塞式编程

    /** * NIO非阻塞式编程<p> * 服务端和客户端各自维护一个管理通道的对象,我们称之为selector,该对象能检测一个或多个通道 (channel) 上的事件. * 我们以服务端 ...

  3. NIO【同步非阻塞io模型】关于 NIO socket 的详细总结【Java客户端+Java服务端 + 业务层】【可以客户端间发消息】

    1.前言 以前使用 websocket来实现双向通信,如今深入了解了 NIO 同步非阻塞io模型 , 优势是 处理效率很高,吞吐量巨大,能很快处理大文件,不仅可以 做 文件io操作, 还可以做sock ...

  4. NIO【同步非阻塞io模型】关于 文件io 的总结

    1.前言 这一篇随笔是写 NIO 关于文件输入输出的总结 /* 总结: 1.io操作包括 socket io ,file io ; 2.在nio模型,file io使用fileChannel 管道 , ...

  5. IO同步阻塞与同步非阻塞

    BIO.NIO.AIO IO(BIO)和NIO区别:其本质就是阻塞和非阻塞的区别 阻塞概念:应用程序在获取网络数据的时候,如果网络传输数据很慢,就会一直等待,直到传输完毕为止. 非阻塞概念:应用程序直 ...

  6. 如何解读 Java IO、NIO 中的同步阻塞与同步非阻塞?

    原文链接:如何解读 Java IO.NIO 中的同步阻塞与同步非阻塞? 一.前言 最近刚读完一本书:<Netty.Zookeeper.Redis 并发实战>,个人觉得 Netty 部分是写 ...

  7. java的高并发IO原理,阻塞BIO同步非阻塞NIO,异步非阻塞AIO

    原文地址: IO读写的基础原理 大家知道,用户程序进行IO的读写,依赖于底层的IO读写,基本上会用到底层的read&write两大系统调用.在不同的操作系统中,IO读写的系统调用的名称可能不完 ...

  8. 同步异步阻塞非阻塞Reactor模式和Proactor模式 (目前JAVA的NIO就属于同步非阻塞IO)

    在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作. 在比较这两个模式之前,我们首先的搞明白 ...

  9. IO通信模型(二)同步非阻塞模式NIO(NonBlocking IO)

    同步非阻塞模式(NonBlocking IO) 在非阻塞模式中,发出Socket的accept()和read()操作时,如果内核中的数据还没有准备好,那么它并不会阻塞用户进程,而是立刻返回一个信息.也 ...

随机推荐

  1. flask框架预备知识

    1.web预备知识 2.flask介绍 3.web框架的本质及分类 4.flask安装与基本设置 1.web预备知识 HTTP协议:https://www.cnblogs.com/wyb666/p/9 ...

  2. Python分页转Mybatis pagehelper格式分页

    最近工作里遇到一个需求要把之前用Java写的一个http接口替换成用Python写的,出参是带了mybatis pageHelper中PageInfo信息的一个JSON串,而Python这边分页不会涉 ...

  3. pycharm最新版新建工程没导入本地包问题:module 'selenium.webdriver' has no attribute 'Firefox'

    前言 最新版的pycharm做了很大的改变,新建工程的时候,默认不导入本地的安装包,这就导致很多小伙伴踩坑了... 明明已经pip安装过selenium了,但是却报AttributeError:mod ...

  4. NLP—WordNet——词与词之间的最小距离

    WordNet,是由Princeton 大学的心理学家,语言学家和计算机工程师联合设计的一种基于认知语言学的英语词典.它不是光把单词以字母顺序排列,而且按照单词的意义组成一个“单词的网络”.我们这次的 ...

  5. 17.在Action获取Scope对象

    转自:https://wenku.baidu.com/view/84fa86ae360cba1aa911da02.html 引言:在前面的Action操作中,关键就是Action中的exectue方法 ...

  6. a 超链接标签

    Title百度 第一章 第一章 第一章内容 第二章内容 <!DOCTYPE html><html lang="en"><head> <me ...

  7. [ERR] Not all 16384 slots are covered by nodes.

    redis集群开不起来,用redis-cli连接的时候出现如下错误: CLUSTERDOWN The cluster is down 然后使用redis-trib.rb检查: ./redis-trib ...

  8. 遇到的IE不兼容问题总结

    IE浏览器兼容问题困扰多时,由于IE6不在进行修补,IE6的考虑也越来越少,有些IE遇到的不兼容的现象想做一个总结 1宽度或高度:IE的是width+border+margin+padding goo ...

  9. 判断一个对象是否为真 __nonzero__ 方法和 __len__方法

    class A(): def __nonzero__(self): # 判断 一个对象是否为空,先查看该方法的返回值 return 1 def __len__(self): # 如果没有上一个方法,那 ...

  10. Intellij IDEA Debug

    Debug用来追踪代码的运行流程,通常在程序运行过程中出现异常,启用Debug模式可以分析定位异常发生的位置,以及在运行过程中参数的变化.通常我们也可以启用Debug模式来跟踪代码的运行流程去学习三方 ...