这两天仿hadoop 写java RPC框架,使用PB作为序列号工具,在写读数据的时候遇到一个小坑。之前写过NIO代码,恰好是错误的代码产生正确的逻辑,误以为自己写对了。现在简单整理一下。

使用NIO,select()到读事件时,要处理4种情况:

1. channel还有数据,继续读。

2. channel中暂时没数据,但channel还没断开,这是读取到的数据个数为0,结束读,继续到select()处阻塞等待数据。

3. 另一端channel.close()关闭连接,这时候读channel返回的读取数是-1,表示已经到末尾,跟读文件到末尾时是一样的。既然已经结束了,就把对应的SelectionKey给cancel掉,表示selector不再监听这个channel上的读事件。并且关闭连接,本端channel.close()。

4. 另一端被强制关闭,也就是channel没有close()就被强制断开了,这时候本端会抛出一个IOException异常,要处理这个异常。

之前对 另一端channel.close()关闭连接 没有细究,不清楚 读channel返回的读取数-1 是什么意思。然后没有cancel对应的SelectionKey,也没关闭连接,结果就是selector.select()一直返回读事件,但是没有数据。

直接贴服务器和客户端代码:

Server:

package socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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;
import java.util.Set; public class NIOServer2 { private void startServer() throws IOException {
Selector selector = Selector.open(); {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ServerSocket ss = ssc.socket();
InetSocketAddress address = new InetSocketAddress(9000);
ss.bind(address); System.out.println("ssc 0 : " + ssc);
System.out.println("ss 0 : " + ss); SelectionKey acceptKey = ssc.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("acceptKey: " + acceptKey);
printKeyInfo(acceptKey);
System.out.println("Going to listen on 9000");
} while (true) {
System.out.println("===================================\nstart select...");
int num = selector.select();
System.out.println("NIOServer: Number of keys after select operation: " + num); Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator(); while (it.hasNext()) {
SelectionKey key = it.next();
System.out.println("key: " + key);
printKeyInfo(key); it.remove(); if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
System.out.println("select ACCEPT");
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false); System.out.println("ssc 1 : " + ssc);
System.out.println("sc 1 : " + sc); SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);
System.out.println("new key:" + newKey);
printKeyInfo(newKey);
}
else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
// System.out.println("select READ");
// System.out.print("before cancel:");printKeyInfo(key);
// key.cancel();
// System.out.println("after cancel:");printKeyInfo(key);
SocketChannel sc = (SocketChannel) key.channel();
System.out.println("sc 2 : " + sc); //echo data
//下面的处理是正确的,count<0则cancel key。count=0则进入下一轮select()阻塞等待数据。
// try {
// int count = doRead(key);
// if (count < 0) {
// key.cancel();
// System.out.println("cancel key for < 0");
// sc.read(ByteBuffer.allocate(2));
// }
// } catch(IOException e) {
// e.printStackTrace();
// key.cancel();
// System.out.println("cancel key");
// } //下面的处理过程是错误的,偶然情况下会出现正确逻辑。在客户端连续写,写完马上关闭连接,这时下面代码能打印出客户端的输出,
//客户端关闭连接,下面的代码马上爆出异常,是这行代码。java.io.IOException: 您的主机中的软件中止了一个已建立的连接。
// int nbytes = 0;
// ByteBuffer echoBuffer = ByteBuffer.allocate(16);
// while (true) {
// echoBuffer.clear();
// int r = sc.read(echoBuffer);
// System.out.println(new String(echoBuffer.array()));
// if (r <= 0) break;
// echoBuffer.flip();
// sc.write(echoBuffer);
// nbytes += r;
// }
// System.out.println("echoed " + nbytes + " from " + sc); //下面的是处理过程是正确的。正确的做法就是对读取到n,0,-1分别处理,还要对客户端强制关闭的异常做处理
while (true) {
ByteBuffer buffer = ByteBuffer.allocate(2);
buffer.clear();
int r;
try {
r = sc.read(buffer);
System.out.println("r = " + r);
System.out.println(new String(buffer.array()));
if (r < 0) {
//客户端socket.close()会到这里,读取数r=-1
key.cancel();
System.out.println("cancel key for < 0");
break;
} else if (r == 0) {
//客户端socket没有关闭,而channel没有数据,数据数r=0。
//有时候select()返回了,但channel不一定有数据。可能select()是被其他方法唤醒
break;
}
} catch (IOException e) {
//客户端强制关闭会来这里报异常
e.printStackTrace();
key.cancel();
System.out.println("cancel key for Exception");
break;
}
}//while
}// if ... else if
// try {
// Thread.sleep(500);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}//while
}//while
} private int doRead(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
while (true) {
int count = -1;
ByteBuffer buffer = ByteBuffer.allocate(2);
if (buffer.remaining() > 0) {
count = channel.read(buffer);
System.out.println("count = " + count);
if (count <= 0) return count;
}
}
} private static void printKeyInfo(SelectionKey sk) {
String s = new String(); s = "Att: " + (sk.attachment() == null ? "no" : "yes");
s += ", Read: " + sk.isReadable();
s += ", Acpt: " + sk.isAcceptable();
s += ", Cnct: " + sk.isConnectable();
s += ", Wrt: " + sk.isWritable();
s += ", Valid: " + sk.isValid();
s += ", interestOps: " + sk.interestOps();
s += ", readyOps: " + sk.readyOps();
System.out.println(s);
} public static void main(String[] args) {
try {
new NIOServer2().startServer();
} catch (IOException e) {
e.printStackTrace();
}
}
}

Client:

package socket;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException; public class SocketClient { public static void main(String[] args) throws UnknownHostException, IOException, InterruptedException {
Socket socket = new Socket("localhost", 9000);
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
byte[] bytes = "fdfd".getBytes();
// System.out.println("send fdfd");
out.write(bytes);
out.flush(); // Thread.sleep(15*1000); // System.out.println("send loll");
out.write("loull".getBytes());
out.flush(); // Thread.sleep(1*1000);
socket.close();
System.out.println("client socket close");
}
}

浪费了一些时间,一方面因为自己对网络编程不够熟悉,比如不清楚-1什么意思。另一方面Java NIO的API还是略显难用。

Java NIO 读数据处理过程的更多相关文章

  1. Java NIO通信的基础,基于TCP C/S例子介绍

    为了更好的理解Netty异步事件驱动网络通信框架,有必要先了解一点Java NIO原生的通信理论,下面将结合基于TCP的例子程序,含客户端和服务端的源码,实现了Echo流程. Java NIO的核心概 ...

  2. 【JAVA NIO】java NIO

    本文是博主深入学习Netty前的一些铺垫,之前只是使用Netty,用的很粗暴,导包,上网找个DEMO就直接用,对Netty中的组件了解并不深入. 于是再此总结下基础,并对一些核心组件作如下记录: 1. ...

  3. Java NIO中的读和写

    一.概述 读和写是I/O的基本过程.从一个通道中读取只需创建一个缓冲区,然后让通道将数据读到这个缓冲区.写入的过程是创建一个缓冲区,用数据填充它,然后让通道用这些数据来执行写入操作. 二.从文件中读取 ...

  4. 史上最强Java NIO入门:担心从入门到放弃的,请读这篇!

    本文原题“<NIO 入门>,作者为“Gregory M. Travis”,他是<JDK 1.4 Tutorial>等书籍的作者. 1.引言 Java NIO是Java 1.4版 ...

  5. 源码分析netty服务器创建过程vs java nio服务器创建

    1.Java NIO服务端创建 首先,我们通过一个时序图来看下如何创建一个NIO服务端并启动监听,接收多个客户端的连接,进行消息的异步读写. 示例代码(参考文献[2]): import java.io ...

  6. scala文件读取报错“java.nio.charset.MalformedInputException: Input length = 1”

    今天写spark程序的时候遇到了一个问题就是,读取文件的时候报了一个错:“Exception in thread "main" java.nio.charset.Malformed ...

  7. java nio 写一个完整的http服务器 支持文件上传 chunk传输 gzip 压缩 使用过程 和servlet差不多

    java nio 写一个完整的http服务器  支持文件上传   chunk传输    gzip 压缩      也仿照着 netty处理了NIO的空轮询BUG        本项目并不复杂 代码不多 ...

  8. Java NIO (转)

    Java NIO提供了与标准IO不同的IO工作方式: Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(B ...

  9. Java - NIO

    java.nio:NIO-2: NIO 面向流的IO体系一次只能处理一个或多个字节/字符,直至读取所有字节/符,且流中的数据不能前后移动.效率低,当数据源中没有数据时会阻塞线程.Java-4提供的新A ...

随机推荐

  1. C++类型转化分析(1)

    仔细想想地位卑贱的类型转换功能(cast),其在程序设计中的地位就象goto语句一样令人鄙视.但是它还不是无法令人忍受,因为当在某些紧要的关头,类型转换还是必需的,这时它是一个必需品. 不过C风格的类 ...

  2. sql语句 当前时间查找重复 时间戳转换

    查找重复数据 select id, name, memo from A ) >= ) mysql 当前时间 SELECT NOW(); //2015-10-27 16:43:45 UNIX时间戳 ...

  3. 批处理快速创建wifi

    为什么要用cmd这种古老的东西创建wifi呢,电脑管家.360安全卫士都有这种插件,一键开启关闭,多方便啊! 开始用的也是电脑管家的免费wifi插件,但是我越来越不能忍它极慢的启动关闭过程,每一次看着 ...

  4. 应用github pages创建自己的个人博客

    首先你需要注册自己的github账号 1.登录或者注册github,登录之后点击右上角的“+”号,选择“New repository”菜单,创建仓库,用于存储和博客相关的源文件. 2.跳转页面将填写域 ...

  5. crucible3.x +fisheye3.x 安装和破解

    2015-11-24 22:29 423人阅读 评论(1) 收藏 举报  分类: linux(1)  版权声明:本文为博主原创文章,未经博主允许不得转载. 破解文件下载路径:http://downlo ...

  6. php 连接主从数据库

    本代码是从uchome的代码修改的,是因为要解决uchome的效率而处理的.这个思维其实很久就有了,只是一直没有去做,相信也有人有同样的想法,如果有类似的,那真的希望提出相关的建议.封装的方式比较简单 ...

  7. angularjs select 循环中出现第一个 option 为空格问题

    当select 的ng-module 为空时, select显示空白行. 解决:指定ng-module的默认值.

  8. php--城市分类

    效果图:

  9. mysql integer size 大小

    I was always wondering what the size of numeric columns in MySQL was. Forgive me if this is obvious ...

  10. Selenium2学习-035-WebUI自动化实战实例-033-页面快照截图应用之三 -- 区域截图(专业版)

    之前有写过两篇博文讲述了 WebUI 自动化测试脚本中常用的截图方法,敬请参阅如下所示链接: 浏览器显示区域截图 浏览器指定区域截图 那么当需要截取的区域不在浏览器显示窗口范围之内时,之前的方法显然无 ...