这两天仿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. 关于0x000000c2问题解决方法

    首先大家要用进入PE系统,按照我说的顺序做就可以解决了! 1,进入PE后找到系统盘,默认一般为C盘-->进入WINDOWS文件夹-->进入system32文件夹-->找到regedt ...

  2. JQ+rotate插件实现图片旋转,兼容IE7+ \ CHROME等浏览器

    插件:/jquery.rotate.min.js CODE: <!DOCTYPE html> <html> <head> <meta charset=&quo ...

  3. jQuery Questions:Front-end Developer Interview Questions

    Explain "chaining". Chaining allows us to run multiple jQuery methods (on the same element ...

  4. mysqli_multi_query($link, $wsql)

    if (mysqli_multi_query($link, $wsql)) { do { if ($result = mysqli_store_result($link)) { mysqli_free ...

  5. prior knowledge

    https://en.wikipedia.org/wiki/Bayes'_theorem For example, if cancer is related to age, then, using B ...

  6. 正则基础之 \b 单词边界

    本文转载自: http://www.jb51.net/article/19330.htm 1概述 “\b”匹配单词边界,不匹配任何字符. “\b”匹配的只是一个位置,这个位置的一侧是构成单词的字符,另 ...

  7. PowerDesigner连接MySQL,建立逆向工程图解

    传说中,程序员们喜欢用powerDesign进行数据库建模.通常都是先设计出物理模型图,在转换出数据库需要的SQL语句,从而生成数据库.但,江湖中流传着"powerDesign逆向工程&qu ...

  8. 【故障处理】ORA-12162: TNS:net service name is incorrectly specified (转)

    本文将给大家阐述一个因未设置系统环境变量ORACLE_SID导致ORA-12162错误的案例.希望大家有所思考. 1.获得有关ORA-12162报错信息的通用表述信息 [oracle@asdlabdb ...

  9. php YAF

    Yaf 的特点: 用C语言开发的PHP框架, 相比原生的PHP, 几乎不会带来额外的性能开销. 所有的框架类, 不需要编译, 在PHP启动的时候加载, 并常驻内存. 更短的内存周转周期, 提高内存利用 ...

  10. Node的Buffer

    var buf3 = new Buffer([1,2,3,4,-10,256],'utf8');//默认为utf8 console.log(buf3[0]);//正常的范围是0~255 console ...