Java - 网络IO的阻塞
最近学习时碰到事件驱动和非阻塞的相关知识,
随之想到了Java中的Reactor、io与nio的一些东西;
在前辈的博客上翻了翻、复习复习,在此记录一番。
实在找不到比较大点的东西,于是随便弄了个压缩包,大小在1G左右;
写个程序模拟一下下载,开两个客户端线程请求下载;
结果会是:一个请求会一直阻塞,直到一个文件下载完成后另一个文件才开始下载。
先看看服务端的代码:
class DownLoadServer implements Runnable {
@Override
public void run() {
try {
@SuppressWarnings("resource")
final ServerSocket ss = new ServerSocket(8989);
while (true) {
Socket server = ss.accept();
byte[] bfile = new byte[1024];
try {
FileInputStream fis = new FileInputStream("D:/doc_backup.rar");
OutputStream os = server.getOutputStream();
while (fis.read(bfile) > -1) {
os.write(bfile);
}
fis.close();
server.close();
} catch (IOException e) {
System.out.println("server线程输出流我的天");
}
}
} catch (Exception e) {
System.out.println("server线程 我的天~");
}
}
}
很简单,就是accept后开个inputStream和outputStream,边读边写。
接着再看看客户端的代码:
class DownlLoadClient implements Runnable {
@SuppressWarnings("resource")
@Override
public void run() {
try {
Socket client = new Socket("127.0.0.1", 8989);
InputStream is = client.getInputStream();
FileOutputStream fos = new FileOutputStream(
"E:/testfolder/langchao" + Thread.currentThread().getId()
+ ".txt");
byte[] fromServer = new byte[1024];
while (is.read(fromServer) > -1) {
fos.write(fromServer);
}
client.close();
} catch (IOException e) {
System.out.println("client线程我的天~");
}
}
}
输出的文件名是随便取的,也没什么特别,只是把读过来的输出去。
结果当然是这个样子的:
服务端只有一对inputStream和outputStream对象在受理请求,前面的没写完后面的别想写。
那如果有很多inputStream和outputStream对象受理请求呢?
想法不错,也就是说把服务端代码改成这样子:
class DownLoadServer implements Runnable {
@Override
public void run() {
try {
@SuppressWarnings("resource")
final ServerSocket ss = new ServerSocket(8989);
while (true) {
final Socket server = ss.accept();
Thread t = new Thread() {
@Override
public void run() {
super.run();
byte[] bfile = new byte[1024];
try {
FileInputStream fis = new FileInputStream("D:/doc_backup.rar");
OutputStream os = server.getOutputStream();
while (fis.read(bfile) > -1) {
os.write(bfile);
}
fis.close();
server.close();
} catch (IOException e) {
System.out.println("server线程输出流我的天");
}
}
};
t.start();
}
} catch (Exception e) {
System.out.println("server线程 我的天~");
}
}
}
大概就是这个意思,每accept到就为客户端提供"一对一特殊服务";
嗯,或者也可以算一下获取了多少下载请求,每N次请求开1次"特殊服务"。
但无论如何都无法回避一个问题——"特殊服务"的成本很高,线程的切换和线程的资源都是开销。
如果继续按照这个方法做下去,也只能是弄个Thread Pool。
但如果请求数量超过了pool的maxActive数量,那问题又饶了一圈回来了。
我们追求低成本高效率,于是早在JDK1.4就有了java.nio;
nio怎么讲?有说是new io的、也有叫native io,或许叫non-block io...
概念上也就是channel、buffer、selector、selectionKey...
先看一下server代码:
System.out.println("server start...");
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8989));
serverChannel.configureBlocking(false);
Selector sel = Selector.open();
serverChannel.register(sel, SelectionKey.OP_ACCEPT);
File file = new File("D:/doc_backup.rar");
ByteBuffer buffer = ByteBuffer.allocate(100*1024);
CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
while(true){
sel.select();
Iterator selKeyItr = sel.selectedKeys().iterator();
while(selKeyItr.hasNext()){
SelectionKey key = selKeyItr.next();
selKeyItr.remove();
String outputFilePath=StringUtils.EMPTY;
if(key.isAcceptable()){
System.out.println("server acceptable");
SocketChannel channel = ((ServerSocketChannel)key.channel()).accept();
channel.configureBlocking(false);
channel.register(sel, SelectionKey.OP_READ);
}else if(key.isReadable()){
System.out.println("server readable");
SocketChannel channel = (SocketChannel) key.channel();
channel.configureBlocking(false);
channel.read(buffer);
buffer.flip();
CharBuffer clientBuffer = decoder.decode(buffer);
outputFilePath = clientBuffer.toString();
buffer.clear();
SelectionKey writeKey = channel.register(sel, SelectionKey.OP_WRITE);
}else if(key.isWritable()){
System.out.println("server writable");
SocketChannel channel =(SocketChannel) key.channel();
FileChannel fileChannel = new FileInputStream(file).getChannel();
ByteBuffer fileByte = ByteBuffer.allocate(1024*100);
while(fileChannel.read(fileByte)!=-1){
fileByte.flip();
channel.write(fileByte);
fileByte.clear();
}
channel.register(sel, SelectionKey.OP_READ);
}
}
}
代码贴出来有点乱,但也就开一个线程,监听与注册事件。
select()方法必须,不然client的send根本recv不到。
socketChannel将blocking设置为false,不然会在事件注册时出现java.nio.channels.IllegalBlockingModeException
Unchecked exception thrown when a blocking-mode-specific operation is invoked upon a channel in the incorrect blocking mode.
同样地,在write事件中把blocking设置为true或者使用阻塞的面向流的IO也会出现同样的异常。
client继承Thread,run method如下:
public void run() {
try {
System.out.println("client...");
SocketAddress addr = new InetSocketAddress(8989);
SocketChannel client = SocketChannel.open();
client.configureBlocking(false);
Selector sel = Selector.open();
client.register(sel, SelectionKey.OP_CONNECT);
CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
client.connect(addr);
while (true) {
sel.select();
Iterator selKeyItr = sel.selectedKeys().iterator();
while (selKeyItr.hasNext()) {
SelectionKey key = selKeyItr.next();
selKeyItr.remove();
if (key.isConnectable()) {
System.out.println("client connectble");
SocketChannel channel = (SocketChannel) key.channel();
String filePath = "E:/testfolder/channelTest"+Thread.currentThread().getId()+".rar";
channel.finishConnect();
channel.write(encoder.encode(CharBuffer.wrap(filePath)));
channel.register(sel, SelectionKey.OP_READ).attach(filePath);
} else if (key.isReadable()) {
System.out.println("client readble...");
SocketChannel channel = (SocketChannel) key.channel();
if(key.attachment()!=null){
@SuppressWarnings("resource")
FileChannel fc = new FileOutputStream(key.attachment().toString()).getChannel();
ByteBuffer fileByte = ByteBuffer.allocate(1024*100);
while(channel.read(fileByte)!=-1){
fileByte.flip();
fc.write(fileByte);
fileByte.clear();
}
}
channel.register(sel, SelectionKey.OP_CONNECT);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
在上面代码中的attach()并没有发挥太大用处,attach()可以为selectionKey对象添加任何一个object。
但仅限一个,若没添加,attachment()会取出null。
运行后发现事件都获取到了,但文件仍然是一个接一个的下载。
原因是server触发write事件后创建fileChannel并一次写完。
事件响应的执行体太大,影响后面的执行。
非阻塞嘛,要得就是立即返回。
解决方法是分多次事件去读写,每次事件继续读写上一次事件的缓冲。
我可以好好使用一下这个attach()了。
首先我加了一个resolver类,我打算把他的实例加到attachment中去:
class ChannelResolver{
private FileChannel channel;
private ByteBuffer buffer;
private FileInputStream fis;
public ChannelResolver(String filePath){
try {
this.fis = new FileInputStream(filePath);
this.channel = this.fis.getChannel();
buffer = ByteBuffer.allocate(1024*100);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
ByteBuffer readInto(){
try {
buffer.clear();
int i = channel.read(buffer);
buffer.flip();
if(i<0){
return null;
}
} catch (IOException e) {
e.printStackTrace();
}
return buffer;
}
}
将channel注册write事件后在return的selectionKey上attach一个实例。
然后在write事件中获取attachment进行读写:
public void run() {
System.out.println("server start...");
ServerSocketChannel serverChannel;
try {
serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8989));
serverChannel.configureBlocking(false);
Selector sel = Selector.open();
serverChannel.register(sel, SelectionKey.OP_ACCEPT);
ByteBuffer buffer = ByteBuffer.allocate(100*1024);
CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
while(true){
sel.select();
Iterator selKeyItr = sel.selectedKeys().iterator();
while(selKeyItr.hasNext()){
SelectionKey key = selKeyItr.next();
selKeyItr.remove();
if(key.isAcceptable()){
System.out.println("server acceptable");
SocketChannel channel = ((ServerSocketChannel)key.channel()).accept();
channel.configureBlocking(false);
channel.register(sel, SelectionKey.OP_READ);
}else if(key.isReadable()){
System.out.println("server readable"+Thread.currentThread().getName());
SocketChannel channel = (SocketChannel) key.channel();
if(channel.read(buffer)>0){
buffer.flip();
CharBuffer clientBuffer = decoder.decode(buffer);
System.out.println("from client::"+clientBuffer.toString());
buffer.clear();
}
channel.register(sel, SelectionKey.OP_WRITE).attach(new ChannelResolver("D:/doc_backup.rar"));
}else if(key.isWritable()){
SocketChannel channel =(SocketChannel) key.channel();
if(key.attachment()!=null){
ChannelResolver resolver = (ChannelResolver)key.attachment();
buffer = resolver.readInto();
if(buffer!=null){
channel.write(buffer);
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
在handler的readInto()中已经进行了flip(),在这里就不用再flip()了。
相应地,client的读取也要改一下:
public void run() {
try {
System.out.println("client...");
SocketAddress addr = new InetSocketAddress(8989);
SocketChannel client = SocketChannel.open();
client.configureBlocking(false);
Selector sel = Selector.open();
client.register(sel, SelectionKey.OP_CONNECT);
CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
ByteBuffer buffer= ByteBuffer.allocate(1024*500);
client.connect(addr);
while (true) {
sel.select();
Iterator selKeyItr = sel.selectedKeys().iterator();
while (selKeyItr.hasNext()) {
SelectionKey key = selKeyItr.next();
selKeyItr.remove();
if (key.isConnectable()) {
System.out.println("client connectble");
SocketChannel channel = (SocketChannel) key.channel();
channel.configureBlocking(false);
channel.finishConnect();
channel.write(encoder.encode(CharBuffer.wrap("start download")));
channel.register(sel, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
if(channel.read(buffer)>0){
buffer.flip();
fc.write(buffer);
buffer.clear();
}else{
channel.close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
下面引用书本上的一段话:
[是基于事件驱动思想的,实现上通常采用Reactor模式,从程序角度而言,当发起IO的读写操作时,是非阻塞的;当socket有流可读或可写入socket时,操作系统会相应地通知应用程序进行处理,应用再将流读取到缓冲区或写入操作系统。对于网络IO而言,主要有连接建立、流读取和流写入三种事件。
AIO同样基于事件驱动思想,实现上通常采用Proactor模式。从程序角度而言,和NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。
较之NIO而言,AIO一方面简化了程序的编写,流的读取和写入都由操作系统来代替完成;另一方面省去了NIO中程序要遍历事件通知队列(selector)的代价。windows基于iocp、Linux基于epoll。]
Java - 网络IO的阻塞的更多相关文章
- 网络IO之阻塞、非阻塞、同步、异步总结
网络IO之阻塞.非阻塞.同步.异步总结 1.前言 在网络编程中,阻塞.非阻塞.同步.异步经常被提到.unix网络编程第一卷第六章专门讨论五种不同的IO模型,Stevens讲的非常详细,我记得去年看第一 ...
- 网络IO之阻塞、非阻塞、同步、异步总结【转】
1.前言 在网络编程中,阻塞.非阻塞.同步.异步经常被提到.unix网络编程第一卷第六章专门讨论五种不同的IO模型,Stevens讲的非常详细,我记得去年看第一遍时候,似懂非懂,没有深入理解.网上有详 ...
- Java 网络 IO 模型
在进入主题之前先看个 Java 网络编程的一个简单例子:代码很简单,客户端和服务端进行通信,对于客户端的每次输入,服务端回复 get.注意,服务端可以同时允许多个客户端连接. 服务端端代码: // 创 ...
- Java网络编程 -- BIO 阻塞式网络编程
阻塞IO的含义 阻塞(blocking)IO :阻塞是指结果返回之前,线程会被挂起,函数只有在得到结果之后(或超时)才会返回 非阻塞(non-blocking)IO :非阻塞和阻塞的概念相对应,指在不 ...
- 通过实例理解Java网络IO模型
网络IO模型及分类 网络IO模型是一个经常被提到的问题,不同的书或者博客说法可能都不一样,所以没必要死抠字眼,关键在于理解. Socket连接 不管是什么模型,所使用的socket连接都是一样的. 以 ...
- (转)Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码)
原文出自:http://blog.csdn.net/anxpp/article/details/51512200 1.BIO编程 1.1.传统的BIO编程 网络编程的基本模型是C/S模型,即两个进程间 ...
- Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码)
本文会从传统的BIO到NIO再到AIO自浅至深介绍,并附上完整的代码讲解. 下面代码中会使用这样一个例子:客户端发送一段算式的字符串到服务器,服务器计算后返回结果到客户端. 代码的所有说明,都直接作为 ...
- Java 网络IO编程(BIO、NIO、AIO)
本概念 BIO编程 传统的BIO编程 代码示例: public class Server { final static int PROT = 8765; public static void main ...
- 多路复用 阻塞/非阻塞IO模型 网络IO两个阶段
1.网络IO的两个阶段 waitdata copydata send 先经历:copydata阶段 recv 先经历:waitdata阶段 再经历 copydata阶段 2.阻塞的IO模型 之前写的都 ...
随机推荐
- 使用InstallUtil安装或卸载服务
使用InstallUtil安装或卸载服务 一.安装服务: C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe D:\MyServ ...
- cobbler PXE 安装系统时出现的问题
cobbler PXE 安装系统时出现的问题 1.安装包未找到.解决方法:ios镜像里没有软件包,换掉该软件包的ios镜像,或者在ks.cfg 文件里删去或注释掉%packages 里指定安装的软件包 ...
- iOS应用开发权限请求处理
1.写在前面 APP开发避免不开系统权限的问题,如何在APP以更加友好的方式向用户展示系统权限,似乎也是开发过程中值得深思的一件事: 那如何提高APP获取iOS系统权限的通过率呢?有以下几种方式: 1 ...
- javascript设为首页、加入收藏
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- kvm虚拟化之kvm虚拟机vnc配置
本文是通过vnc方式访问虚拟主机上的KVM虚拟机. 这里的通过vnc方式访问虚拟机不是在kvm虚拟机安装配置vnc服务器,通过虚拟主机的IP地址与端口进行访问,kvm虚拟化对vnc的支持相对来说 ...
- Vim查找与替换
\c 忽略大小写 \C 强制区分大小写 \v 除了_.字母.数字以为的所有字符都当做具有特殊含义的字符 \V 只有反斜杠有特殊含义 %s///gn 统计某个词出现的次数 替换的flag g 全局范围执 ...
- CentOS6.9 ARM虚拟机扩容系统磁盘
由于扩容磁盘的操作非同小可,一旦哪一步出现问题,就会导致分区损坏,数据丢失等一系列严重的问题,因此建议:在进行虚拟机分区扩容之前,一定要备份重要数据文件,并且先在测试机上验证以下步骤,再应用于您的生产 ...
- 使用私有git仓库备份服务器脚本和配置文件
1. 创建私有git仓库 服务器端配置: # 安装 git yum -y install git # 创建 git 用户 useradd git # 创建私有仓库数据存储目录 mkdir /git_b ...
- SimpleITK学习(二)图像读取
通常我会用simpleitk来读取dicom文件,主要是为了将dicom文件转换为numpy矩阵,便于输入神经网络,读取dicom文件可分为两种情况,一.单独的dicom文件 二.一系列dicom文件 ...
- [八分之三的男人] POJ - 1741 点分治 && 点分治笔记
题意:给出一棵带边权树,询问有多少点对的距离小于等于\(k\) 本题解参考lyd的算法竞赛进阶指南,讲解的十分清晰,比网上那些讲的乱七八糟的好多了 不过写起来还是困难重重(史诗巨作 打完多校更详细做法 ...