Java NIO Selector选择器简介

文章目录

  • 前言
  • 一、IO多路复用
  • 二、Selector如何确保多个通道的操作协调一致
  • 三、NIO中怎样实现通道的非阻塞IO操作
  • 四、网络服务器和客户端简单代码示例
      • 服务器端代码
      • 客户端端代码


前言

Selector是Java NIO(New I/O)中的核心组件之一,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写、可连接或可接收等。通过Selector,可以实现单线程管理多个Channel对应的网络连接,从而避免多线程的线程上下文切换带来的额外开销。

Selector与Channel之间的关系是通过注册的方式完成的。只有SelectableChannel才能被Selector管理,例如所有的Socket通道。当一个Channel注册到Selector上并且处于某种就绪状态时,它就可以被Selector查询到。此时,Selector会生成一个SelectionKey,这个Key代表了注册到Selector的Channel。通过这个Key,我们可以知道哪些Channel已经就绪,然后进行相应的读写操作。

使用Selector可以极大地提高服务器的吞吐能力,因为它允许一个线程同时监控多个IO流(Socket)的状态,从而能够同时管理多个客户端连接。这在处理大量并发连接时非常有用,可以有效降低系统资源消耗并提高响应速度。

总的来说,Selector是Java NIO中实现IO多路复用模式的关键组件,它使得单线程能够高效地管理多个网络连接,从而提高了服务器的性能和可扩展性。


一、IO多路复用

Selector在Java NIO中通过一种称为“IO多路复用”的技术来实现单线程管理多个网络连接。这种技术允许单个线程同时监视多个Channel的状态,并根据它们的就绪情况(如可读、可写、连接等)来执行相应的操作。以下是Selector实现单线程管理多个网络连接的主要步骤:

  1. 创建Selector:首先,需要创建一个Selector对象。这个对象将用于后续注册Channel和检查它们的状态。

  2. 注册Channel到Selector:然后,将需要监控的Channel(如ServerSocketChannel或SocketChannel)注册到Selector上,并指定感兴趣的操作集(OP_READ、OP_WRITE等)。每个注册的Channel都会返回一个SelectionKey,这个Key是Channel和Selector之间关联的标识。

  3. 选择就绪的Channel:通过调用Selector的select()方法,线程将阻塞,等待至少一个Channel就绪。当某个Channel的状态发生变化(例如,有数据可读或可写),或者达到了超时时间(如果设置了超时),select()方法将返回。此时,可以通过selectedKeys()方法获取一个包含所有就绪的SelectionKey的集合。

  4. 处理就绪的Channel:遍历selectedKeys()返回的集合,对于每个就绪的SelectionKey,可以通过它获取对应的Channel,并执行相应的读写操作。例如,如果某个Channel的状态是OP_READ,那么就可以从该Channel读取数据。

  5. 更新Channel状态并继续监听:处理完每个就绪的Channel后,需要将其对应的SelectionKey从selectedKeys()集合中移除,以避免重复处理。然后,可以继续调用select()方法,等待新的Channel就绪。

通过这种方式,Selector允许单个线程高效地管理多个网络连接。线程不再需要为每个连接创建一个单独的线程(像传统BIO模型中那样),而是可以轮询多个连接的状态,并在它们就绪时进行处理。这大大减少了线程切换的开销,提高了系统的吞吐量和响应速度。

需要注意的是,虽然Selector能够高效地管理大量连接,但实际的IO操作(如读写数据)仍然需要在单独的线程中执行,以避免阻塞Selector线程。因此,在实际应用中,通常会结合线程池等技术来进一步优化性能。

二、Selector如何确保多个通道的操作协调一致

Selector在Java NIO中通过其独特的机制来确保多个通道(Channel)的操作能够协调一致。这主要依赖于Selector的注册、选择和处理三个核心步骤。

  1. 注册步骤

    • 在注册步骤中,通道(Channel)会被注册到Selector上,并指定它们感兴趣的事件类型(如读、写、连接等)。注册成功后,Selector会维护一个内部的数据结构,用来跟踪每个通道的状态和它们感兴趣的事件。
  2. 选择步骤

    • 选择步骤是Selector的核心功能。当调用Selector的select()方法时,Selector会阻塞等待,直到至少有一个注册的通道变为就绪状态(即发生了感兴趣的事件)。这个过程中,Selector会不断监控所有注册的通道,确保一旦有通道就绪,能够立即响应。
  3. 处理步骤

    • 一旦select()方法返回,表示有通道就绪,Selector会提供一个包含所有就绪通道的SelectionKey集合。开发者可以遍历这个集合,对每个就绪的通道进行相应的处理。这里的关键是,Selector确保了只有当通道真正就绪时,才会将其包含在返回的集合中,从而避免了不必要的检查和操作。

为了确保多个通道的操作协调一致,Selector还提供了以下几个关键机制:

  • 非阻塞IO:Java NIO使用非阻塞IO模型,这意味着通道(Channel)的读写操作不会阻塞线程。即使某个通道当前没有数据可读或可写,Selector也不会因此而被阻塞。这使得Selector能够同时监控多个通道,而不会因为某个通道的阻塞而影响其他通道的处理。

  • 事件驱动:Selector是基于事件驱动的。只有当通道的状态发生变化(即发生了感兴趣的事件)时,Selector才会返回并通知开发者。这种机制确保了开发者只会在需要时处理通道,从而提高了效率和响应速度。

  • 选择键(SelectionKey):每个注册到Selector的通道都会得到一个唯一的选择键(SelectionKey)。这个键不仅代表了通道和Selector之间的关联,还包含了通道的状态信息和感兴趣的事件类型。通过选择键,开发者可以方便地获取通道的信息并进行相应的操作。

综上所述,Selector通过其独特的注册、选择和处理机制,以及非阻塞IO和事件驱动的特性,确保了多个通道的操作能够协调一致。这使得开发者能够使用单个线程高效地管理大量网络连接,提高了系统的性能和可扩展性。

三、NIO中怎样实现通道的非阻塞IO操作

在Java NIO中,实现通道(Channel)的非阻塞IO操作主要涉及到通道的配置和选择器的使用。下面是一些关键的步骤和概念:

  1. 创建非阻塞通道
    要创建非阻塞通道,你需要在打开通道后将其配置为非阻塞模式。这可以通过调用通道的configureBlocking(false)方法来实现。例如,对于SocketChannel,你可以这样做:

    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.configureBlocking(false); // 设置为非阻塞模式
    

    一旦通道被设置为非阻塞模式,任何对该通道的IO操作(如read()write())都会立即返回,而不会等待操作完成。如果操作不能立即完成,这些方法将返回0(对于读取操作)或抛出IOException(对于写入操作)。

  2. 使用Selector
    非阻塞IO的关键在于使用Selector来检查通道的就绪状态。Selector允许你注册一个或多个通道,并查询哪些通道已经准备好进行读或写操作。

    首先,你需要创建一个Selector实例:

    Selector selector = Selector.open();
    

    然后,将通道注册到Selector上,并指定感兴趣的事件类型(如SelectionKey.OP_READSelectionKey.OP_WRITE):

    socketChannel.register(selector, SelectionKey.OP_READ);
    

    注册完成后,你可以调用selector.select()方法来等待通道就绪。这个方法会阻塞,直到至少有一个通道的就绪状态发生改变,或者超时。

  3. 处理就绪的通道
    selector.select()方法返回时,你可以通过调用selector.selectedKeys()来获取一个包含所有就绪通道的SelectionKey集合。然后,你可以遍历这个集合,并对每个就绪的通道执行相应的操作。

    while (selector.select() > 0) { // 等待至少一个通道就绪Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();if (key.isAcceptable()) {// 新的连接已接受,处理它} else if (key.isConnectable()) {// 连接已建立,处理它} else if (key.isReadable()) {// 通道已准备好读取,处理它} else if (key.isWritable()) {// 通道已准备好写入,处理它}keyIterator.remove(); // 从集合中移除已处理的键}
    }
    

    注意,在每次迭代时,你都需要从selectedKeys集合中移除已处理的SelectionKey,以避免重复处理。

  4. 执行非阻塞IO操作
    对于就绪的通道,你可以执行非阻塞的IO操作。由于通道已经配置为非阻塞模式,这些操作会立即返回,而不会阻塞线程。你需要根据通道的就绪状态(读或写)来执行相应的操作。

    if (key.isReadable()) {ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = socketChannel.read(buffer); // 非阻塞读取if (bytesRead == -1) {// 连接已关闭,处理它} else {// 处理读取到的数据}
    }
    

通过结合非阻塞通道和Selector的使用,Java NIO能够实现高效的单线程或多线程网络IO处理,从而大大提高服务器的吞吐量和响应能力。

四、网络服务器和客户端简单代码示例

以下是一个简单的Java NIO网络服务器和客户端的示例代码。

服务器端代码

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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;public class NioServerSocket {public static void main(String[] args) throws IOException {// 打开 ServerSocketChannelServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 设置为非阻塞模式serverSocketChannel.configureBlocking(false);// 绑定端口serverSocketChannel.bind(new InetSocketAddress(8080));// 打开 SelectorSelector selector = Selector.open();// 注册 Channel 到 Selector,并指定监听 ACCEPT 事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);while (true) {// 等待至少一个 Channel 变为 readyint readyChannels = selector.select();if (readyChannels == 0) continue;Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();if (key.isAcceptable()) {// 客户端连接请求ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel client = server.accept();// 设置为非阻塞模式client.configureBlocking(false);// 注册客户端 Channel 到 Selector,并指定监听 READ 事件client.register(selector, SelectionKey.OP_READ);System.out.println("Accepted connection from " + client);} else if (key.isReadable()) {// 客户端数据可读SocketChannel client = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = client.read(buffer);if (bytesRead == -1) {// 客户端断开连接client.close();} else {// 处理数据buffer.flip();while (buffer.hasRemaining()) {System.out.print((char) buffer.get());}}}// 从 selectedKeys 集合中移除已处理的 SelectionKeykeyIterator.remove();}}}
}

客户端端代码

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;  public class Client {  public static void main(String[] args) throws IOException {  // 打开 SocketChannel,并设置为非阻塞模式  SocketChannel socketChannel = SocketChannel.open();  socketChannel.configureBlocking(false);  // 打开 Selector  Selector selector = Selector.open();  // 尝试连接到服务器,但不等待连接完成  socketChannel.connect(new InetSocketAddress("localhost", 8000));  // 注册 SocketChannel 到 Selector,监听 CONNECT 事件  socketChannel.register(selector, SelectionKey.OP_CONNECT);  // 等待连接建立  while (!socketChannel.finishConnect()) {  // 如果连接尚未建立,则等待至少一个通道就绪  selector.select();  // 获取就绪的 SelectionKey 集合  Set<SelectionKey> selectedKeys = selector.selectedKeys();  Iterator<SelectionKey> keyIterator = selectedKeys.iterator();  while (keyIterator.hasNext()) {  SelectionKey key = keyIterator.next();  if (key.isConnectable()) {  // 处理连接事件  SocketChannel client = (SocketChannel) key.channel();  // 如果连接建立失败,处理异常  if (!client.finishConnect()) {  System.err.println("Failed to connect to server");  client.close();  return;  }  // 连接建立成功,开始发送数据  System.out.println("Connected to server");  // 发送数据到服务器  String message = "Hello, Server!";  ByteBuffer buffer = ByteBuffer.allocate(48);  buffer.clear();  buffer.put(message.getBytes());  buffer.flip();  while (buffer.hasRemaining()) {  client.write(buffer);  }  // 如果不需要进一步通信,关闭 SocketChannel  client.close();  }  // 从已选择的键集合中移除当前的键  keyIterator.remove();  }  }  // 关闭 SocketChannel 和 Selector  socketChannel.close();  selector.close();  }  
}

在这个示例中,服务器端代码创建了一个非阻塞的 ServerSocketChannel 并绑定到指定的端口。然后,它注册 ServerSocketChannelSelector 上,并监听 ACCEPT 事件。一旦客户端连接,服务器接受连接,并将新创建的 SocketChannel 注册到 Selector 上,监听 READ 事件。

客户端代码则创建了一个非阻塞的 SocketChannel,并尝试连接到服务器。一旦连接建立,客户端就会立即发送数据并关闭连接。

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

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

相关文章

云原生架构(微服务、容器云、DevOps、不可变基础设施、声明式API、Serverless、Service Mesh)

前言 读完本文&#xff0c;你将对云原生下的核心概念微服务、容器云、DevOps、Immutable Infrastructure、Declarative-API、Serverless、Service Mesh 等有一个相对详细的了解&#xff0c;帮助你快速掌握云原生的核心和要点。 因题主资源有限, 这里会选用部分云服务商的组件进…

LeetCode:1026. 节点与其祖先之间的最大差值(DFS Java)

目录 1026. 节点与其祖先之间的最大差值 题目描述&#xff1a; 实现代码与解析&#xff1a; DFS 原理思路&#xff1a; 1026. 节点与其祖先之间的最大差值 题目描述&#xff1a; 给定二叉树的根节点 root&#xff0c;找出存在于 不同 节点 A 和 B 之间的最大值 V&#xff…

【面经】3月29日 美团/美团平台/后端/一面/1h

面试官先介绍自己部门的业务&#xff1a;存储中心&#xff0c;涉及到大量数据的离线处理&#xff08;亿级别&#xff09;。 手撕&#xff08;删除链表倒数第k个节点&#xff09; 自我介绍 项目介绍&#xff08;还没说完被打断了&#xff0c;面试官说你这个感觉就是把功能说了一…

11-1(2)-CSS 背景+CSS 精灵图

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 一、CSS 背景1 背景颜色2 背景色半透明3 背景图片4 背景平铺5 背景图片位置6 …

【数据结构与算法】(一)数据结构相关的基本概念

文章目录 【数据结构与算法】&#xff08;一&#xff09;数据结构相关的基本概念前言1.1 数据结构的研究内容1.2 基本概念和术语1.2.1 数据、数据元素、数据项和数据对象1.2.2 数据结构1.2.3 数据类型和抽象数据类型 1.3 抽象数据类型的表示与实现1.4 算法和算法分析1.4.1 算法…

中国电子学会(CEIT)2021年09月真题C语言软件编程等级考试三级(含详细解析答案)

中国电子学会(CEIT)考评中心历届真题(含解析答案) C语言软件编程等级考试三级 2021年09月 编程题五道 总分:100分一、菲波那契数列(20分) 菲波那契数列是指这样的数列:数列的第一个和第二个数都为1,接下来每个数都等于前面2个数之和。给出一个正整数a,要求菲波…

深度剖析:网络安全中的红蓝对抗策略

红蓝对抗 红蓝对抗服务方案 在蓝队服务中&#xff0c;作为攻击方将开展对目标资产的模拟入侵&#xff0c;寻找攻击路径&#xff0c;发现安全漏洞和隐患。除获取目标系统的关键信息&#xff08;包括但不限于资产信息、重要业务数据、代码或管理员账号等&#xff09;外&#x…

9(10)-3-CSS 定位

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 CSS 定位1 定位组成2 定位模式3 边偏移4 静态定位 static5 相对定位 relative…

如何在iPhone上恢复永久删除的照片?

2007 年&#xff0c;Apple Inc. 推出了这款震撼人心的智能手机&#xff0c;后来被称为 iPhone。您会惊讶地发现&#xff0c;迄今为止&#xff0c;Apple Inc. 已售罄 7 亿台 iPhone 设备。根据 2023 年 8 月的一项调查数据&#xff0c;95% 的智能手机利润都落入了苹果公司的口袋…

ubuntu同步网络时间

安装ntpdate sudo apt-get update sudo apt-get install ntpdate设置系统时间与网络时间同步 sudo ntpdate cn.pool.ntp.org设置时区亚洲上海 sudo cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime设置时间为24小时制 echo "LC_TIMEen_DK.UTF-8" >>/…

Django--方法

path() 方法 一个路由配置模块就是一个urlpatterns列表&#xff0c;列表的每个元素都是一项path&#xff0c;每一项path都是以path()的形式存在。 path()方法可以接收4个参数&#xff0c;其中前2个是必须的&#xff1a;route和view&#xff0c;以及2个可选的参数&#xff1a;k…

跨平台的组播测试工具mping、udp_sender及udp_reciver的源码及使用教程

文章目录 1.前言2.mping工具编译3.mping工具使用3.1 参数说明3.1 组播播发&#xff08;-s&#xff09;3.1 组播播发&#xff08;-r&#xff09;3.3 Linux下mping测试 4.Linux组播udp_sender及udp_reciver使用4.1 udp_sender源码4.1 udp_reciver源码4.3 编译方法4.4 测试使用4.4…

android11 SystemUI入門之KeyguardPatternView解析

view层级树为&#xff1a; 被包含在 keyguard_host_view.xml中 。 <?xml version"1.0" encoding"utf-8"?> <!-- This is the host view that generally contains two sub views: the widget viewand the security view. --> <com.andro…

并发编程01-深入理解Java并发/线程等待/通知机制

为什么我们要学习并发编程&#xff1f; 最直白的原因&#xff0c;因为面试需要&#xff0c;我们来看看美团和阿里对 Java 岗位的 JD&#xff1a; 从上面两大互联网公司的招聘需求可以看到&#xff0c; 大厂的 Java 岗的并发编程能力属于标配。 而在非大厂的公司&#xff0c; 并…

16_USART串口发送和接收数据

USART串口发送和接收数据 USART串口发送和接收数据 USART串口发送和接收数据 主函数 #include "stm32f10x.h" // Device header #include "Delay.h" #include "LED.h" #include "Key.h" #include "stdint.h&q…

Linux mattrib命令教程:如何管理MS-DOS文件的属性(附实例详解和注意事项)

Linux mattrib命令介绍 mattrib 是一个用于更改MS-DOS文件属性标志的命令。它可以添加属性标志到一个MS-DOS文件&#xff08;使用 操作符&#xff09;或者移除属性标志&#xff08;使用 - 操作符&#xff09;。 Linux mattrib命令适用的Linux版本 mattrib 命令在大多数Linu…

【算法】寻找数组中心下标

题目 给定一个非空数组&#xff0c;找到一个元素&#xff0c;该元素左侧元素和等于其右侧元素和&#xff0c;返回该元素下标。 如果没有则返回-1&#xff0c;有多个则返回最左侧一个。 原理 1、双指针 定义两个变量&#xff0c;一个为从左侧累加的和 leftSum 0&#xff0c…

前端分页和后端分页

分页一般是前端分页还是后端分页 分页可以在前端或后端进行&#xff0c;具体取决于项目的需求和实现方式。以下是前端分页和后端分页的一些特点和适用场景&#xff1a; 前端分页&#xff1a; 特点&#xff1a;前端分页是指在前端&#xff08;浏览器端&#xff09;对数据进行分…

Ubuntu22.04中基于Qt开发Android App

文章目录 前言在Ubuntu22.04中配置开发环境案例测试参考 前言 使用Qt开发手机应用程序是一种高效且灵活的选择。Qt作为一个跨平台的开发框架&#xff0c;为开发者提供了统一的开发体验和丰富的功能库。首先&#xff0c;Qt的跨平台性让开发者可以使用相同的代码库在不同的操作系…

Error: TF_DENORMALIZED_QUATERNION: Ignoring transform forchild_frame_id

问题 运行程序出现&#xff1a; Error: TF_DENORMALIZED_QUATERNION: Ignoring transform for child_frame_id “odom” from authority “unknown_publisher” because of an invalid quaternion in the transform (0.0 0.0 0.0 0.707) 主要是四元数没有归一化 Eigen::Quatern…