文章目录

  • 概述
  • 一、Socket
  • 二、NIO三大组件与事件
  • 三、Reactor模式
  • 四、NIO通信案例
    • 4.1、服务端
    • 4.2、客户端


本文为个人学习笔记整理,仅供交流参考,非专业教学资料,内容请自行甄别

概述

  前篇中提到,BIO是阻塞的IO,阻塞体现在建立连接和通信时,并且线程模型是1:1的。即使使用线程池进行处理,也受限于最大线程数以及cpu上下文的切换。
  NIO则是非阻塞的IO,利用了Reactor反应器模式和多路复用机制。可以实现服务端一个线程应对多个客户端的连接和请求而不阻塞。

一、Socket

  在计算机网络中,TCP、UDP是运输层的协议,应用层需要依靠运输层提供服务。Socket是位于应用层和运输层之间的一个中间软件抽象层,屏蔽了运输层协议的一些实现细节,例如TCP的三次握手,四次挥手,滑动窗口算法等。
  从设计模式的角度来看,Socket属于典型的外观模式,用户无需关心底层的具体实现细节,通过Socket提供的API即可实现网络编程。
  主机 A 的应用程序要能和主机 B 的应用程序通信,必须通过 Socket 建立连接,无论是客户端连接上服务端,还是服务端接收了客户端的连接,都会产生一个Socket 实例:
在这里插入图片描述
创建服务端
在这里插入图片描述
接收客户端的连接
在这里插入图片描述
启动客户端

二、NIO三大组件与事件

  NIO的三大组件:

  • Selector:选择器,客户端和服务端会将自己感兴趣的事件注册到选择器上,在有事件到达时,选择器通知订阅者去执行相应的操作。(Reactor模式),通过一个Selector去管理多个Channel,就称为NIO的多路复用机制
  • Channel:通道,是应用间传递数据的渠道,是双工通信,应用程序可以通过通道读取数据,也可以通过通道向操作系统写数据,而且可以同时进行读写。
  • Buffer:缓冲区,BIO和NIO的区别其一就是,BIO是面向字节流的,而NIO是面向缓冲区的。数据是从通道读入缓冲区,从缓冲区写入到通道中。
    • 在写入时,应用程序将数据写入Buffer,再通过Channel将Buffer中的数据传递出去。
    • 在读取时,先将Channel中的数据读入Buffer,应用程序再从Buffer中读取数据。

  与Selector和Channel相关的,还有一个SelectionKey。SelectionKey是一个抽象类,维护了Selector和Channel的关系。SelectionKey定义了四个事件:
在这里插入图片描述

  • OP_READ:读事件,当缓冲区中有数据可读时,触发该事件,通常需要主动去关注,以接收到缓冲区有数据可读的通知,并进行处理。
  • OP_WRITE:写事件,当缓冲区有空闲时,就会触发该事件。一般情况下是无需关注该事件的,因为缓冲区不会被占满,关注该事件浪费CPU资源。
  • OP_CONNECT:连接事件,当SocketChannel.connect()请求连接成功后就绪,只允许客户端关注。
  • OP_ACCEPT:接收连接事件,当服务器接收到客户端的连接后就需,只允许服务端关注。

  对于服务端而言,通常是ServerSocketChannel创建后注册OP_ACCEPT事件,在接收到客户端的连接,产生SocketChannel后,关注OP_READ事件。
在这里插入图片描述
在这里插入图片描述
  对于客户端而言,通常是SocketChannel创建后,执行连接服务端的操作,因为通过SocketChannelconfigureBlocking方法,将通道设置成了非阻塞,所以调用connect()方法可能立即返回,而不会等待连接建立。连接过程在后台异步进行,线程可以继续执行其他任务,就要根据connect()方法的返回值,执行不同的操作:

  • 如果connect()方法返回true,代表连接建立成功,就关注OP_READ事件。
  • 如果connect()方法返回false,代表连接没有建立完成,继续关注OP_CONNECT事件。

在这里插入图片描述
  后续在进行事件处理时,如果关注了OP_CONNECT事件,就再次判断通道是否建立完成,如果建立完成,就关注OP_READ事件。
在这里插入图片描述

三、Reactor模式

  Reactor模式是NIO的底层实现机制,体现在NIO的Selector选择器上。
在这里插入图片描述
图片来源:图灵学院

  Reactor模式的思想,简单来说,就是用户向选择器进行注册,告诉选择器自己所关心的事件,当对应的事件发生时,再由选择器去通知用户,然后用户执行自己的操作。所以选择器是Reactor模式的核心组件。
  Reactor模式主要由两个核心部分组成:

  1. Reactor:负责监听和分发I/O事件(如连接事件、读写事件)。它运行在一个独立线程中,通过选择器(Selector)实现I/O多路复用,不断轮询注册的事件源。当事件就绪时,Reactor将其分发给对应的事件处理器。
  2. 处理资源池:通常是一个线程池,负责执行事件处理逻辑(如读取数据、执行业务逻辑、发送响应)。这避免了耗时操作阻塞Reactor线程。

  假设一个Web服务器使用Reactor模式:
3. 主线程(Reactor线程)注册SocketChannel的READ事件,并调用selector.select()阻塞。
4. 同时,一个工作线程(来自处理资源池)可以处理之前接收到的HTTP请求(如解析数据、访问缓存)。
5. 当新数据到达时,Reactor线程唤醒,派发READ事件给处理器,然后工作线程接管处理。

  Reactor模式也有三种实现:

  1. 单线程的reactor:连接,读取,发送数据,处理业务,都在一个线程中执行。
  2. 多线程工作reactor:连接,读取,发送数据,由一个线程完成,业务工作由线程池去处理。
  3. 多线程主从reactor:连接由一个线程完成,读取,发送数据,由另一个线程完成,业务工作由线程池去处理。

四、NIO通信案例

4.1、服务端

public class NioServerDemo {public static void main(String[] args) throws IOException {//netty服务端 开启ssc//everSocketChannel:对连接建立的事件感兴趣,是对SeverSocket的包装。ServerSocketChannel ssc = ServerSocketChannel.open();//开启选择器Selector selector = Selector.open();//设置为非阻塞ssc.configureBlocking(false);//绑定端口ssc.socket().bind(new InetSocketAddress(8080));//Selector登记SSC对连接事件感兴趣。//SelectionKey是SSC和SC向Selector注册的相关的事件的代号。标记了不同的事件。读,写,连接,接受连接事件。ssc.register(selector, SelectionKey.OP_ACCEPT);while (true) {//获取当前有哪些事件selector.select(1000);Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();//防止事件重复被处理iterator.remove();//进行处理handle(key, selector);}}}private static void handle(SelectionKey key, Selector selector) throws IOException {if (key.isValid()) {/*处理新加入客户端的请求假设此时有新连接到达Selector*/if (key.isAcceptable()) {//拿到服务端的ssc//Selector将新连接分配给SSC。ServerSocketChannel ssc = (ServerSocketChannel) key.channel();//接受请求,得到sc//SSC接收完连接后,和客户端进行三次握手,产生一个Socket,然后将Socket包装成SCSocketChannel sc = ssc.accept();System.out.println("==========建立连接=========");//同样设置为非阻塞sc.configureBlocking(false);//Selector登记SC对读事件感兴趣//关注读事件sc.register(selector, SelectionKey.OP_READ);}/*处理读事件假设此时客户端发送了数据到Selector,通知SC有数据过来了。*/ else if (key.isReadable()) {//sc:socketchannel 处理读事件//由SC和客户端进行实际的网络读写。SocketChannel sc = (SocketChannel) key.channel();//创建缓冲区//并非直接将数据交付给应用层,而是经过buffer缓冲区。发送数据也是要经过buffer。//buffer本质上是内存中的一块区域,需要读写模式切换ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//从通道读消息,写入缓冲区int bytes = sc.read(byteBuffer);if (bytes > 0) {//切换模式byteBuffer.flip();byte[] bs = new byte[byteBuffer.remaining()];byteBuffer.get(bs);String message = new String(bs, StandardCharsets.UTF_8);System.out.println("服务器收到消息:" + message);//发送应答消息到客户端doWrite(sc, message);} else if (bytes < 0) {/*取消特定的注册关系*/key.cancel();/*关闭通道*/sc.close();}}}}/*发送应答消息*/private static void doWrite(SocketChannel sc, String response) throws IOException {byte[] bytes = response.getBytes();ByteBuffer buffer = ByteBuffer.allocate(bytes.length);//将服务端的响应存入buffer中buffer.put(bytes);//切换模式buffer.flip();//写入客户端(从buffer中读,写入对方 )sc.write(buffer);}}

4.2、客户端

public class NioClientDemo {private static SocketChannel sc;public static void main(String[] args) throws IOException {new Thread(()->{try {client();} catch (IOException e) {throw new RuntimeException(e);}}).start();Scanner scanner = new Scanner(System.in);String next = scanner.next();doWrite(sc,next);}private static void client() throws IOException {//创建选择器Selector selector = Selector.open();//创建客户端通道sc = SocketChannel.open();//设置通道为非阻塞sc.configureBlocking(false);//非阻塞的连接//如果sc.connect返回true,代表连接已经建立完成if (sc.connect(new InetSocketAddress("127.0.0.1", 8080))) {//可以关注读取事件sc.register(selector, SelectionKey.OP_READ);}//否则连接还没有建立完成else {//继续关注建立连接事件sc.register(selector, SelectionKey.OP_CONNECT);}//轮询选择器while (true) {selector.select(1000);Iterator<SelectionKey> it = selector.selectedKeys().iterator();while (it.hasNext()) {SelectionKey sk = it.next();it.remove();if (sk.isValid()) {//获得关心当前事件的channelSocketChannel socketChannel = (SocketChannel) sk.channel();//连接事件if (sk.isConnectable()) {//查看通道连接是否建立完成if (sc.finishConnect()) {//建立完成,注册读事件socketChannel.register(selector,SelectionKey.OP_READ);} else{System.exit(1);}}else if (sk.isReadable()){//创建ByteBuffer,并开辟一个1M的缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);//将channel中的数据读取到buffer中int readBytes = sc.read(buffer);//读取到字节,对字节进行编解码if(readBytes>0){//将缓冲区当前的limit设置为position,position=0,// 用于后续对缓冲区的读取操作buffer.flip();//根据缓冲区可读字节数创建字节数组byte[] bytes = new byte[buffer.remaining()];//将缓冲区可读字节数组复制到新建的数组中buffer.get(bytes);String result = new String(bytes,"UTF-8");System.out.println("客户端收到消息:" + result);}//链路已经关闭,释放资源else if(readBytes<0){sk.cancel();sc.close();}}}}}}private static void doWrite(SocketChannel channel,String request)throws IOException {//将消息编码为字节数组byte[] bytes = request.getBytes();//根据数组容量创建ByteBufferByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);//将字节数组复制到缓冲区writeBuffer.put(bytes);//flip操作writeBuffer.flip();//发送缓冲区的字节数组/*关心事件和读写网络并不冲突*/channel.write(writeBuffer);}
}

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

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

相关文章

Redis4缓存穿透:布隆过滤器与空对象方案

缓存穿透缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;这些请求都会达到数据库。1)方案1&#xff1a;缓存空对象在缓存中存储一个空值每次读取这个空优点&#xff1a;实现简单&#xff0c;维护方便缺点&#xff1a;造成…

域名WHOIS信息查询免费API使用指南

本文介绍由接口盒子提供的免费域名WHOIS查询API服务&#xff0c;帮助开发者快速获取域名的注册信息、到期时间、DNS服务器等关键数据。 一、接口基本信息 ​功能说明​&#xff1a;查询顶级域名的WHOIS信息&#xff08;不支持国别域名/中文域名&#xff09;​请求地址​&#…

【18位数据次方提高数据输出速度】2022-3-9

实在是无法忍受W10输出数据那么慢W7需要2分钟输出数据W10则需要10分钟完成W7需要3分钟W10则需要15分钟完成输出数据&#xff0c;虽然W10运算速度比W7快很多但是加上输出速度总体完成时间居然差不多&#xff01;随着使用数组超过百万W7数据输出时间也变长&#xff0c;随着数组数…

云原生技术与应用-Kubernetes架构原理与集群环境部署

目录 一.为什么需要kubernetes 1.对于开发人员 2.对于运维人员 二.kubernetes带来的挑战 三.kubernetes架构解析 1.master节点的组件 2.node节点包含的组件 3.kubernetes网络插件 四.kubernetes快速安装kubernetes集群 1.部署docker环境 2.部署kubernetes集群 五.Metrics-…

百度权重提升技巧分析:从底层逻辑到实战策略

在搜索引擎优化&#xff08;SEO&#xff09;领域&#xff0c;百度权重始终是网站运营者关注的核心指标之一。它不仅反映了网站在百度搜索中的综合表现&#xff0c;更直接影响着流量获取能力与商业价值。然而&#xff0c;百度权重并非百度官方直接公布的数据&#xff0c;而是第三…

模拟数据生成---使用NGS数据模拟软件VarBen

目录 1.在BAM文件中根据指定的变异等位基因分数的指定位置或区域随机选择read。 2.筛选变异等位基因分数的reads: 3.装BWA和samtools软件包(samtools在linux系统中下载过,前文有讲过) 4.写py脚本 5.下载pysam库模块 6.下载参考基因组hg38 7.解压gz 8.建立samtools索引…

Redis-典型应用-分布式锁

目录 1.什么是分布式锁? 2.分布式锁的实现 3.引入过期时间 4.引入校验ID 5.引入lua脚本: 6.引入看门狗(watch dog) 7.引入redislock算法: 1.什么是分布式锁? 在 分布式系统中,会出现多个节点同时访问同一个公共资源, 此时就需要通过锁来作互斥控制,避免出现类似于多线程…

Dinky (Mac) 本地开发环境搭建指南

目录 一、前置条件 二、代码准备 三、前端环境搭建 1. 安装Node环境 2. 安装PNPM 3. 构建前端 四、后端环境搭建 1. 本地编译依赖 2. 添加必要依赖 3. 启动后端服务 五、访问系统 附录&#xff1a;官方参考 一、前置条件 确保已安装以下软件&#xff1a; 软件要求…

Java Set 集合详解:从基础语法到实战应用,彻底掌握去重与唯一性集合

作为一名 Java 开发工程师&#xff0c;你一定在实际开发中遇到过需要去重、唯一性校验、快速查找等场景。这时候&#xff0c;Set 集合 就成为你不可或缺的工具。本文将带你全面掌握&#xff1a;Set 接口的定义与核心方法常见实现类&#xff08;如 HashSet、TreeSet、LinkedHash…

在分布式系统中,如何保证缓存与数据库的数据一致性?

口诀&#xff1a; 读多写少用旁路&#xff0c;先更库再删缓存&#xff1b; 强一致选写透&#xff0c;缓存代理更库走&#xff1b; 性能优先用写回&#xff0c;异步批量有风险&#xff1b; 高并发加双删&#xff0c;延迟兜底防旧残&#xff1b; 强一致用锁串&#xff0c;并发虽低…

【洛谷P1417】烹调方案 题解

题目大意 一共有 nnn 件食材&#xff0c;每件食材有三个属性&#xff0c;aia_iai​&#xff0c;bib_ibi​ 和 cic_ici​&#xff0c;如果在 ttt 时刻完成第 iii 样食材则得到 ai−tbia_i-t\times b_iai​−tbi​ 的美味指数&#xff0c;用第 iii 件食材做饭要花去 cic_ici​ 的…

vue svg实现一个环形进度条组件

svg实现一个环形进度条设计初衷&#xff1a;本来想直接使用element的进度条组件的&#xff0c;但是好多属性都没有办法控制。 UI设计的图如下&#xff0c;需要控制未完成和已完成的颜色&#xff0c;端点的形状改为普通的butt 所以使用svg实现了一个环形进度条组件element组件设…

02 51单片机之LED闪烁

文章目录1、单片机1-1、简介1-2、应用场景2、51单片机2-1、背景2-2、主要品牌及其产品2-3、基本组成2-4、命名规则3、单片机内部结构3-1、单片机内部结构图3-2、单片机内部结构3-3、单片机内部管脚图3-4、单片机最小系统3-5、开发板介绍4、点亮LED4-1、新建工程4-1-1、创建工程…

Typecho博客集成算术验证码防御垃圾评论实战指南

文章目录 Typecho实现算术验证码防御机器人垃圾评论的完整方案 背景与问题分析 技术方案设计 系统架构 技术选型 核心实现步骤 1. 创建验证码生成函数 2. 修改评论表单模板 3. 添加AJAX刷新功能 4. 创建验证码刷新接口 5. 添加评论提交验证 安全增强措施 1. 防止暴力破解 2. 增…

clonezilla 导出自动化恢复iso

clonezilla 下载及U盘工具下载 clonezilla rufus U盘写入工具ventoy U盘工具downloaddownloaddownload clonezilla 备份&#xff0c;连贯上一篇文章参考 Choose Clonezilla live (VGA 800x600) Wait for it to complete Language selection Keyboard Settings Select Mode …

深度学习模型开发部署全流程:以YOLOv11目标检测任务为例

深度学习模型开发部署全流程&#xff1a;以YOLOv11目标检测任务为例 深度学习模型从开发到部署的完整流程包含需求分析、数据准备、模型训练、模型优化、模型测试和部署运行六大核心环节。YOLOv11作为新一代目标检测模型&#xff0c;不仅延续了YOLO系列的高效实时性能&#xff…

单片机(STM32-串口通信)

一、串口通信基础概念串口通信&#xff08;Serial Communication&#xff09;是一种在计算机和外部设备之间进行数据传输的通信方式。它通过串行方式逐位传输数据&#xff0c;是最基本和常用的通信接口之一。主要特点1. 串行传输(1)数据按位顺序传输&#xff0c;一次只能传输一…

Redis学习其三(订阅发布,主从复制,哨兵模式)

文章目录9.Redis订阅与发布9.1发布订阅命令9.2示例10.Redis主从复制10.1概念10.2环境配置10.3集群搭建&#xff08;一主二从配置&#xff09;10.4使用规则&原理11.哨兵模式11.1基本概念11.2工作原理11.3使用案例12.缓存穿透,雪崩&#xff08;待拓展&#xff09;12.1缓存穿透…

跨平台 App 如何无痛迁移到鸿蒙系统?全流程实战+Demo 教程

摘要 目前&#xff0c;随着 HarmonyOS&#xff08;鸿蒙系统&#xff09;的快速发展&#xff0c;越来越多开发者和企业希望将已有的 Android、Flutter、React Native 等跨平台应用迁移到鸿蒙生态中。鸿蒙不仅具备分布式能力、原生性能和统一的开发范式&#xff0c;还提供了丰富的…

智慧后厨检测算法构建智能厨房防护网

智慧后厨检测&#xff1a;构建安全洁净厨房的智能解决方案背景&#xff1a;传统后厨管理的痛点与智慧化需求餐饮行业后厨管理长期面临操作规范难落实、安全隐患难察觉、卫生状况难追溯等痛点。传统人工巡检效率低、覆盖面有限&#xff0c;难以实现24小时无死角监管。例如&#…