NIO工作方式浅析

java Socket 工作机制

  • Socket是描述计算机之前相互通信的一种抽象功能。通过基于TCP/IP的流套接字协议建立连接
  • A机器B机器通信—建立Socket连接—通过TCP连接(端口号指定唯一应用)----IP寻址(寻找唯一主机)----最终找到唯一主机上的唯一应用
建立通信链路
  1. 客户端:
  • 客户端建立链路之前需要有一个Socket实例,操作系统为Socket实例做如下操作

    • 分配一个没被使用的端口号
    • 创建包含本地地址 + 远程地址 + 端口号 的套接字数据结构
  • 这个数据结构会一直保存在系统,直到链接关闭

  • 创建Socket实例返回之前,需要进行TCP三次握手协议,完成TCP握手协议才算完成Socket创建

  1. 服务端:
  • 端口分配+监听:与客户的对应创建ServerSocket实例,只需要端口没被占就能创建成功
    • 操作系统页会创建底层数据接口包括:监听端口 + 监听地址通配符(例如 *,匹配所有)
  • 阻塞+创建套接字数据结构:之后调用accept(),进入阻塞,等待客户端请求,当一个请求到了,操作系统将为这个连接创建一个套接字数据结构,包括如下信息
    • 请求源地址端口 + 请求源端口
  • 完成TCP三次握手:将创建的套接字关联到ServerSocket实例的一个未完成的链接数据结构列表中(此时还未建立与客户端的Socket)
  • 完成套接字数据状态转换:等到客户端与服务端TCP三次握手完成后,将这个ServerSocket才算创建完成,此时会将对应套接字数据结构从未完成列表移动到已完成列表
  • 如上即与ServerSocket所关联的列表中每一个数据结构都代表与一个客户端建立的TCP链接

NIO工作方式

BIO瓶颈
  • BIO即阻塞I/O,不管是磁盘还是网络I/O,数据写入OutputStream或者从InputStream读取都会阻塞,一旦阻塞,线程就失去了CPU的使用权,这在高并发情况下性能是无法接收到。
  • 情况一在需要大量HTTP长连接的场景下,例如实时聊天,服务端不可能同时维护几百万用户的HTTP链接,因为这些链接并不是每时每刻都在传输数据。这种情况下保有大量HTTP是不可取的,即使线程数够,也会浪费大量的硬件机器资源,同时这种情况我们也无法判断那个HTTP的优先级更高,先处理哪个
  • 另外一种情况客户端多线程并发获取服务端资源,此时需要同步服务端资源情况,这种同步情况比单线程复杂的多。

在这里插入图片描述

NIO工作机制
  • NIO中关键类:Channel 和Selector 是NIO中核心概念
  • Channel类比Socket,但是比Socket更具体,可以看成是数据的运输工具
  • Selector类似运输工具的调度系统,控制Channel的状态,是正在发送,还是已经卸货等,他在轮询每个Channel的状态
  • Buffer类 类比Stream,但是比Stream更具体,Buffer是数据的容器,但是粒度比Channel要小,一个Channel中可能有N个Buffer。
    • Stream不一样,他只代表一个大的数据容器,在数据装载之前并不知道任何信息,因为他都封装在运输工具Socket中了
  • NIO引入Channel,Buffer,Selector就是将信息具体化,每个细节都开放接口处理给程序员控制。如下图所示

在这里插入图片描述

  • 有如下案例
/*** @author liaojiamin* @Date:Created in 15:11 2022/7/29*/
public class ServerSelectorDemo {public void selector() throws IOException {ByteBuffer byteBuffer = ByteBuffer.allocate(1024);Selector selector = Selector.open();ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.socket().bind(new InetSocketAddress(8080));//注册监听事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);while (true){//获取所有key集合Set selectedKeys = selector.selectedKeys();Iterator iterator = selectedKeys.iterator();while (iterator.hasNext()){SelectionKey selectionKey = (SelectionKey) iterator.next();if((selectionKey.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT){ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();//接受服务端请求SocketChannel socketChannel = serverSocketChannel1.accept();socketChannel.configureBlocking(false);socketChannel.register(selector, SelectionKey.OP_READ);iterator.remove();}else if ((selectionKey.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ){SocketChannel socketChannel = (SocketChannel) selectionKey.channel();while (true){byteBuffer.clear();//读取数据int n = socketChannel.read(byteBuffer);if(n <= 0){break;}byteBuffer.flip();}iterator.remove();}}}}
}
  • 如上代码中是基础的NIO模式:
    • 通过Selector静态工厂创建一个选择器
    • 创建一个服务端Channel,并且绑定到一个Socket对象,将将这个通信信道注册到选择器上
    • 设置是否阻塞模式,接着就能调用Selector的 selectedKeys检查这个已经注册的选择器上所有通信信道是否有需要的事情发生
    • 如果有某个指定类型事件,就返回所有的SelectionKey,通过这个对象的Channel可以获取到通信信道对象,从而读取通信的数据 – Buffer,这个Buffer是我们可以控制的缓冲器。
  • Selector可以同时监听一组通信信道(Channel)上的I/O状态,前提是这个Selector已经注册到这些通信信道中
Buffer 工作方式
  • Buffer中索引及说明
    • capacity:缓冲区数组总长度
    • position:下一个要操作的数据元素数据
    • limit:缓冲区数组中不可操作的下一个元素的位置, limit <= capacity
    • mark:用于记录当前position的前一个位置或者默认是0
  • Buffer 通过ByteBuffer.allocate(11) 方式创建一个byte数组缓冲区,初始状态写入如下图,position位置0 ,capacity和limit默认都是数组长度,当写入11个byte时候,位置变化如下,代码如下
//初始化方法
ByteBuffer(int mark, int pos, int lim, int cap,   // package-privatebyte[] hb, int offset){super(mark, pos, lim, cap);this.hb = hb;this.offset = offset;}
//super是调用Byffer中的初始化方法
Buffer(int mark, int pos, int lim, int cap) {       // package-privateif (cap < 0)throw new IllegalArgumentException("Negative capacity: " + cap);this.capacity = cap;limit(lim);position(pos);if (mark >= 0) {if (mark > pos)throw new IllegalArgumentException("mark > position: ("+ mark + " > " + pos + ")");this.mark = mark;}}
  • 当写入4个byte后的示意图
    请添加图片描述

  • 当调用byteBuffer.flip()方法时候,数组的状态如下
    请添加图片描述

  • 此时底层操作系统就可以从缓冲区中正确读取这5个字节数据,并且发送出去,在下一次写入数据之前我们在调用clear()方法,缓冲区的索引又会回到初始位置。

  • mark的作用:当调用mark()方法时候,会记录当前position的前一个位置,我们需要调用reset时候,position恢复mark记录的值

allocate 与 allocateDirect的区别
  • 通过allocate分配的内存,我们会通过Clannel获取I/O 数据,

    • 首先要通过操作系统Socket缓冲区:这个操作系统缓冲区就是底层TCO管理的RecvQ或者SendQ队列
    • 接着再将数据复制到Buffer
  • 在这两个区域的复制过程(操作系统缓冲区 ----- 用户缓冲区)是很耗性能的

  • 通过allocateDirect分配的内存:直接操作操作系统缓冲区

    • 方法返回与底层存储空间关联的缓冲区,通过Native代码操作费JVM堆内存
    • 缺点是必须每次创建或者释放都调用一次System.gc()
    • 如下allocateDirect初始化源码,都是用的unsafe 去分配的内存
DirectByteBuffer(int cap) {                   // package-privatesuper(-1, 0, cap, cap);boolean pa = VM.isDirectMemoryPageAligned();int ps = Bits.pageSize();long size = Math.max(1L, (long)cap + (pa ? ps : 0));Bits.reserveMemory(size, cap);long base = 0;try {base = unsafe.allocateMemory(size);} catch (OutOfMemoryError x) {Bits.unreserveMemory(size, cap);throw x;}unsafe.setMemory(base, size, (byte) 0);if (pa && (base % ps != 0)) {// Round up to page boundaryaddress = base + ps - (base & (ps - 1));} else {address = base;}cleaner = Cleaner.create(this, new Deallocator(base, size, cap));att = null;}
HeapByteBufferDirectByteBuffer
存储位置java Heap中DirectByteBuffer
I/O需要用户地址空间和操作系统内核地址空间复制数据不需要复制
内存管理Java GC回收,创建 并且 回收开销少通过System.gc()要释放Java对象引用的DirectByteBuffer内存空间,如果Java对象长时间持有引用可能导致Native内存泄露。创建和回收内存开销大
使用场景并发连接数少于1000,I/O操作较少比时候比较合适数据量大,生命周期长的情况下合适
NIO数据访问方式
  • NIO提供了比传统文件访问更好的方法,两个优化方法:FileChannel.transferTO,FileChannel.transferFrom, 另外一个是FileChannel.map

    • FileChannel.transferXXX:与传统访问文件方式比较,减少数据从内核到用户控件的复制过程。数据直接在内核空间中移动。
  • 传统的数据访问方式
    请添加图片描述

  • FileChannel.transferXXX的访问方式
    请添加图片描述

  • 如上图中,可以看到,不管是读还是写的方式,都能减少用户地址空间到内核地址空间数据复制的这一个步骤

  • FileChannel.map的方式,也同样的能按照一定大小块映射为内存区域。当范问这块内存的时候就是直接操作文件了。这样就省去了数据从内核到用户空间的复制

  • 这种方式适合对大文件的只读操作。比如文件的MD5校验等,

  • 如下一个实现案例。

/*** @author liaojiamin* @Date:Created in 14:09 2022/8/1*/
public class FileChannelMapCopyFile {public static void main(String[] args) throws FileNotFoundException {int BUFFER_SIZE = 1024;String fileName = "E:\\learn\\问题汇总\\MYSQL.md";long fileLength = new File(fileName).length();int bufferCount = 1+ (int) (fileLength / BUFFER_SIZE);MappedByteBuffer[] byteBuffers = new MappedByteBuffer[bufferCount];long remaining = fileLength;String fileName_1 = "E:\\learn\\问题汇总\\MYSQL_1.md";FileOutputStream fileOutputStream = new FileOutputStream(fileName_1);FileChannel writeChannel = fileOutputStream.getChannel();for (int i = 0; i < bufferCount; i++) {RandomAccessFile file;try {file = new RandomAccessFile(fileName, "r");Integer size = (int)Math.min(remaining, BUFFER_SIZE);byteBuffers[i] = file.getChannel().map(FileChannel.MapMode.READ_ONLY, i * BUFFER_SIZE, size);ByteBuffer byteBuffers1 = byteBuffers[i].get(new byte[size]);byteBuffers1.flip();writeChannel.write(byteBuffers1);}catch (Exception e){e.printStackTrace();}remaining -= BUFFER_SIZE;}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310249.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

bufferedimage生成的图片模糊_Kaptcha图片验证码工具

阅读文本大概需要3分钟。验证码的作用图片验证码自从诞生以来从未被抛弃&#xff0c;依然发出属于它所应有的光。验证码经常验证如下一些场景。1、用户登录&#xff0c;防止机器人登录2、论坛留言&#xff0c;防止恶意灌水3、短信验证码发送&#xff0c;防止盗刷短信Kaptcha 简…

[Java基础]对象(反)序列化流

对象序列化流: 代码如下: package ObjectOutputStreamPack;import java.io.Serializable;public class Student implements Serializable {private String name;private int age;public Student() {}public Student(String name, int age) {this.name name;this.age age;}pu…

C# 9 新特性:代码生成器、编译时反射

前言今天 .NET 官方博客宣布 C# 9 Source Generators 第一个预览版发布&#xff0c;这是一个用户已经喊了快 5 年特性&#xff0c;今天终于发布了。简介Source Generators 顾名思义代码生成器&#xff0c;它允许开发者在代码编译过程中获取查看用户代码并且生成新的 C# 代码参与…

I/O性能与可靠性

I/O调优 磁盘I/O优化 性能检测&#xff1a; 压力测试应用程序&#xff0c;观察系统I/O wait指标是否正常&#xff0c;例如有n个CPU&#xff0c;利息情况下I/O wait参数不超过25%&#xff0c;如果超过&#xff0c;就是这个程序的瓶颈就是在IO操作上了可以用iostat命令查看另外…

.NET开源工具类库:Masuit.Tools

【开源框架】| 通用工具类库这是恰童鞋骚年的第223篇原创文章本文介绍一个我的同事【懒得勤快】&#xff08;人称勤快哥&#xff0c;我们叫他骚哥&#xff09;写的一个.NET开源工具类库项目&#xff0c;包含一些常用的操作类&#xff0c;大都是静态类&#xff0c;加密解密&…

[Java基础]字节,字符打印流

代码如下&#xff1a; package PrintWriterPack;import java.io.FileNotFoundException; import java.io.PrintWriter;public class PrintWriterDemo {public static void main(String[] args) throws FileNotFoundException {PrintWriter pw new PrintWriter("D:\\Java…

javaI/O包中的包装模式

设计模式解析–适配器模式 对适配器模式功能比较好理解&#xff0c;就是讲一个类的接口换成客户端所能接受的另外一个接口&#xff0c;从而使两国接口不匹配而无法在一起工作的两个类能在一起工作。 适配器的结构 适配器UML图如下 Target&#xff08;目标接口&#xff09;&…

DevOps vs. Agile:它们有什么共同点?

导语DevOps与Agile有很多不同&#xff0c;但它们之间仍可发现很多共同点&#xff0c;这篇文章为读者揭晓。正文DevOps和Agile之间有着明显的关系。Agile是方法论&#xff0c;Scrum是框架&#xff0c;并DevOps随着看板也落在了Agile的“伞”下。精益&#xff0c;大规模的Scrum&a…

[Java基础]Properties

代码如下: package PropertiesPack;import java.util.Properties; import java.util.Set;public class PropertiesDemo01 {public static void main(String[] args){Properties prop new Properties();prop.put("001","Tom");prop.put("002",&…

空心点_空心砖的新玩法,看完大开眼界

我们所见的空心砖大都在建筑工地上常见的有水泥空心砖和粘土空心砖特点是轻质、环保、保温和隔音如此常见的空心砖仅仅是只为建筑而生吗不是&#xff0c;不管是古朴砖红色空心砖&#xff0c;还是高冷的水泥空心砖他们可以演绎不同的角色甚至让每个渴望回归本真的我们&#xff0…

IO与零拷贝

IO与零拷贝 零拷贝基本介绍 零拷贝时网络编程的一个关键优化点在Java程序中&#xff0c;常用的零拷贝又mmap&#xff08;内存映射&#xff09;和sendFile。那么在OS中的设计时如何&#xff0c;我们需分析mmap和sendFile对比最后通过案例分析 用户进程与操作系统关系 我们先…

从堆里找回“丢失”的代码

前言 前一阵子&#xff0c;使用小乌龟&#xff08;TortoiseGit&#xff09;提交代码的时候&#xff0c;错误的 Revert 了部分代码&#xff0c;本文记录了找回这部分代码的过程。文章标题致敬张银奎老师《格蠹汇编》的第一章 —— 从堆里抢救丢失的博客。说明&#xff1a; 本文的…

[Java基础]线程基础与实现多线程

代码如下&#xff1a; package MyThreadPack;public class MyThread extends Thread {Overridepublic void run() {for (int i 0;i<100;i){System.out.println(i);}} }package MyThreadPack;public class MyThreadDemo01 {public static void main(String[] args){MyThrea…

gpio 树莓派3a+_树莓派4上市:性能飙升起售价依然是35美元

旨在通过超实惠方式&#xff0c;鼓励孩子们投身编程事业的奇趣开发套件树莓派&#xff0c;在昨天迎来了Raspberry Pi 4正式开售的消息。新一代树莓派引入64位处理器、802.11ac双频Wi-Fi以及通过HAT的以太网供电(PoE)等新卖点。树莓派Raspberry Pi 4的处理能力是前一代的三倍、多…

入门级微单反性能对比

入门级相机筛选 先说挑选规则 由于微单体型小于单反&#xff0c;所以在机型选择上微单&#xff1e;单反&#xff1b;最好具备翻转屏和触摸屏&#xff1b;机身防抖不是刚需&#xff0c;但能解决小范围抖动情况下稳定问题&#xff1b;自动对焦很重要&#xff0c;眼控对焦是加分…

知识更新越来越快,但是学习起来越来越困

大家好&#xff0c;我是Z哥&#xff0c;先祝大家节日快乐。不知道这个假期你打算出门吗&#xff1f;Z哥我是打算不出远门了&#xff0c;怕死&#xff0c;哈哈。索性好好宅家里学习&#xff0c;强化一下自己。相信大家也感受到了&#xff0c;随着互联网加速了信息的流动速度&…

什么标准规定了aes加密_Python 爬虫进阶必备 | 关于某租房网站数据加密的分析(送两本 Python 书)...

关于某租房网站数据加密的分析aHR0cHM6Ly93d3cubWFvbWFvenUuY29tLw抓包分析先看看这个网站的首页数据可以看到首页的 html 是压缩的&#xff0c;但是格式化之后没有看到需要的首页数据。过滤 xhr 请求看到一个 index.json的请求可以看到这个请求的请求参数以及返回值都是密文返…

[Java基础]线程同步之卖票案列分析

案列: 卖票。 需求: 某电影院目前正在上映国产大片&#xff0c;共有100张票&#xff0c;而它有3个窗口卖票&#xff0c;请设计一个程序模拟该电影院卖票。 代码如下: package SellTicketPack;public class SellTicket implements Runnable{private int tickets 100;Overrid…

sap 标准委外和工序委外_SAP FICO零基础学习_0035_标准成本估算-主数据-物料主数据...

前辈的第35堂课&#xff1a;谢谢大家的喜欢和关注噢~这里的“前辈”其实指的是给我讲课的前辈啦&#xff0c;我不是前辈噢&#xff0c;我只是一个刚刚接触FICO的小白&#xff0c;跟大家分享前辈给我讲的东西。因为刚接触&#xff0c;学习的内容比较简单~有基础的小伙伴可以去看…

[Java基础]线程安全的类

package ThreadDemoPack01;import java.util.*;public class ThreadDemo01 {public static void main(String[] args){StringBuffer sb new StringBuffer();//线程安全StringBuilder sb2 new StringBuilder();//线程不安全Vector<String> v new Vector<String>(…