这两天仿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. Ecplise + Xdebug 一波三折终于能单步调试了

    http://my.oschina.net/012345678/blog/152889 Ecplise + Xdebug 一波三折终于能单步调试了 发表于2年前(2013-08-15 15:50)   ...

  2. [转]理解OAuth 2.0

    作者: 阮一峰 OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版. 本文对OAuth 2.0的设计思路和运行流程,做一个简明通俗的解释, ...

  3. p::first-line { text-transform: uppercase }

    https://www.w3.org/TR/css3-selectors/ Note that the length of the first line depends on a number of ...

  4. mysql-insert-返回主键id

    function gen_this_insert_id($insert) { GLOBAL $link; $insert .= ' SELECT LAST_INSERT_ID();'; if (mys ...

  5. 【转】将 azw3 格式转换为 mobi 格式并保持原有排版格式

    小伙伴多次向 Kindle 伴侣提出一个问题,那就是通过 Calibre 将排版精美的 azw3 格式电子书转换成 mobi 格式后推送到 Kindle,排版格式会发生很大的变化,比如行距过窄.内嵌字 ...

  6. lisp分支

    newLISP http://www.ituring.com.cn/article/110968 clojure             http://clojure.org/             ...

  7. Bluetooth L2CAP介绍

    目录 1. 通用操作 1. L2CAP Channel 2. 设备间操作 3. 层间操作 4. 操作模式 2. 数据包格式(Data Packet Format) 1. B-Frame 2. G-Fr ...

  8. AMQP协议

    当前各种应用大量使用异步消息模型,并随之产生众多消息中间件产品及协议,标准的不一致使应用与中间件之间的耦合限制产品的选择,并增加维护成本. AMQP是一个提供统一消息服务的应用层标准协议,基于此协议的 ...

  9. Linq&Lumbda

    var y = from model in list.Where(s=>s.product==product||product=="")                    ...

  10. SQL Server批量数据导出导入BCP使用

    BCP简介 bcp是SQL Server中负责导入导出数据的一个命令行工具,它是基于DB-Library的,并且能以并行的方式高效地导入导出大批量的数据.bcp可以将数据库的表或视图直接导出,也能通过 ...