这两天仿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. CentOS上安装Node.js

    CentOS上安装Node.js [日期:2014-07-21] 来源:Linux社区  作者:maskdfe [字体:大 中 小]     CentOS上安装Node.js(想在Linux上学习No ...

  2. Xamarin迁移到 Unified API 注意事项

    参考官方文档: Migrating to Unified API for Components #if __UNIFIED__ ... // Mappings Unified CoreGraphic ...

  3. 关于C语言读取多行数据的问题

    我有如下数据格式3360  2001     5   1750.5   1246.5  22.5  n    1775=1177-1316:13360  2001     5   1750.5   1 ...

  4. 第四章 跨平台图像显示库——SDL 第一节 与SDL第一次亲密接触

    http://blog.csdn.net/visioncat/article/details/1596576 GCC for Win32 开发环境介绍(5) 第四章 跨平台图像显示库——SDL 第一节 ...

  5. ngrok的使用

    windows的ngrok配置: 步骤一:下载ngrok http://pan.baidu.com/s/1jH0s8o2 步骤二:如果你的国外网址没被墙就直接使用cmd命令行使用. 国内ngrok配置 ...

  6. node.js学习路线图

    http://www.admin10000.com/document/4624.html

  7. 【Swing】Swing初步认知

    AWT(abstract Window Toolkit) AWT是java发布的第一个图形界面库.AWT通过调用系统本地的库,来创建和显示控件. AWT 的组件都是使用的native code来编写的 ...

  8. xftp的使用

    1.xftp 一个基于 MS windows 平台的功能强大的SFTP.FTP 文件传输软件 2.下载安装 *3.在linux上安装服务 sudo yum install vsftp

  9. Linq&Lumda---LINQ to DataSet的DataTable操作

    1. DataTable读取列表 DataSet ds = new DataSet();// 省略ds的Fill代码DataTable products = ds.Tables["Produ ...

  10. Shell 字符串常见操作

    参考文章:http://blog.csdn.net/chen_jp/article/details/8922582 一 字符替换 origin=原字符串  str=替换后的字符串 替换命令: str= ...