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

一、前言

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

二、正文

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

  服务端:

1package com.nio.server; 2 3import java.net.InetSocketAddress; 4import java.nio.ByteBuffer; 5import java.nio.channels.SelectionKey; 6import java.nio.channels.Selector; 7import java.nio.channels.ServerSocketChannel; 8import java.nio.channels.SocketChannel; 9import java.util.Iterator; 10 11public class NIOServer { 12 private static Selector selector; 13 private static ServerSocketChannel serverSocketChannel; 14 private static ByteBuffer bf = ByteBuffer.allocate(1024); 15 public static void main(String[] args) throws Exception{ 16 init(); 17 while(true){ 18 selector.select(); 19 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); 20 while(it.hasNext()){ 21 SelectionKey key = it.next(); 22 if(key.isAcceptable()){ 23 System.out.println("连接准备就绪"); 24 ServerSocketChannel server = (ServerSocketChannel)key.channel(); 25 System.out.println("等待客户端连接中........................"); 26 SocketChannel channel = server.accept(); 27 channel.configureBlocking(false); 28 channel.register(selector,SelectionKey.OP_READ); 29 } 30 else if(key.isReadable()){ 31 System.out.println("读准备就绪,开始读......................."); 32 SocketChannel channel = (SocketChannel)key.channel(); 33 System.out.println("客户端的数据如下:"); 34 35 int readLen = 0; 36 bf.clear(); 37 StringBuffer sb = new StringBuffer(); 38 while((readLen=channel.read(bf))>0){ 39 sb.append(new String(bf.array())); 40 bf.clear(); 41 } 42 if(-1==readLen){ 43 channel.close(); 44 } 45 channel.write(ByteBuffer.wrap(("客户端,你传过来的数据是:"+sb.toString()).getBytes())); 46 } 47 it.remove(); 48 } 49 } 50 } 51 private static void init() throws Exception{ 52 selector = Selector.open(); 53 serverSocketChannel = ServerSocketChannel.open(); 54 serverSocketChannel.configureBlocking(false); 55 serverSocketChannel.socket().bind(new InetSocketAddress(8080)); 56 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); 57 } 58}

  客户端:

1package com.nio.client; 2 3import java.net.InetSocketAddress; 4import java.nio.ByteBuffer; 5import java.nio.channels.SelectionKey; 6import java.nio.channels.Selector; 7import java.nio.channels.SocketChannel; 8import java.util.Iterator; 9 10public class NIOClient { 11 private static Selector selector; 12 public static void main(String[]args) throws Exception{ 13 selector = Selector.open(); 14 SocketChannel sc = SocketChannel.open(); 15 sc.configureBlocking(false); 16 sc.connect(new InetSocketAddress("127.0.0.1",8080)); 17 sc.register(selector,SelectionKey.OP_READ); 18 19 ByteBuffer bf = ByteBuffer.allocate(1024); 20 bf.put("Hi,server,i'm client".getBytes()); 21 22 23 if(sc.finishConnect()){ 24 bf.flip(); 25 while(bf.hasRemaining()){ 26 sc.write(bf); 27 } 28 29 while(true){ 30 selector.select(); 31 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); 32 while(it.hasNext()){ 33 SelectionKey key = it.next(); 34 35 if(key.isReadable()){ 36 bf.clear(); 37 SocketChannel othersc = (SocketChannel)key.channel(); 38 othersc.read(bf); 39 System.out.println("服务端返回的数据:"+new String(bf.array())); 40 } 41 } 42 selector.selectedKeys().clear(); 43 } 44 } 45 } 46}

  服务端运行结果:

   客户端运行结果:

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

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

  服务端:

1package com.nio.server; 2 3import java.net.InetSocketAddress; 4import java.nio.ByteBuffer; 5import java.nio.channels.SelectionKey; 6import java.nio.channels.Selector; 7import java.nio.channels.ServerSocketChannel; 8import java.nio.channels.SocketChannel; 9import java.util.Iterator; 10 11public class NIOServer { 12 private static Selector selector; 13 private static ServerSocketChannel serverSocketChannel; 14 private static ByteBuffer bf = ByteBuffer.allocate(1024); 15 public static void main(String[] args) throws Exception{ 16 init(); 17 while(true){ 18 selector.select(); 19 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); 20 while(it.hasNext()){ 21 SelectionKey key = it.next(); 22 if(key.isAcceptable()){ 23 System.out.println("连接准备就绪"); 24 ServerSocketChannel server = (ServerSocketChannel)key.channel(); 25 System.out.println("等待客户端连接中........................"); 26 SocketChannel channel = server.accept(); 27 channel.configureBlocking(false); 28 channel.register(selector,SelectionKey.OP_READ); 29 } 30 else if(key.isReadable()){ 31 System.out.println("读准备就绪,开始读......................."); 32 SocketChannel channel = (SocketChannel)key.channel(); 33 System.out.println("客户端的数据如下:"); 34 35 int readLen = 0; 36 bf.clear(); 37 StringBuffer sb = new StringBuffer(); 38 while((readLen=channel.read(bf))>0){ 39 bf.flip(); 40 byte [] temp = new byte[readLen]; 41 bf.get(temp,0,readLen); 42 sb.append(new String(temp)); 43 bf.clear(); 44 } 45 if(-1==readLen){ 46 channel.close(); 47 }            System.out.println(sb.toString()); 48 channel.write(ByteBuffer.wrap(("客户端,你传过来的数据是:"+sb.toString()).getBytes())); 49 } 50 it.remove(); 51 } 52 } 53 } 54 private static void init() throws Exception{ 55 selector = Selector.open(); 56 serverSocketChannel = ServerSocketChannel.open(); 57 serverSocketChannel.configureBlocking(false); 58 serverSocketChannel.socket().bind(new InetSocketAddress(8080)); 59 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); 60 } 61}

  客户端:

1package com.nio.client; 2 3import java.io.ByteArrayOutputStream; 4import java.net.InetSocketAddress; 5import java.nio.ByteBuffer; 6import java.nio.channels.SelectionKey; 7import java.nio.channels.Selector; 8import java.nio.channels.SocketChannel; 9import java.util.Iterator; 10 11public class NIOClient { 12 private static Selector selector; 13 public static void main(String[]args) throws Exception{ 14 selector = Selector.open(); 15 SocketChannel sc = SocketChannel.open(); 16 sc.configureBlocking(false); 17 sc.connect(new InetSocketAddress("127.0.0.1",8080)); 18 sc.register(selector,SelectionKey.OP_READ); 19 20 ByteBuffer bf = ByteBuffer.allocate(1024); 21 bf.put("Hi,server,i'm client".getBytes()); 22 23 24 if(sc.finishConnect()){ 25 bf.flip(); 26 while(bf.hasRemaining()){ 27 sc.write(bf); 28 } 29 30 while(true){ 31 selector.select(); 32 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); 33 while(it.hasNext()){ 34 SelectionKey key = it.next(); 35 36 37 if(key.isReadable()){ 38 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 39 bf.clear(); 40 SocketChannel othersc = (SocketChannel)key.channel(); 41 while(othersc.read(bf)>0){ 42 bf.flip(); 43 while(bf.hasRemaining()){ 44 bos.write(bf.get()); 45 } 46 bf.clear(); 47 }; 48 System.out.println("服务端返回的数据:"+bos.toString()); 49 } 50 } 51 selector.selectedKeys().clear(); 52 } 53 } 54 } 55}

  客户端的输出:

  

三、参考链接

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

四、联系本人

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

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Netty如何实现同一个端口接收TCP和HTTP请求

前言在java的网络编程世界里,Netty的地位可谓是举足轻重,说到基于NIO的网络编程,Netty几乎成为企业的首选,本文不会过多介绍Netty的基本使用等知识,本文着重介绍在Netty中如何实现同一个端口,既能接收TCP请求,也能接收Http请求。由于一些特殊的原因,我要实现一款消息中间件,暂时称为“企业消息总线”吧。简单描述一下场景,对如

Java BIO、NIO与AIO的介绍(学习过程)

JavaBIO、NIO与AIO的介绍因为netty是一个NIO的框架,所以在学习netty的过程中,开始之前。针对于BIO,NIO,AIO进行一个完整的学习。学习资源分享:Netty学习:https://www.bilibili.com/video/BV1DJ411m7NR?from

NIO 看破也说破(四)—— Java的NIO

Java的NIO有selector,系统内核也提供了多种非阻塞IO模型,Java社区也出现了像netty这种优秀的NIO框架。Java的NIO与内核的阻塞模型到底什么关系,为什么Java有NIO的API还出现了netty这种框架,网上说的reactor到底是什么?本文通过分析代码,带你一步步搞清楚Java的NIO和系统函数之间的关系,以及Java