NIO网络编程中重复触发读(写)事件
一、前言
公司最近要基于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,小杜比亚-博客园。
NIO网络编程中重复触发读(写)事件的更多相关文章
- Netty | 第1章 Java NIO 网络编程《Netty In Action》
目录 前言 1. Java 网络编程 1.1 Javs NIO 基本介绍 1.2 缓冲区 Buffer 1.2 通道 Channel 1.3 选择器 Selector 1.4 NIO 非阻塞网络编程原 ...
- 浅谈TCP/IP网络编程中socket的行为
我认为,想要熟练掌握Linux下的TCP/IP网络编程,至少有三个层面的知识需要熟悉: 1. TCP/IP协议(如连接的建立和终止.重传和确认.滑动窗口和拥塞控制等等) 2. Socket I/O系统 ...
- 【Linux网络编程】TCP网络编程中connect()、listen()和accept()三者之间的关系
[Linux网络编程]TCP网络编程中connect().listen()和accept()三者之间的关系 基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下: conn ...
- 网络编程中select模型和poll模型学习(linux)
一.概述 并发的网络编程中不管是阻塞式IO还是非阻塞式IO,都不能很好的解决同时处理多个socket的问题.操作系统提供了复用IO模型:select和poll,帮助我们解决了这个问题.这两个函数都能够 ...
- Java网络编程中异步编程的理解
目录 前言 一.异步,同步,阻塞和非阻塞的理解 二.异步编程从用户层面和框架层面不同角度的理解 用户角度的理解 框架角度的理解 三.为什么使用异步 四.理解这些能在实际中的应用 六.困惑 参考文章 前 ...
- [转帖]关于网络编程中MTU、TCP、UDP优化配置的一些总结
关于网络编程中MTU.TCP.UDP优化配置的一些总结 https://www.cnblogs.com/maowang1991/archive/2013/04/15/3022955.html 感谢原作 ...
- java 基础之--nio 网络编程
在传统的Java 网络编程中,对于客户端的每次连接,对于服务器来说,都要创建一个新的线程与客户端进行通讯,这种频繁的线程的创建,对于服务器来说,是一种巨大的损耗,在Java 1.4 引入Java ni ...
- 网络编程中的read,write函数
关于TCP/IP协议,建议参考Richard Stevens的<TCP/IP Illustrated,vol1>(TCP/IP详解卷1). 关于第二层面,依然建议Richard Steve ...
- Unix网络编程中的五种I/O模型_转
转自:Unix网络编程中的的五种I/O模型 下面主要是把unp第六章介绍的五种I/O模型. 1. 阻塞I/O模型 例如UDP函数recvfrom的内核到应用层.应用层到内核的调用过程是这样的:首先把描 ...
随机推荐
- leetcode-746-Min Cost Climbing Stairs(动态规划)
题目描述: On a staircase, the i-th step has some non-negative cost cost[i] assigned (0 indexed). Once yo ...
- xshell sftp可用命令,sftp: cannot open d: to write![解决]
sftp可用命令: cd 路径 更改远程目录到“路径” lcd 路径 更改本地目录到“路径” chgrp group path 将文件“path”的组更改为“group” chmod mode pat ...
- 20155204 2016-2017-2 《Java程序设计》第9周学习总结
20155204 2016-2017-2 <Java程序设计>第9周学习总结 教材学习内容总结 SRP单一职责原则,也叫内聚性,判断srp the 类 方法 itself c与java的区 ...
- 20155306 实验二 Java面向对象程序设计
20155306 实验二 Java面向对象程序设计 实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉S.O.L.I.D原则 了解设计模式 实验要 ...
- 20155318 2016-2017-2 《Java程序设计》第二周学习总结
20155318 2016-2017-2 <Java程序设计>第二周学习总结 教材学习内容总结 Java编程风格 基本语法与C相似:调用main函数写public static void ...
- 20155325 2016-2017-2 《Java程序设计》课程总结
(按顺序)每周作业链接汇总 预备作业1:浅谈对师生关系的看法以及对未来学习生活的展望 预备作业2:学习娄老师<做中学>系列文章.自身C语言情况.Java课程目标 预备作业3:安装虚拟机情况 ...
- 20155338 《Java程序设计》实验三(敏捷开发与XP实践)实验报告
20155338 <Java程序设计>实验三(敏捷开发与XP实践)实验报告 一.实验内容及步骤 (一)使用Code菜单 • 在IDEA中使用工具(Code->Reformate Co ...
- 2016-2017-2 20155339 实验二《Java面向对象程序设计》实验报告
2016-2017-2 20155339 实验二<Java面向对象程序设计>实验报告 实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟 ...
- Yii 2.0 使用验证码
Yii2.0 提供了验证码组件.调用起来比较方便.以登录页面添加验证码为例. 1. 模型中添加字段和验证规则. common\models\LoginForm 添加如下代码 public $captc ...
- XAF-物料管理信息工作日志
前段时间已经开始了第一阶段验收了,客户方并未把重点放在业务流程上面,一直在调整一些界面问题.有点小纠结. 今天要调一下菜单位置. 没修改时,是这样的: 到了列表界面,会多一个全文检索出来. 后来,客户 ...