一、前言

  公司最近要基于Netty构建一个TCP通讯框架, 因Netty是基于NIO的,为了更好的学习和使用Netty,特意去翻了之前记录的NIO的资料,以及重新实现了一遍NIO的网络通讯,不试不知道,一试发现好多细节没注意,导致客户端和服务端通讯的时候出现了一些非常莫名其妙的问题,这边我记录下耗了我一晚上的问题~

二、正文

  废话不多说,先上问题代码~

  服务端:

package com.nio.server;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;public class NIOServer {private static Selector selector;private static ServerSocketChannel serverSocketChannel;private static ByteBuffer bf = ByteBuffer.allocate(1024);public static void main(String[] args) throws Exception{init();while(true){selector.select();Iterator<SelectionKey> it = selector.selectedKeys().iterator();while(it.hasNext()){SelectionKey key = it.next();if(key.isAcceptable()){System.out.println("连接准备就绪");ServerSocketChannel server = (ServerSocketChannel)key.channel();System.out.println("等待客户端连接中........................");SocketChannel channel = server.accept();channel.configureBlocking(false);channel.register(selector,SelectionKey.OP_READ);}else if(key.isReadable()){System.out.println("读准备就绪,开始读.......................");SocketChannel channel = (SocketChannel)key.channel();System.out.println("客户端的数据如下:");int readLen = 0;bf.clear();StringBuffer sb = new StringBuffer();while((readLen=channel.read(bf))>0){sb.append(new String(bf.array()));bf.clear();}if(-1==readLen){channel.close();}channel.write(ByteBuffer.wrap(("客户端,你传过来的数据是:"+sb.toString()).getBytes()));}it.remove();}}}private static void init() throws Exception{selector = Selector.open();serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.socket().bind(new InetSocketAddress(8080));serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);}
}

  客户端:

package com.nio.client;import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;public class NIOClient {private static Selector selector;public static void main(String[]args) throws Exception{selector = Selector.open();SocketChannel sc = SocketChannel.open();sc.configureBlocking(false);sc.connect(new InetSocketAddress("127.0.0.1",8080));sc.register(selector,SelectionKey.OP_READ);ByteBuffer bf = ByteBuffer.allocate(1024);bf.put("Hi,server,i'm client".getBytes());if(sc.finishConnect()){bf.flip();while(bf.hasRemaining()){sc.write(bf);}while(true){selector.select();Iterator<SelectionKey> it = selector.selectedKeys().iterator();while(it.hasNext()){SelectionKey key = it.next();if(key.isReadable()){bf.clear();SocketChannel othersc  = (SocketChannel)key.channel();othersc.read(bf);System.out.println("服务端返回的数据:"+new String(bf.array()));}}selector.selectedKeys().clear();}}}
}

  服务端运行结果:

  客户端运行结果:

  这边我们可以看到,客户端输出了两次,笔者调试的时候发现,服务端只往客户端写过一次数据,但是客户端却打印了两次数据,而且两次的数据不一样,挺诡异的!然后我就各种查,折磨了我一夜,今早一来,又想着怎么解决问题,不经意间发现了一篇文章:java nio使用的是水平触发还是边缘触发?,文章中指出Nio的Selector.select()是“水平触发”(也叫“条件触发”),只要条件一直满足,那么就会一直触发,至此我如醍醐灌顶:是不是我通道里面的数据第一次没有读取干净?导致客户端触发了多次读取?后来验证之后,发现确实是这个问题,读者可以看我服务器端的代码,我返回的是数据字节数是:"服务端返回的数据:".length()+bf.array().length=26+1024,而客户端只是将这个数据读入1024大小的ByteBuffer中,还有26字节没有读取干净,所以就触发了第二次的读事件!!!

  既然问题找到了,现在就是要解决如何将通道内的数据读取干净了,修改之后的代码如下, 特别注意红色部分:

  服务端:

  

package com.nio.server;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;public class NIOServer {private static Selector selector;private static ServerSocketChannel serverSocketChannel;private static ByteBuffer bf = ByteBuffer.allocate(1024);public static void main(String[] args) throws Exception{init();while(true){selector.select();Iterator<SelectionKey> it = selector.selectedKeys().iterator();while(it.hasNext()){SelectionKey key = it.next();if(key.isAcceptable()){System.out.println("连接准备就绪");ServerSocketChannel server = (ServerSocketChannel)key.channel();System.out.println("等待客户端连接中........................");SocketChannel channel = server.accept();channel.configureBlocking(false);channel.register(selector,SelectionKey.OP_READ);}else if(key.isReadable()){System.out.println("读准备就绪,开始读.......................");SocketChannel channel = (SocketChannel)key.channel();System.out.println("客户端的数据如下:");int readLen = 0;bf.clear();StringBuffer sb = new StringBuffer();while((readLen=channel.read(bf))>0){  bf.flip();byte [] temp = new byte[readLen];bf.get(temp,0,readLen);sb.append(new String(temp));bf.clear();}if(-1==readLen){channel.close();}            System.out.println(sb.toString());channel.write(ByteBuffer.wrap(("客户端,你传过来的数据是:"+sb.toString()).getBytes()));}it.remove();}}}private static void init() throws Exception{selector = Selector.open();serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.socket().bind(new InetSocketAddress(8080));serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);}
}

  客户端:

  

package com.nio.client;import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;public class NIOClient {private static Selector selector;public static void main(String[]args) throws Exception{selector = Selector.open();SocketChannel sc = SocketChannel.open();sc.configureBlocking(false);sc.connect(new InetSocketAddress("127.0.0.1",8080));sc.register(selector,SelectionKey.OP_READ);ByteBuffer bf = ByteBuffer.allocate(1024);bf.put("Hi,server,i'm client".getBytes());if(sc.finishConnect()){bf.flip();while(bf.hasRemaining()){sc.write(bf);}while(true){selector.select();Iterator<SelectionKey> it = selector.selectedKeys().iterator();while(it.hasNext()){SelectionKey key = it.next();if(key.isReadable()){ByteArrayOutputStream bos = new ByteArrayOutputStream();bf.clear();SocketChannel othersc  = (SocketChannel)key.channel();while(othersc.read(bf)>0){bf.flip();while(bf.hasRemaining()){bos.write(bf.get());}bf.clear();};System.out.println("服务端返回的数据:"+bos.toString());}}selector.selectedKeys().clear();}}}
}

  客户端的输出:

   

三、参考链接

https://www.zhihu.com/question/22524908

四、联系本人

  为方便没有博客园账号的读者交流,特意建立一个企鹅群(纯公益,非利益相关),读者如果有对博文不明之处,欢迎加群交流:261746360,小杜比亚-博客园。

转载于:https://www.cnblogs.com/xdouby/p/8942083.html

NIO网络编程中重复触发读(写)事件相关推荐

  1. 网络编程中BIO和NIO的区别

    网络编程中BIO和NIO的区别 先上结论 BIO中,每个请求因为要阻塞直到结果返回,所以比较好的解决是每个请求都需要一个线程来处理,但是线程又是他的制约条件. NIO中,每个请求进来都会绑定到一个ch ...

  2. BIO,Socket网络编程入门代码示例,NIO网络编程入门代码示例,AIO 网络编程

    BIO,Socket网络编程入门代码示例 1.BIO服务器端程序 package cn.itcast.bio;import java.io.InputStream; import java.io.Ou ...

  3. Netty专题-(3)NIO网络编程

    之前在上一章Netty专题-(2)NIO三大核心中介绍了NIO的三大核心类Selector . Channel 和 Buffer,这一章我们将利用这些核心进行编程实现相关的一些功能.在正式进入编程之前 ...

  4. 你对Java网络编程了解的如何?Java NIO 网络编程 | Netty前期知识(二)

    本文主要讲解NIO的简介.NIO和传统阻塞I/O有什么区别.NIO模型和传统I/O模型之间的对比.以及围绕NIO的三大组件来讲解,理论代码相结合. 很喜欢一句话:"沉下去,再浮上来" ...

  5. 网络编程中常见错误码总结

    在网络编程中,总有各种需要注意的环节,几乎每个API都要进行异常处理,判断返回值以及错误码来定位是否需要退出. 本文根据自身使用经验,总结以下错误码及其出现场景和一般处理流程. 网络编程的一般性流程如 ...

  6. 深入分析网络编程中踩过的坑

    网络编程中经常会遇到一些异常的情况,定位问题需要了解协议栈的实现,以下是工作中遇到的一些常见问题的深入分析和解决思路. 问题1:server端业务进程响应心跳超时被监控进程kill,导致数据或者逻辑异 ...

  7. NIO网络编程实战之简单多人聊天室

    NIO网络编程实战 利用NIO编程知识,实现多人聊天室. 1. NIO编程实现步骤 第一步:创建Selector 第二步:创建ServerSocketChannel,并绑定监听端口 第三步:将Chan ...

  8. protobuf在网络编程中的应用思考

    protobuf简介 protobuf是google提供的一个开源序列化框架,类似于XML,JSON这样的数据表示语言,其最大的特点是基于二进制,因此比传统的XML表示高效短小得多.虽然是二进制数据格 ...

  9. 网络编程中的关键问题总结

    网络编程中的关键问题总结 总结下网络编程中关键的细节问题,包含连接建立.连接断开.消息到达.发送消息等等: 连接建立 包括服务端接受 (accept) 新连接和客户端成功发起 (connect) 连接 ...

  10. 实例解析网络编程中的另类内存泄漏

    本文分享自华为云社区<[网络编程开发系列]一种网络编程中的另类内存泄漏>,作者:架构师李肯. 1 写在前面 最近在排查一个网络通讯的压测问题,最后发现跟"内存泄漏"扯上 ...

最新文章

  1. 剑指offer:数组中重复的数字
  2. fragment生命周期
  3. thinkPHP开发基础知识 包括变量神马的
  4. POJ2762(判断无向图的弱连通)
  5. rstudio 导出结果_RStudio如何完美导出包含中文的图
  6. eclipse各种配置
  7. 【机器学习】机器学习从零到掌握之六 -- 教你使用验证分类器测试算法
  8. Atitit.论图片类型 垃圾文件的识别与清理  流程与设计原则 与api概要设计 v2 pbj
  9. 5安卓输入法键盘显示 搜索_手机输入法谁更黑科技?讯飞搜狗百度大PK
  10. presto字符串转日期
  11. span标签置灰_ant-desgin-vue——tree自定义不可选用的置灰或禁用
  12. MatrixDB v4.5.0 重磅发布,全新推出 MARS2 存储引擎!
  13. linux执行jar的两种方式
  14. 总结Android系统启动完整流程(六)
  15. 用文件保存游戏服务器数据恢复,免越狱 教你恢复游戏数据存档
  16. CentOS 8安装源设置基础软件仓库时出错
  17. 多设备monkey测试工具_基于Tkinter GUI操作
  18. SPM混沌映射(含MATLAB代码)
  19. ionic+vue+capacitor系列笔记--capacitor3.X和2.X+android自定义capacitor的JSbridge插件注册与使用(不同版本注册方式不同,返回值格式也不同,使用
  20. 浅谈neg+sbb指令

热门文章

  1. OpenAI 最强对话模型 ChatGPT 注册使用笔记
  2. 狐妖小红娘手游服务器维护多久,狐妖小红娘手游:《狐妖小红娘》手游停服公告...
  3. 蓝狐笔记:DeFi现在仍处在初级阶段 | FBEC 2020特别策划
  4. 来自CodeSmith的震撼
  5. CRM —— 1、搭建开发环境
  6. Hbuilder X自定义安装微信开发工具无法启动
  7. 爬虫实战之爬取电影天堂全部电影信息
  8. Python爬虫入门教程13:高质量电脑桌面壁纸爬取
  9. 运动耳机哪个好?六款耳机教会你选运动耳机
  10. centos lvm管理2t以上硬盘