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

Wesley13
• 阅读 983

一、前言

  公司最近要基于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();
            }
        }
    }
}

  服务端运行结果:

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

   客户端运行结果:

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

  这边我们可以看到,客户端输出了两次,笔者调试的时候发现,服务端只往客户端写过一次数据,但是客户端却打印了两次数据,而且两次的数据不一样,挺诡异的!然后我就各种查,折磨了我一夜,今早一来,又想着怎么解决问题,不经意间发现了一篇文章: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();
            }
        }
    }
}

  客户端的输出:

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

三、参考链接

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

四、联系本人

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

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
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
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Stella981 Stella981
3年前
Netty如何实现同一个端口接收TCP和HTTP请求
前言在java的网络编程世界里,Netty的地位可谓是举足轻重,说到基于NIO的网络编程,Netty几乎成为企业的首选,本文不会过多介绍Netty的基本使用等知识,本文着重介绍在Netty中如何实现同一个端口,既能接收TCP请求,也能接收Http请求。由于一些特殊的原因,我要实现一款消息中间件,暂时称为“企业消息总线”吧。简单描述一下场景,对如
Wesley13 Wesley13
3年前
Java BIO、NIO与AIO的介绍(学习过程)
JavaBIO、NIO与AIO的介绍因为netty是一个NIO的框架,所以在学习netty的过程中,开始之前。针对于BIO,NIO,AIO进行一个完整的学习。学习资源分享:Netty学习:https://www.bilibili.com/video/BV1DJ411m7NR?from
Stella981 Stella981
3年前
NIO 看破也说破(四)—— Java的NIO
Java的NIO有selector,系统内核也提供了多种非阻塞IO模型,Java社区也出现了像netty这种优秀的NIO框架。Java的NIO与内核的阻塞模型到底什么关系,为什么Java有NIO的API还出现了netty这种框架,网上说的reactor到底是什么?本文通过分析代码,带你一步步搞清楚Java的NIO和系统函数之间的关系,以及Java
Stella981 Stella981
3年前
BIO、NIO、AIO系列二:Netty
一、概述Netty是一个Java的开源框架。提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。Netty是一个NIO客户端,服务端框架。允许快速简单的开发网络应用程序。例如:服务端和客户端之间的协议,它简化了网络编程规范。二、NIO开发的问题
Stella981 Stella981
3年前
Netty 入门初体验
Netty简介Netty是一款异步的事件驱动的网络应用程序框架,支持快速开发可维护的高性能的面向协议的服务器和客户端。Netty主要是对java的nio包进行的封装为什么要使用Netty上面介绍到Netty是一款高性能的网络通讯框架,那么我们为什么要使用Netty,换句话说,
Stella981 Stella981
3年前
Netty入门
一、是什么  Netty是一个高性能、异步事件驱动、基于JavaNIO的异步的可扩展的客户端/服务器网络编程框架。  Netty提供了对TCP、UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过FutureListener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果
Stella981 Stella981
3年前
Netty堆外内存泄露排查与总结
导读Netty是一个异步事件驱动的网络通信层框架,用于快速开发高可用高性能的服务端网络框架与客户端程序,它极大地简化了TCP和UDP套接字服务器等网络编程。Netty底层基于JDK的NIO,我们为什么不直接基于JDK的NIO或者其他NIO框架:1.使用JDK自带的NIO需要了解太多的概念,编程复杂。2
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这