3种下载程序的比较为何使用NIO进行异步网络通讯.docx
- 文档编号:4381978
- 上传时间:2022-12-01
- 格式:DOCX
- 页数:22
- 大小:44.16KB
3种下载程序的比较为何使用NIO进行异步网络通讯.docx
《3种下载程序的比较为何使用NIO进行异步网络通讯.docx》由会员分享,可在线阅读,更多相关《3种下载程序的比较为何使用NIO进行异步网络通讯.docx(22页珍藏版)》请在冰豆网上搜索。
3种下载程序的比较为何使用NIO进行异步网络通讯
3种下载文件程序的思考,为何使用NIO进行异步网络通讯
刘岩
Email:
suhuanzheng7784877@
1.前言
现在很多做网络通讯中间代理层的通讯都是使用Java1.4以后推出的NIO进行编写,现在还有很多开源的框架也是封装了NIO的书写细节来帮助大家简写异步非阻塞通讯服务。
像MySql的代理中间件amoeba-mysql-proxy就是采用NIO的方式处理client端过来的request,之后与Mysql-Server层的通讯也是采用NIO进行命令消息发送的。
再看咱们JavaEye首页介绍的项目xmemcached,其中作者Dennis是其xmemcached的开发人,他也是通过NIO的方式与memcached的Server进行异步通讯的,Dennis的另一个项目yanf4j就是一个NIO框架,xmemcache也是借助这个NIO框架实现的异步非阻塞方式的网络通讯,Apache的MINA框架都是NIO的封装再实现。
那么我们就来回顾一下以前的处理方式,来看看为什么现在要使用NIO来进行异步非阻塞方式的通讯吧,网上很多文章都是几句话将NIO和原始的socket通讯的优劣一带而过,我们这次用一个简单的下载大文件的网络服务程序进行说明。
使用3种模式来说明,分别是同步单独线程服务运行模式、传统阻塞多线程模式、使用NIO异步非阻塞模式。
我们设置服务器上有一个1.81GB的电影,格式为RMVB。
使用Server进行服务监听,客户端请求到Server,建立网络通讯,进行电影下载。
2.同步单线程阻塞
使用同步单线程下载,是最原始的socket通讯,服务端的代码如下
packageserver;
importjava.io.FileInputStream;
importjava.io.InputStream;
importjava.io.OutputStream;
import.ServerSocket;
import.Socket;
/**
*liuyan
*/
publicclassFilmServer{
publicstaticvoidmain(String[]args){
FilmServerms=newFilmServer();
try{
ms.server();
}catch(Exceptione){
e.printStackTrace();
}
}
/**
*服务器端响应请求
*
*@throwsException
*/
publicvoidserver()throwsException{
//0.建立服务器端的server的socket
ServerSocketss=newServerSocket(9999);
while(true){
//1.打开socket连接
//等待客户端的请求
finalSocketserver=ss.accept();
System.out.println("服务-----------请求开始start");
//2.打开socket的流信息,准备下面的操作
finalInputStreamis=server.getInputStream();
byteb[]=newbyte[1024];
intreadCount=is.read(b);
Stringstr=newString(b);
str=str.trim();
finalStringserverFileName=str;
//3.对流信息进行读写操作
System.out.println("客户端传过来的信息是:
"+str);
System.out.println("线程"+Thread.currentThread().getName()+"启动");
try{
FileInputStreamfileInputStream=newFileInputStream(
serverFileName);
//3.1服务器回复客户端信息(response)
OutputStreamos=server.getOutputStream();
byte[]bfile=newbyte[1024];
//往客户端写
while(fileInputStream.read(bfile)>0){
os.write(bfile);
}
fileInputStream.close();
os.close();
//4.关闭socket
//先关闭输入流
is.close();
//最后关闭socket
server.close();
}catch(Exceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
System.out.println("服务-----------请求结束over");
}
}
}
服务端这么写代码会有什么问题?
咱们先来看客户端代码,之后运行后就知道了。
packageclient;
importjava.io.FileOutputStream;
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.OutputStream;
import.Socket;
import.UnknownHostException;
/**
*liuyan
*@version1.0
*/
publicclassFilmClient{
publicstaticvoidmain(String[]args){
for(inti=1;i<=2;i++){
Clientclient=newClient();
client.i=i;
client.start();
}
}
}
classClientextendsThread{
inti;
@Override
publicvoidrun(){
//1.建立scoket连接
Socketclient;
try{
client=newSocket("127.0.0.1",9999);
//2.打开socket的流信息,准备下面的操作
OutputStreamos=client.getOutputStream();
//3.写信息
os.write(("d:
//film//2.rmvb").getBytes());
StringfilmName="c:
//io"+i+".rmvb";
FileOutputStreamfileOutputStream=newFileOutputStream(filmName);
//3.1接收服务器端的反馈
InputStreamis=client.getInputStream();
byteb[]=newbyte[1024];
while(is.read(b)>0){
fileOutputStream.write(b);
}
//4.关闭socket
//先关闭输出流
os.close();
//最后关闭socket
client.close();
}catch(UnknownHostExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}catch(IOExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
}
客户端启动了2个线程进行下载电影的工作,先启动服务端,再运行客户端,会看笔者本地的硬盘C分区到有如下效果。
可以看到线程2的下载任务一直是0字节,等第一个线程下载完成后呢,线程2的下载任务才能进行。
服务端的代码造成的问题就是使用传统的sokect网络通讯,那么另一个客户端的线程请求到server端的时候就发生了阻塞的情况,也就是说,服务端相当一个厕所,厕所就有只有一个坑位,来了一个人,相当于客户端请求,那这个人相当于就把坑位给占了,write操作和read操作会阻塞,这个人还没解决完问题呢,下个人就来了,没办法,哥们儿先在门外等等啊,等前一个客户爽完了再给您提供服务好吧。
那么如何解决这个占着坑位不让别人用的情况呢?
3.阻塞的多线程
为了解决以上问题,那么之后很多Server肯定不可能像以上程序那么做,不过以前很多Server都是基于单线程服务改造一下,做成多线程的Server的通讯,修改一下上面的Server代码,如下
packageserver;
importjava.io.FileInputStream;
importjava.io.InputStream;
importjava.io.OutputStream;
import.ServerSocket;
import.Socket;
/**
*
*/
publicclassFilmServerNewThread{
publicstaticvoidmain(String[]args){
FilmServerNewThreadms=newFilmServerNewThread();
try{
ms.server();
}catch(Exceptione){
e.printStackTrace();
}
}
/**
*服务器端响应请求
*
*@throwsException
*/
publicvoidserver()throwsException{
//0.建立服务器端的server的socket
ServerSocketss=newServerSocket(9999);
while(true){
//1.打开socket连接
//等待客户端的请求
finalSocketserver=ss.accept();
System.out.println("服务-----------请求开始start");
//2.打开socket的流信息,准备下面的操作
finalInputStreamis=server.getInputStream();
byteb[]=newbyte[1024];
intreadCount=is.read(b);
Stringstr=newString(b);
str=str.trim();
finalStringserverFileName=str;
//3.对流信息进行读写操作
System.out.println("客户端传过来的信息是:
"+str);
if(readCount>0){
newThread(){
@Override
publicvoidrun(){
System.out.println("线程"
+Thread.currentThread().getName()+"启动");
try{
FileInputStreamfileInputStream=newFileInputStream(
serverFileName);
//3.1服务器回复客户端信息(response)
OutputStreamos=server.getOutputStream();
byte[]bfile=newbyte[1024];
//往客户端写
while(fileInputStream.read(bfile)>0){
os.write(bfile);
}
fileInputStream.close();
os.close();
//4.关闭socket
//先关闭输入流
is.close();
//最后关闭socket
server.close();
}catch(Exceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
}.start();
}
System.out.println("服务-----------请求结束over");
}
}
}
以上的Server就是在原始的socket基础上加了线程,每一个Client请求过来后,整个Server主线程不必处于阻塞状态,接收请求后直接另起一个新的线程来处理和客户端的交互,就是往客户端发送二进制包。
这个在新线程中虽然阻塞,但是对于服务主线程没有阻塞的影响,主线程依然通过死循环监听着客户端的一举一动。
另一个客户端的线程发起请求后就再起一个新的线程对象去为客户端服务。
执行效果如下
2个线程互不影响,各自下载各自的。
当然从非常严格的意义来讲,str变量在十分高并发的情况下有线程安全问题,这个咱暂且忽略,就着眼于低并发的情况。
这个问题是什么呢,就是如果客户端请求比较多了,那么为每一个客户端开辟一个新的线程对象来处理网络传输的请求,需要创建个线程对象,而且这个线程对象从时间上来讲还是处于长连接,这个就比较消费系统资源,这个打开进程管理器就可以看到。
而且每一个线程内部都是阻塞的,也没有说完全利用好这个新创建的线程。
还拿刚才上厕所举例子,好比现在不止一个坑位了,来了一个用户我这边就按照工程师的厕所坑位图建立一个新的坑位,客户来了,不用等待老坑位,用新创建的坑位就行了。
等那个老坑位用完了,自然有垃圾回收器去消灭那个一次性的坑位的,腾出资源位置为了建立新的坑位。
长时间连接的意思,相当于这个人上厕所的时间非常长,便秘?
?
需要拉一天才能爽完……老的坑位一时半会儿回收不了,新的坑位需要有空间为其建造茅房以便满足客户端的“急切方便”需要。
久而久之,线程数目一多,系统就挂了的概率就增多了(谁也别想上,全玩完了)。
4.异步非阻塞
使用JDK1.4的NIO可以适当的解决上面的问题,异步I/O是一种没有阻塞地读写数据的方法。
通常,在代码进行read()调用时,代码会阻塞直至有可供读取的数据。
同样,write()调用将会阻塞直至数据能够写入。
异步I/O调用不会阻塞。
相反,您将注册对特定I/O事件的兴趣―可读的数据的到达、新的套接字连接,等等,而在发生这样的事件时,系统将会告诉您。
异步I/O的一个优势在于,它允许您同时根据大量的输入和输出执行I/O。
同步程序常常要求助于轮询,或者创建许许多多的线程以处理大量的连接。
使用异步I/O,您可以监听任何数量的通道上的事件,不用轮询,也不用额外的线程。
还是举上公共厕所例子,虽然这个例子有点臭臭的。
您现在有“便便”的需求了,不用那么麻烦,看看公共厕所是否有人占领,也不用给您另起个新坑位,您就拿一根我们服务端定制的容器和一个很粗管子,这个坐便器的大小因您那个地方的尺寸而定,坐便器往您的那个地方一放,再将坐便器和管子一连接,OK,您就敞开了“爽”吧。
不用担心,这个管子自然会连接到相应的肥料厂家,将您的排泄物有效回收加以利用的。
您完了事,擦擦屁股,关上管子该干嘛还干嘛就行了。
另一个人也有这个需求,没问题,每个要我们提供服务的人都用这根管子,和自己的坐便器就行了,管子很粗,谁来连这个管子都行,有多少都行啊。
下面我们来看基于NIO的网络下载程序
packageserver;
importjava.io.FileInputStream;
importjava.io.IOException;
import.InetSocketAddress;
importjava.nio.ByteBuffer;
importjava.nio.CharBuffer;
importjava.nio.channels.FileChannel;
importjava.nio.channels.SelectionKey;
importjava.nio.channels.Selector;
importjava.nio.channels.ServerSocketChannel;
importjava.nio.channels.SocketChannel;
importjava.nio.charset.Charset;
importjava.nio.charset.CharsetDecoder;
importjava.util.Iterator;
/**
*
*@authorliuyan
*
*/
publicclassNIOServer{
staticintBLOCK=500*1024;
/**
*处理客户端的内部类,专门负责处理与用户的交互
*/
publicclassHandleClient{
protectedFileChannelchannel;
protectedByteBufferbuffer;
StringfilePath;
/**
*构造函数,文件的管道初始化
*@paramfilePath
*@throwsIOException
*/
publicHandleClient(StringfilePath)throwsIOException{
//文件的管道
this.channel=newFileInputStream(filePath).getChannel();
//建立缓存
this.buffer=ByteBuffer.allocate(BLOCK);
this.filePath=filePath;
}
/**
*读取文件管道中数据到缓存中
*@return
*/
publicByteBufferreadBlock(){
try{
//清除缓冲区的内容,posistion设置为0,limit设置为缓冲的容量大小capacity
buffer.clear();
//读取
intcount=channel.read(buffer);
//将缓存的中的posistion设置为0,将缓存中的limit设置在原始posistion位置上
buffer.flip();
if(count<=0)
returnnull;
}catch(IOExceptione){
e.printStackTrace();
}
returnbuffer;
}
/**
*关闭服务端的文件管道
*/
publicvoidclose(){
try{
channel.close();
}catch(IOExceptione){
e.printStackTrace();
}
}
}
protectedSelectorselector;
protectedStringfilename="d:
\\film\\60.rmvb";//abigfile
protectedByteBufferclientBuffer=ByteBuffer.allocate(BLOCK);
protectedCharsetDecoderdecoder;
//构造服务端口,服务管道等等
publicNIOServer(intport)throwsIOException{
selector=this.getSelector(port);
Charsetcharset=Charset.forName("GB2312");
decoder=charset.newDecoder();
}
//获取Selector
//构造服务端口,服务管道等等
protectedSelectorgetSelector(intport)throwsIOException{
ServerSocketChannelserver=ServerSocketChannel.open();
Selectorsel=Selector.open();
server.socket().bind(newInetSocketAddress(port));
server.configureBlocking(false);
//刚开始就注册链接事件
server.register(sel,SelectionKey.OP_ACCEPT);
returnsel;
}
//服务启动的开始入口
publicvoidlisten(){
try{
for(;;){
//?
selector.select();
Iterator
.iterator();
while(iter.hasNext()){//首先是最先感兴趣的连接事件
SelectionK
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 下载 程序 比较 为何 使用 NIO 进行 异步 网络通讯