这两天仿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. TextView实现圆角效果

    自定义一个Xml样式: <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android ...

  2. Note: RewriteCond规则

    如果文件存在,就直接访问文件,不进行下面的RewriteRule:RewriteCond %{REQUEST_FILENAME} !-f 如果目录存在,就直接访问目录,不进行下面的RewriteRul ...

  3. Windows远程桌面连接Mac OS X

    Windows远程桌面连接Mac OS X   第一步:Mac OS X 10.5 已经增加支持了由VNC Viewer访问的功能,设置如下:   系统偏好设置-共享-勾选“屏幕共享”,然后在电脑设置 ...

  4. /etc/hosts.conf

    一 作用       指定如何解析主机域名.可设置网络安全. 二 参数说明      默认情况,/etc/hosts.conf 文件有如下内容——      order hosts,bind     ...

  5. 6月辞职->帝都生活

    ---恢复内容开始--- 5月初送走了静,有点伤心,但还是忍住没哭. 纠结了一下上哪个班,上不上基础班,不能再拖了,果断交钱报6月份的ios基础班.之前还有个电话面试,怕怕的,考了很多函数的知识,好多 ...

  6. spark之combineByKey

    combineByKey def combineByKey[C](createCombiner: (V) => C, mergeValue: (C, V) => C, mergeCombi ...

  7. autowire异常的三个情况

    2010-3-11 16:06:00 net.sf.ehcache.config.ConfigurationFactory parseConfiguration 警告: No configuratio ...

  8. docker kubernetes--

    http://kubernetes.io/docs/getting-started-guides/docker/ https://hub.docker.com/r/solsson/hyperkube- ...

  9. C++控制程序只运行一个实例

    int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { ...

  10. [LeetCode] Decode Ways(DP)

    A message containing letters from A-Z is being encoded to numbers using the following mapping: 'A' - ...