Java NIO使用案例和说明

Java NIO(New Input/Output)和传统的 Java Socket 编程提供了不同的方法来处理网络通信。Java NIO 引入了非阻塞 I/O 和多路复用的概念,这使得它在处理大量并发连接时比传统的阻塞式 Socket 更加高效。

传统 Java Socket 编程有几个特点:

  • 阻塞式 I/O:传统的 Socket 类是阻塞式的,意味着当你调用 accept(), read(), 或 write() 方法时,线程会一直等待直到操作完成。
  • 一对一模型:每个连接都需要一个独立的线程来处理客户端请求,这在面对大量并发连接时可能导致资源耗尽。
  • 简单易用:API 相对简单,适合初学者或小型应用。

Java NIO是 JDK1.4开始提供的一套新的 I/O API,旨在提供更高效的非阻塞 I/O 操作和更灵活的缓冲区管理。NIO 与传统的 Java I/O API 相比,提供了更好的性能和可扩展性,特别是在处理大量并发连接时。对于需要处理大量并发连接的应用程序,Java NIO 明显优于传统的 Socket 编程。由于它可以使用单个线程管理多个连接,因此减少了线程切换带来的开销。以下是关于 Java NIO 的详细介绍。

Java NIO 的主要特性

  1. 缓冲区(Buffer)
    • 概念:Buffer 是一个容器对象,用于存储基本数据类型的值。它提供了一种机制来操作字节、字符等数据。
    • 类型:常见的 Buffer 类型包括 ByteBuffer, CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer, 和 DoubleBuffer
    • 方法:Buffer 提供了诸如 put(), get(), flip(), clear() 等方法来操作数据。
  2. 通道(Channel)
    • 概念:Channel 是一个能够进行 I/O 操作的对象。与传统的流不同,Channel 可以同时进行读写操作,并且可以是非阻塞的。
    • 类型:常见的 Channel 类型包括 FileChannel, SocketChannel, ServerSocketChannel, 和 DatagramChannel
    • 操作:Channel 可以通过 read()write() 方法与 Buffer 进行数据交换。
  3. 选择器(Selector)
    • 概念:Selector 允许单个线程管理多个 Channel,从而实现非阻塞 I/O。这对于处理大量并发连接非常有用。
    • 注册:Channel 需要注册到 Selector 上,并指定感兴趣的事件类型(如读、写)。
    • 选择:通过 select() 方法,Selector 会等待直到至少有一个 Channel 准备好进行 I/O 操作。

下面是一个完整的 Java NIO 客户端和服务端示例。我们将创建一个简单的回显服务器(Echo Server),它接收客户端发送的消息并将其回显给客户端。这个例子将展示如何使用 Selector 来管理多个客户端连接,以及如何通过非阻塞 I/O 进行通信。

服务端代码

package com.wuxiaolong.socket.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.nio.charset.StandardCharsets;
import java.util.Iterator;public class EchoServer {private static final int PORT = 8080; // 服务器监听的端口号private Selector selector; // 选择器用于管理多个通道/*** 构造函数:初始化选择器并设置服务器通道。*/public EchoServer() throws IOException {// 打开选择器,用于多路复用 I/O 操作selector = Selector.open();// 打开服务器通道,并绑定到指定端口ServerSocketChannel serverChannel = ServerSocketChannel.open();serverChannel.socket().bind(new InetSocketAddress(PORT));serverChannel.configureBlocking(false); // 设置为非阻塞模式// 注册选择器,监听连接事件(OP_ACCEPT)serverChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服务器已启动,监听端口 " + PORT);}/*** 启动服务器并进入主循环,处理客户端连接和消息。*/public void start() throws IOException {while (true) {// 选择已就绪的键(有新连接或可读取的数据)  如果没有事件响应  这里会阻塞selector.select();// 获取已就绪的键集合,并迭代处理每个键Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();while (selectedKeys.hasNext()) {SelectionKey key = selectedKeys.next();selectedKeys.remove(); // 从集合中移除已处理的键if (!key.isValid()) { // 如果键无效,跳过continue;}try {if (key.isAcceptable()) {// 处理新的客户端连接ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();SocketChannel clientChannel = serverChannel.accept();if (clientChannel != null) {clientChannel.configureBlocking(false); // 设置为非阻塞模式clientChannel.register(selector, SelectionKey.OP_READ); // 注册读取事件System.out.println("新客户端连接: " + clientChannel.getRemoteAddress());}} else if (key.isReadable()) {// 读取客户端消息SocketChannel clientChannel = (SocketChannel) key.channel();
//                        ByteBuffer readBuffer = ByteBuffer.allocate(256); // 堆上创建缓冲区ByteBuffer readBuffer = ByteBuffer.allocateDirect(256); // 直接内存上创建缓冲区int readBytes = clientChannel.read(readBuffer); // 从通道读取数据到缓冲区if (readBytes == -1) {// 客户端关闭连接clientChannel.close();System.out.println("客户端断开连接");} else if (readBytes > 0) {// 准备回显数据readBuffer.flip(); // 切换缓冲区为读取模式byte[] messageBytes = new byte[readBuffer.remaining()];readBuffer.get(messageBytes);String message = new String(messageBytes, StandardCharsets.UTF_8);System.out.println("收到消息: " + message);// 创建一个新的缓冲区来存储回显数据ByteBuffer writerBuffer = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8));// 回显消息给客户端while (writerBuffer.hasRemaining()) {clientChannel.write(writerBuffer); // 将缓冲区中的数据写回客户端}writerBuffer.clear(); // 清空原始缓冲区以备下次使用}}} catch (IOException e) {// 捕获并处理 I/O 异常,确保通道关闭key.cancel();if (key.channel() != null) {try {key.channel().close();} catch (IOException ex) {ex.printStackTrace();}}}}}}/*** 主方法:创建并启动服务器。*/public static void main(String[] args) throws IOException {new EchoServer().start();}
}

客户端代码

package com.wuxiaolong.socket.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;public class EchoClient {private static final String SERVER_HOST = "localhost"; // 服务器主机地址private static final int SERVER_PORT = 8080; // 服务器监听的端口号private SocketChannel socketChannel; // 客户端通道/*** 构造函数:初始化客户端通道并连接到服务器。*/public EchoClient() throws IOException {// 打开客户端通道并尝试连接到服务器socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress(SERVER_HOST, SERVER_PORT));socketChannel.configureBlocking(false); // 设置为非阻塞模式System.out.println("已连接到服务器 " + SERVER_HOST + ":" + SERVER_PORT);}/*** 发送消息给服务器并接收回显消息。** @param message 要发送的消息字符串*/public void sendMessage(String message) throws IOException {// 发送消息到服务器ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8));socketChannel.write(writeBuffer);// 等待服务器回显writeBuffer.clear(); // 清空缓冲区// 不断读取直到没有更多数据while (true){ByteBuffer readBuffer = ByteBuffer.allocate(256);int bytesRead = socketChannel.read(readBuffer);if (bytesRead > 0) {readBuffer.flip(); // 切换缓冲区为读取模式byte[] responseBytes = new byte[readBuffer.remaining()];readBuffer.get(responseBytes);String resp = new String(responseBytes, StandardCharsets.UTF_8);readBuffer.clear(); // 清空缓冲区以备下次读取System.out.println("服务器回显: " + resp);break;}}}/*** 关闭客户端通道。*/public void close() throws IOException {socketChannel.close();System.out.println("客户端已断开连接");}/*** 主方法:创建客户端并允许用户通过命令行输入消息。*/public static void main(String[] args) {try{EchoClient client = new EchoClient();Scanner scanner = new Scanner(System.in);System.out.println("请输入要发送的消息(输入 'exit' 退出):");while (true) {System.out.print("您: ");String message = scanner.nextLine();if ("exit".equalsIgnoreCase(message)) {break;}client.sendMessage(message);}scanner.close();} catch (IOException e) {e.printStackTrace();}}
}

性能优势

  • 非阻塞 I/O:NIO 支持非阻塞模式,允许单个线程管理多个 I/O 操作,从而提高了并发性能。
  • 零拷贝技术:某些情况下,NIO 可以减少数据在用户空间和内核空间之间的拷贝次数,例如使用 sendfile() 或内存映射文件。
  • 直接缓冲区ByteBuffer.allocateDirect() 创建的直接缓冲区位于堆外内存中,减少了垃圾回收的压力。

实际应用

NIO 在需要高性能网络通信的应用场景中非常有用,比如:

  • Web 服务器:处理大量并发 HTTP 请求。
  • 数据库驱动:高效地与数据库进行交互。
  • 实时系统:要求低延迟和高吞吐量的数据传输。

总结

Java NIO 提供了一套强大而灵活的 API,使得开发者可以构建高效、可扩展的 I/O 应用程序。理解 NIO 的核心概念和机制,可以帮助在实际项目中更好地利用这些特性,提升应用程序的性能和可靠性。

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

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

相关文章

人工智能基础软件-Jupyter Notebook

简介&#xff1a; Jupyter Notebook是基于网页的用于交互计算的应用程序。其可被应用于全过程计算&#xff1a;开发、文档编写、运行代码和展示结果。 Jupyter Notebook是以网页的形式打开&#xff0c;可以在网页页面中直接编写代码和运行代码&#xff0c;代码的运行结果也会直…

tesla openday数据驱动串讲

一、我写的目的 tesla的数据驱动全流程代表着现在&#xff08;曾经&#xff09;的sota&#xff0c;总结和沉淀他的方法总结后与自己现在的理念做一次对标&#xff0c;查漏补缺找到自己现在的主要问题&#xff0c;聚焦下一阶段的投入 二、主要方法 本文不讲解tesla的视觉技术…

基于神经网络的车牌识别算法matlab仿真 人工智能方法 车牌识别

一 设计方法 设定matlab的车牌识别系统&#xff0c;用神经网络进行预测&#xff0c;将数据集划分为训练集和测试集&#xff0c;设计神经网络结构。根据输入特征的维度和输出标签的维度&#xff0c;确定网络层数和节点数。使用训练集对神经网络进行训练。通过迭代优化网络权重和…

计算机体系结构期末复习4:多处理器缓存一致性(cache一致性)

目录 一、cache一致性问题 1.一致性定义 2.问题定义 3.解决问题的基本策略 二、写返回(write-back)cache的一致性处理 1.MSI协议 2.MESI协议 3.MOESI协议 三、补充知识点&#xff1a;提升cache性能的因素 1.cache miss的三种情况&#xff1a; 2.影响cache性能的因素…

信息化时代的步伐

信息化时代的步伐 下载压缩包的&#xff0c;解压压缩包得到 这里给了一串数字 我们不知道要用什么解码就用随波逐流解码 一键解码得到 说明这是用中文电报解码&#xff1a; flag{计算机要从娃娃抓起}

Linux 基本指令

目录 1.常见指令 1.1 ls指令 1.2 pwd指令 1.3 cd指令 1.4 touch指令 1.5 mkdir指令 1.6 rm和rmdir指令 1.7 man指令 1.8 cp指令 1.9 mv指令 ​编辑 1.10 cat指令 1.11 more指令 1.12 less指令 1.13 head指令 1.14.tail指令 1.15 时间相关的指令 1.16 cal…

TCP客户端模拟链接websocket服务端发送消息(二)

兄弟们&#xff0c;我来填坑了&#xff0c;o(╥﹏╥)o o(╥﹏╥)o o(╥﹏╥)o o(╥﹏╥)o o(╥﹏╥)o o(╥﹏╥)o&#xff0c;前几天写了个tcp模拟websocket客户端的以为完成&#xff0c;后面需要发送消息给服务端&#xff0c;以为简单不就是一个发送消息么&#xff0c;这不是一…

LVGL——基础对象篇

LVGL——基础对象篇 基础对象篇部件大小位置设置部件位置&#xff08;position&#xff09;设置相关API函数&#xff1a; 对齐对齐方式 样式给部件添加样式添加普通样式添加本地样式样式触发时机设置单独设置部件中某个部分的样式 事件事件&#xff08;events&#xff09;相关A…

【机器学习】机器学习的基本分类-半监督学习(Semi-supervised Learning)

半监督学习是一种介于监督学习和无监督学习之间的机器学习方法。它利用少量的标注数据&#xff08;有监督数据&#xff09;和大量的未标注数据&#xff08;无监督数据&#xff09;来进行模型训练&#xff0c;从而在标注数据不足的情况下&#xff0c;提升模型的性能。 半监督学习…

coturn docker 项目 搭建【一切正常】

业务需求&#xff1a;需要coturn这个服务 定制语音视频连线 请参考"小红的逃脱外星人追踪计划" coturn项目 本地测试连接服务 turnutils_stunclient -p 3478 127.0.0.1turnutils_stunclient -p 3478 -L 127.0.0.1 127.0.0.1telnet localhost 3478turnutils_uclient …

WOFOST作物模型(3):(本地化校准)优化PCSE模型中的参数

目录 一、准备自己的LAI观测数据二、优化参数三、损失函数四、NLOPT优化五、优化结果可视化一、准备自己的LAI观测数据 在进行田间实测后,得到自己的LAI观测数据 在程序这个地方输入自己的LAI采样日期和观测值 二、优化参数 这里主要选择了TDWI(Total Dry Weight at ger…

Webpack学习笔记(6)

首先搭建一个基本的webpack环境&#xff1a; 执行npm init -y&#xff0c;创建pack.json&#xff0c;保存安装包的一些信息 执行npm install webpack webpack-cli webpack-dev-server html-webpack-plugin -D&#xff0c;出现node_modules和package-lock.json。 1.source-Ma…

嵌入式开发中的机器人表情绘制

机器人的表情有两种&#xff0c;一种是贴图&#xff0c;一钟是调用图形API自绘。 贴图效果相对比较好&#xff0c;在存储空间大的情况下是可以采用的。 自绘比较麻烦&#xff0c;但在资源和空缺少的情况下&#xff0c;也是很有用的。而且自绘很容易通过调整参数加入随机效果&…

表单元素(标签)有哪些?

HTML 中的表单元素&#xff08;标签&#xff09;用于收集用户输入的数据&#xff0c;常见的有以下几种&#xff1a; 文本输入框 <input type"text">&#xff1a;用于单行文本输入&#xff0c;如用户名、密码等。可以通过设置maxlength属性限制输入字符数&…

探索CSDN博客数据:使用Python爬虫技术

探索CSDN博客数据&#xff1a;使用Python爬虫技术 在数字化的浪潮中&#xff0c;数据的获取与分析变得日益关键。CSDN作为中国领先的IT社区和服务平台&#xff0c;汇聚了海量的技术博客与文章&#xff0c;成为一座蕴藏丰富的数据宝库。本文将引领您穿梭于Python的requests和py…

图像处理(大津法找阈值)

1.摄像头获取到一帧的图片&#xff1a; 2.将在图片中把赛道识别出来&#xff1a; 利用大津法将图片进行二值化&#xff0c;把大致赛道从图中区分出来&#xff1a; 3.对进行二值化之后的图像进行处理&#xff0c;将非赛道部分都进行补画&#xff0c;最后要得到一个明显的赛道图&…

vulnhub靶机driftingblues6

开启靶机 扫ip 扫目录 扫端口 访问扫到的ip 192.168.146.156 访问/robots.txt 有目录/textpattern/textpattern 提示不要忘记zip 访问 /textpattern/files目录是网站目录页面 访问目录/textpattern/textpattern 发现登陆页面 访问/textpattern/README 看到网站为Textpattern C…

Xilinx PCIe高速接口入门实战(三)

引言&#xff1a;为保证FPGA设备可以连接并被系统识别&#xff0c;本节讨论了PCIe基础规范和PCIe板卡电气规范的对FPGA配置时间具体要求。 1. 配置访问时间 在PCIe的标准系统中&#xff0c;当系统通电时&#xff0c;处理器上运行的配置软件开始扫描PCIe总线以发现机器拓扑。…

Linux:线程的概念

线程&#xff1a;进程内的一个执行分支&#xff0c;他的执行粒度比进程要细 一、通过进程引入线程 以前我们想要一个执行流&#xff0c;我们需要fork一个子进程&#xff0c;然后子进程需要拷贝 take_struct结构体进程地址空间页表文件描述符表…… 而当我们只创建一个task_st…