Java 网络编程之TCP(五):分析服务端注册OP_WRITE写数据的各种场景(二)

接上文

二、注册OP_WRITE写数据

服务端代码:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
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;/*** 基于NIO实现服务端,通过Selector基于事件驱动客户端的读取* 服务端接收到数据后,缓存,注册OP_WRITE事件,收到状态转发数据**/
class NIOSelectorOpWriteServer1 {Selector selector;public static void main(String[] args) throws IOException {NIOSelectorOpWriteServer1 server = new NIOSelectorOpWriteServer1();server.start(); // 开启监听和事件处理}public void start() {initServer();// selector非阻塞轮询有哪些感兴趣的事件到了doService();}private void doService() {if (selector == null) {System.out.println("server init failed, without doing read/write");return;}try {while (true) {while (selector.select() > 0) {Set<SelectionKey> keys = selector.selectedKeys(); // 感兴趣且准备好的事件Iterator<SelectionKey> iterator = keys.iterator(); // 迭代器遍历处理,后面要删除集合元素while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove(); // 删除当前元素,防止重复处理// 下面根据事件进行分别处理if (key.isAcceptable()) {// 客户端连接事件acceptHandler(key);} else if (key.isReadable()) {// 读取客户端数据readHandler(key);} else if (key.isWritable()) {// 为了避免重复写,需要先去除OP_WRITE注册状态key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);writeHandler(key);}}}}} catch (IOException exception) {exception.printStackTrace();}}private void initServer() {try {ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.bind(new InetSocketAddress(9090));// 此时在selector上注册感兴趣的事件// 这里先注册OP_ACCEPT: 客户端连接事件selector = Selector.open();serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("server init success");} catch (IOException exception) {exception.printStackTrace();System.out.println("server init failied");}}public void acceptHandler(SelectionKey key) {ServerSocketChannel server = (ServerSocketChannel) key.channel(); // 获取客户端的channeltry {SocketChannel client = server.accept();client.configureBlocking(false); // 设置client非阻塞System.out.println("server receive a client :" + client);// 注册OP_READ事件,用于从客户端读取数据// 给Client分配一个buffer,用于读取数据,注意buffer的线程安全ByteBuffer buffer = ByteBuffer.allocate(1024); // buffer这个参数注册的时候也可以不用client.register(key.selector(), SelectionKey.OP_READ, buffer);} catch (IOException exception) {exception.printStackTrace();}}public void readHandler(SelectionKey key) {System.out.println("read handler");SocketChannel client = (SocketChannel) key.channel(); // 获取客户端的channelByteBuffer buffer = (ByteBuffer) key.attachment(); // 获取Client channel关联的bufferbuffer.clear(); // 使用前clear// 防止数据分包,需要while循环读取try {while (true) {int readLen = client.read(buffer);if (readLen > 0) {// 读取到数据了buffer.flip();byte[] data = new byte[buffer.limit()];buffer.get(data);System.out.println("server read data from " + client + ", data is :" + new String(data));// 给其他客户端注册OP_WRITE;registerWrite(client, data);} else if (readLen == 0) {// 没读到数据System.out.println(client + " : no data");break;} else if (readLen < 0) {// client 关闭连接// 当客户端主动连接断开时,为了让服务器知道断开了连接,会产生OP_READ事件。所以需要判断读取长度,当读到-1时,关闭channel。System.out.println(client + " close");client.close();break;}}} catch (IOException exception) {exception.printStackTrace();// client 关闭连接System.out.println(client + " disconnect");// todo:disconnect 导致一直有read事件,怎么办?try {client.close();} catch (IOException ex) {System.out.println("close ex");}}}public void writeHandler(SelectionKey key) {System.out.println("write handler");SocketChannel client = (SocketChannel) key.channel(); // 获取客户端的channelByteBuffer buffer = (ByteBuffer) key.attachment(); // 获取Client channel关联的buffer,此时处于读模式try {while (buffer.hasRemaining()) {client.write(buffer);}} catch (IOException exception) {System.out.println("write error");exception.printStackTrace();}}private void registerWrite(SocketChannel myself, byte[] data) throws IOException {Set<SelectionKey> keys = selector.keys();// read/write 对应同一个key,同一个client不会发送两遍for (SelectionKey key : keys) {SelectableChannel channel = key.channel();if (channel instanceof SocketChannel && channel != myself) {key.interestOps(key.interestOps() + SelectionKey.OP_WRITE);ByteBuffer attachment = (ByteBuffer) key.attachment();attachment.clear();attachment.put(data);attachment.flip();}}}
}

这里有几个注意项:

1.在注册OP_WRITE时,需要给所有其他客户端注册;

2.注册OP_WRITE时:是使用key.interestOps(key.interestOps() + SelectionKey.OP_WRITE);避免对原来的OP_READ事件进行覆盖;在OP_WRITE事件来的时候,要把先把OP_WRITE事件去掉,key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); 防止重复写事件状态发生;

3.注册OP_WRITE时,要写的数据,直接给到了原来channel对应的attachment里了;在OP_WRITE事件来的时候,可以直接用;

到此,我们一定有个疑问:既然服务端关不关注OP_WRITE事件,都可以给客户端发送数据,意义何在?

那我们就要看下OP_WRITE事件的具备条件:send-queue是否有空间

而服务端要写数据要关注:服务端数据是否准备好了 + send-queue是否有空间

服务端一般都是在自己数据准备好了后,再注册对客户端的OP_WRITE事件。

但是上面的代码中,在给客户端写数据时,是一直写,直到数据写完,但是 send-queue空间有限,当 send-queue写满后,写操作就会阻塞,导致单线程下业务阻塞。。。

此时,OP_WRITE的优势就体现出来了

我们可以对OP_WRITE的使用方式稍微调整,就可以解决上面的问题:

当我们收到OP_WRITE事件,开始给客户端写数据后,当我们发现该客户端对应的send-queue写满,SocketChannel.write(buffer)会返回已经写出去的字节数,此时为0;我们根据此标志知道,此时send-queue满,不能再写了,此时我们记录下没有写的数据,再次给该客户端注册一个OP_WRITE事件,结束本次写过程;让业务线程继续处理其他事件,等到该客户端对应的send-queue有空闲的时候,会再次收到收到OP_WRITE事件,我们就可以继续写数据了;这样就是解决了写数据满导致业务阻塞的问题了。

关于上面的观点可以参考:

java nio selectedKey,关于 NIO 你不得不知道的一些“地雷”-CSDN博客文章浏览阅读302次。本文是笔者在学习NIO过程中发现的一些比较容易让人忽略的知识的一个总结,而这些让人忽略的小细节恰恰是NIO网络编程中必不可少。虽然现在我们不会直接编写NIO来完成我们的网络层通讯,而是使用成熟的基于NIO的网络框架来实现我们的网络层。如,netty、mina。但对NIO网络编程过程的了解,非常有助于我们更深入的理解netty、mina等网络框架,以至于能更好的使用它们。因此,本文并不对NIO的一些..._java nio selectionkey中的事件多次变化都能每监听到吗https://blog.csdn.net/weixin_39850920/article/details/115994629?spm=1001.2014.3001.5506

Java网络编程——NIO处理写事件(SelectionKey.OP_WRITE)-CSDN博客文章浏览阅读2.1k次,点赞5次,收藏23次。selectionKey.interestOps()就是已经注册的事件,SelectionKey中可以只用1个整形数字来表示多个注册的事件(interestOps变量),SelectionKey.OP_READ=1(二进制为 00000001),SelectionKey.OP_WRITE=4(二进制为 00000100),SelectionKey.OP_CONNECT=8(二进制为 00001000),SelectionKey.OP_ACCEPT=16(二进制为 00010000)。..._selectionkey.op_writehttps://blog.csdn.net/huyuyang6688/article/details/126106949?spm=1001.2014.3001.5506

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

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

相关文章

C++ //练习 13.58 编写新版本的Foo类,其sorted函数中有打印语句,测试这个类,来验证你对前两题的答案是否正确。

C Primer&#xff08;第5版&#xff09; 练习 13.58 练习 13.58 编写新版本的Foo类&#xff0c;其sorted函数中有打印语句&#xff0c;测试这个类&#xff0c;来验证你对前两题的答案是否正确。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xf…

RFC 6071: IP Security (IPsec) and Internet Key Exchange (IKE) Document Roadmap

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/96882d1fb67b4383bc77c4dd421f7b

【一刷《剑指Offer》】面试题 10:二进制中 1 的个数

力扣对应题目链接&#xff1a;191. 位1的个数 - 力扣&#xff08;LeetCode&#xff09; 牛客对应题目链接&#xff1a;二进制中1的个数_牛客题霸_牛客网 一、《剑指Offer》内容 核心考点 &#xff1a;二进制计算。 二、分析问题 1、循环检查二进制位 可以直接循环检查给定数字…

Qt 顺序容器类与关联容器类

hello&#xff01;欢迎大家来到我的Qt学习系列之顺序容器类与关联容器类。希望这篇文章能对你有所帮助&#xff01;&#xff01;&#xff01;大家也可以关注我的专栏&#xff0c;我会持续更新一些Qt的学习知识&#xff0c;和数据库的学习内容&#xff01;&#xff01;&#xff…

Springboot实现国际化以及部署Linux不生效问题

1、在application.properties 添加以下配置&#xff1a; #国际化配置 spring.messages.basenamei18n/messages/messages spring.messages.fallback-to-system-localefalse 2、添加配置文件在 resources目录下 如下图所示&#xff1a; 这个国际化文件命名有个坑&#xff0c;必须…

胸部X光图像分类:因果视角

Chest X-ray Image Classification: A Causal Perspective 摘要 这篇论文提出了一种新的方法来处理胸部X射线&#xff08;CXR&#xff09;分类问题&#xff0c;将因果推理融入到基于深度学习的方法中。尽管近年来深度学习在CXR分类方面取得了许多进展&#xff0c;但确保这些算…

【接口测试】JMeter接口关联测试

‍‍1 前言 我们来学习接口管理测试&#xff0c;这就要使用到JMeter提供的JSON提取器和正则表达式提取器了&#xff0c;下面我们来看看是如何使用的吧。 2 JSON提取器 1、添加JSON提取器 在线程组右键 > 添加 > 后置处理器 > JSON提取器 2、JSON提取器参数说明 N…

在浏览器中查看Revit模型:原理及实现

Greg Schleusner AIA 有一天向我提到&#xff0c;业内许多人认为带有 SVF2 的 Autodesk Viewer 是建筑模型最强大、性能最强的在线查看器之一&#xff0c;并且想知道开源 VIM 查看器和格式如何比较。 可以使用NSDT 3DConvert的 Revit插件 上传你的模型并在线查看预览&#xff…

vue的组件化

1.什么是组件化&#xff1f; 组件化开发 指的是&#xff1a;根据 封装 的思想&#xff0c; 把页面上可重用的 UI 结构封装为组件 &#xff0c;从而方便项目的开发和维护。 2.什么是Vue组件 组件是Vue中的一个重要概念&#xff0c;是一个可以重复使用的Vue实例&#xff0c;它拥…

Python爬虫--用户代理IP池

前面一篇讲了用户 UA 代理池&#xff0c;现在这篇来讲下 IP 代理&#xff0c; 相对于 UA 来说&#xff0c;IP 更容易被封&#xff0c; 这里讲两种方法。 方法一&#xff1a;本地ip池 方法一 就是将 IP 拿下来本地&#xff0c;然后通过随机选取或者其他来调用 这就跟之前使用…

怎么通过PHP语言实现远程控制棋牌室

怎么通过PHP语言实现远程控制棋牌室呢&#xff1f; 本文描述了使用PHP语言调用HTTP接口&#xff0c;实现控制棋牌室&#xff0c;通过专用的包间控制器&#xff0c;来实现包间内所有电器以及门锁的独立控制。 可选用产品&#xff1a;可根据实际场景需求&#xff0c;选择对应的规…

洗鞋店上门预约小程序

洗鞋上门预约小程序&#xff0c;一款针对洗鞋行业的移动应用&#xff0c;让你轻松享受洗鞋的便捷服务。只需一键预约&#xff0c;多种洗鞋选项任你选&#xff0c;满足你的个性化需求。简洁明了的操作界面&#xff0c;让你快速下单&#xff0c;享受高效的洗鞋体验。 该系统凭借…

BetterDisplay Pro for Mac:显示器校准软件

BetterDisplay Pro for Mac是一款出色的显示器校准软件&#xff0c;旨在提升你的视觉体验。它提供了准确的显示器参数调整&#xff0c;包括亮度、对比度、色温和色域等&#xff0c;让你的显示器呈现更真实、清晰、细腻的图像。此外&#xff0c;软件还提供多种预设模式和自定义选…

遥感雷达波段的原理及应用

雷达波段是不同波长的组。每一种都有其独特的穿透地球表面的能力。它们还可以揭示环境的不同方面。 雷达频段在电磁频谱内具有特定的频率范围。这些波段由 L-、S-、C- 和 X-波段等字母表示。稍后会详细介绍这一点。 什么是合成孔径雷达&#xff1f; 合成孔径雷达 (SAR) 是一…

后台图书管理系统:SSM整合开发案例

代码已经完善,文章内容会在后续补充 代码地址(https://javazhang.lanzn.com/ig8tf1wd2aba 密码:1234) 1.1 .SpringConfig 代码 Configuration ComponentScan({"com.itheima.service" }) PropertySource("classpath:jdbc.properties") Import({JdbcConfig…

Types of infrastructures in the management of stakeholder relationships

Types of infrastructures in the management of stakeholder relationships are follows: Legal infrastructure Contractual infrastructure Organizational infrastructure Governmental infrastructure

【STM32+HAL】读取电池电量

一、准备工作 有关CUBEMX的初始化配置&#xff0c;参见我的另一篇blog&#xff1a;【STM32HAL】CUBEMX初始化配置 有关定时器触发ADC模式配置&#xff0c;详见【STM32HAL】ADC采集波形实现 有关软件触发ADC模式配置&#xff0c;详见【STM32HAL】三轴按键PS2摇杆 二、所用工具…

深入理解MySQL事务管理

事务是数据库操作中的基本概念&#xff0c;它确保了数据库的一致性和完整性。MySQL作为一种流行的关系型数据库管理系统&#xff0c;提供了强大的事务支持。本文将详细介绍MySQL事务的概念、特性、使用方法以及常见的实践技巧。 1. 什么是事务&#xff1f; 在数据库中&#x…

如何替代传统的方式,提高能源企业敏感文件传输的安全性?

能源行业是一个关键的基础设施领域&#xff0c;它涉及能源的勘探、开采、生产、转换、分配和消费。随着全球经济的发展和人口的增长&#xff0c;能源需求持续上升&#xff0c;这对能源行业的可持续发展提出了挑战。能源行业的传输场景多种多样&#xff0c;需要重点关注能源企业…

NDK 基础(四)—— C++ 高级特性1

1、STL 容器 STL&#xff08;Standard Template Library&#xff09;译为标准模板库&#xff0c;它是一套强大的标准库&#xff0c;是 C 标准库的一部分&#xff0c;在 iostream 中。STL 提供了包括容器在内的许多常用的数据结构和算法&#xff0c;以及用于操作这些数据结构的…