JAVA NIO 实例

来源:互联网 发布:软件设计方案作用 编辑:程序博客网 时间:2024/06/07 11:01

http://blog.csdn.net/eclipser1987/article/details/7329362



 我们都知道TCP是面向连接的传输层协议,一个socket必定会有绑定一个连接,在普通的BIO(阻塞式IO)中,需要有三次握手,然后一般的socket编程就是这样的形式。

Socket服务器端流程如下:加载套接字->创建监听的套接字->绑定套接字->监听套接字->处理客户端相关请求。

Socket客户端同样需要先加载套接字,然后创建套接字,不过之后不用绑定和监听了,而是直接连接服务器,发送相关请求。

 

       他们一直就占用这个连接,如果有信息发送,那么就响应,否则就一直阻塞着。如果有多连接,那么就要使用多线程,一个线程处理一个连接,在连接还少的情况下,是允许的,但如果同时处理的连接过多比如说1000,那么在win平台上就会遇到瓶颈了如果2000,那么在linux上就遇到瓶颈了,因为在不同的平台上每一个进程能够创建的线程数是有限度的,并且过多的线程必将会引起系统对线程调度的效率问题,再怎么也要保证线程优先队列,阻塞队列;假设一千个线程,一个线程最少一兆的栈大小,对内存也是一个很大的消耗。

       总之阻塞式的IO是:一连接<一一一>一线程 

 

       然后出现了NIO,在java1.4引入了java.nio包,java new I/O。引入了操作系统中常用的缓冲区和通道等概念。

 

       缓冲区: 在操作系统中缓冲区是为了解决CPU的计算速度和外设输入输出速度不匹配的问题,因为外设太慢了,如果没有缓冲区,那么CPU在外设输入的时候就要一直等着,就会造成CPU处理效率的低下,引入了缓冲之后,外设直接把数据放到缓冲中,当数据传输完成之后,给CPU一个中断信号,通知CPU:“我的数据传完了,你自己从缓冲里面去取吧”。如果是输出也是一样的道理。

       通道: 那么通道用来做什么呢?其实从他的名字就可以看出,它就是一条通道,您想传递出去的数据被放置在缓冲区中,然后缓冲区中怎么从哪里传输出去呢?或者外设怎么把数据传输到缓冲中呢?这里就要用到通道。它可以进一步的减少CPU的干预,同时更有效率的提高整个系统的资源利用率,例如当CPU要完成一组相关的读操作时,只需要向I/O通道发送一条指令,以给出其要执行的通道程序的首地址和要访问的设备,通道执行通道程序便可以完成CPU指定的I/O任务。

      选择器: 另外一项创新是选择器,当我们使用通道的时候也许通道没有准备好,或者有了新的请求过来,或者线程遇到了阻塞,而选择器恰恰可以帮助CPU了解到这些信息,但前提是将这个通道注册到了这个选择器。

 

 

下面一个例子是我看过的一个讲述的很贴切的例子:   

一辆从 A 开往 B 的公共汽车上,路上有很多点可能会有人下车。司机不知道哪些点会有哪些人会下车,对于需要下车的人,如何处理更好?   

1. 司机过程中定时询问每个乘客是否到达目的地,若有人说到了,那么司机停车,乘客下车。 ( 类似阻塞式 )   

2. 每个人告诉售票员自己的目的地,然后睡觉,司机只和售票员交互,到了某个点由售票员通知乘客下车。 ( 类似非阻塞 )     

很显然,每个人要到达某个目的地可以认为是一个线程,司机可以认为是 CPU 。在阻塞式里面,每个线程需要不断的轮询,上下文切换,以达到找到目的地的结果。而在非阻塞方式里,每个乘客 ( 线程 ) 都在睡觉 ( 休眠 ) ,只在真正外部环境准备好了才唤醒,这样的唤醒肯定不会阻塞。  

 

 

在非阻塞式IO中实现的是:一请求<一一一>一线程

 

     下面这个例子实现了一个线程监听两个ServerSocket,只有等到请求的时候才会有处理。

Server

 

[java] view plain copy
  1. package cn.vicky.channel;  
  2.   
  3. import java.io.IOException;  
  4. import java.net.InetSocketAddress;  
  5. import java.nio.ByteBuffer;  
  6. import java.nio.channels.SelectionKey;  
  7. import java.nio.channels.Selector;  
  8. import java.nio.channels.ServerSocketChannel;  
  9. import java.nio.channels.SocketChannel;  
  10. import java.nio.channels.spi.SelectorProvider;  
  11. import java.util.Iterator;  
  12.   
  13. /**    
  14.  * TCP/IP的NIO非阻塞方式   
  15.  * 服务器端   
  16.  * */  
  17. public class Server implements Runnable {  
  18.   
  19.     //第一个端口     
  20.     private Integer port1 = 8099;  
  21.     //第二个端口     
  22.     private Integer port2 = 9099;  
  23.     //第一个服务器通道 服务A     
  24.     private ServerSocketChannel serversocket1;  
  25.     //第二个服务器通道 服务B     
  26.     private ServerSocketChannel serversocket2;  
  27.     //连接1     
  28.     private SocketChannel clientchannel1;  
  29.     //连接2     
  30.     private SocketChannel clientchannel2;  
  31.   
  32.     //选择器,主要用来监控各个通道的事件     
  33.     private Selector selector;  
  34.       
  35.     //缓冲区     
  36.     private ByteBuffer buf = ByteBuffer.allocate(512);  
  37.       
  38.     public Server() {  
  39.         init();  
  40.     }  
  41.   
  42.     /**   
  43.      * 这个method的作用 
  44.      * 1:是初始化选择器   
  45.      * 2:打开两个通道   
  46.      * 3:给通道上绑定一个socket   
  47.      * 4:将选择器注册到通道上   
  48.      * */  
  49.     public void init() {  
  50.         try {  
  51.             //创建选择器     
  52.             this.selector = SelectorProvider.provider().openSelector();  
  53.             //打开第一个服务器通道     
  54.             this.serversocket1 = ServerSocketChannel.open();  
  55.             //告诉程序现在不是阻塞方式的     
  56.             this.serversocket1.configureBlocking(false);  
  57.             //获取现在与该通道关联的套接字     
  58.             this.serversocket1.socket().bind(new InetSocketAddress("localhost"this.port1));  
  59.             //将选择器注册到通道上,返回一个选择键     
  60.             //OP_ACCEPT用于套接字接受操作的操作集位     
  61.             this.serversocket1.register(this.selector, SelectionKey.OP_ACCEPT);  
  62.   
  63.             //然后初始化第二个服务端     
  64.             this.serversocket2 = ServerSocketChannel.open();  
  65.             this.serversocket2.configureBlocking(false);  
  66.             this.serversocket2.socket().bind(new InetSocketAddress("localhost"this.port2));  
  67.             this.serversocket2.register(this.selector, SelectionKey.OP_ACCEPT);  
  68.   
  69.         } catch (Exception e) {  
  70.             e.printStackTrace();  
  71.         }  
  72.   
  73.     }  
  74.   
  75.     /**   
  76.      * 这个方法是连接   
  77.      * 客户端连接服务器   
  78.      * @throws IOException    
  79.      * */  
  80.     public void accept(SelectionKey key) throws IOException {  
  81.         ServerSocketChannel server = (ServerSocketChannel) key.channel();  
  82.         if (server.equals(serversocket1)) {  
  83.             clientchannel1 = server.accept();  
  84.             clientchannel1.configureBlocking(false);  
  85.             //OP_READ用于读取操作的操作集位     
  86.             clientchannel1.register(this.selector, SelectionKey.OP_READ);  
  87.         } else {  
  88.             clientchannel2 = server.accept();  
  89.             clientchannel2.configureBlocking(false);  
  90.             //OP_READ用于读取操作的操作集位     
  91.             clientchannel2.register(this.selector, SelectionKey.OP_READ);  
  92.         }  
  93.     }  
  94.   
  95.     /**   
  96.      * 从通道中读取数据   
  97.      * 并且判断是给那个服务通道的   
  98.      * @throws IOException    
  99.      * */  
  100.     public void read(SelectionKey key) throws IOException {  
  101.   
  102.         this.buf.clear();  
  103.         //通过选择键来找到之前注册的通道     
  104.         //但是这里注册的是ServerSocketChannel为什么会返回一个SocketChannel??     
  105.         SocketChannel channel = (SocketChannel) key.channel();  
  106.         //从通道里面读取数据到缓冲区并返回读取字节数     
  107.         int count = channel.read(this.buf);  
  108.   
  109.         if (count == -1) {  
  110.             //取消这个通道的注册     
  111.             key.channel().close();  
  112.             key.cancel();  
  113.             return;  
  114.         }  
  115.   
  116.         //将数据从缓冲区中拿出来     
  117.         String input = new String(this.buf.array()).trim();  
  118.         //那么现在判断是连接的那种服务     
  119.         if (channel.equals(this.clientchannel1)) {  
  120.             System.out.println("欢迎您使用服务A");  
  121.             System.out.println("您的输入为:" + input);  
  122.         } else {  
  123.             System.out.println("欢迎您使用服务B");  
  124.             System.out.println("您的输入为:" + input);  
  125.         }  
  126.   
  127.     }  
  128.   
  129.     @Override  
  130.     public void run() {  
  131.         while (true) {  
  132.             try {  
  133.                 System.out.println("running ... ");  
  134.                 //选择一组键,其相应的通道已为 I/O 操作准备就绪。     
  135.                 this.selector.select();  
  136.   
  137.                 //返回此选择器的已选择键集     
  138.                 //public abstract Set<SelectionKey> selectedKeys()     
  139.                 Iterator selectorKeys = this.selector.selectedKeys().iterator();  
  140.                 while (selectorKeys.hasNext()) {  
  141.                     System.out.println("running2 ... ");  
  142.                     //这里找到当前的选择键     
  143.                     SelectionKey key = (SelectionKey) selectorKeys.next();  
  144.                     //然后将它从返回键队列中删除     
  145.                     selectorKeys.remove();  
  146.                     if (!key.isValid()) { // 选择键无效  
  147.                         continue;  
  148.                     }  
  149.                     if (key.isAcceptable()) {  
  150.                         //如果遇到请求那么就响应     
  151.                         this.accept(key);  
  152.                     } else if (key.isReadable()) {  
  153.                         //读取客户端的数据     
  154.                         this.read(key);  
  155.                     }  
  156.                 }  
  157.             } catch (Exception e) {  
  158.                 e.printStackTrace();  
  159.             }  
  160.         }  
  161.     }  
  162.   
  163.     public static void main(String[] args) {  
  164.         Server server = new Server();  
  165.         Thread thread = new Thread(server);  
  166.         thread.start();  
  167.     }  
  168. }  


Client

[java] view plain copy
  1. package cn.vicky.channel;  
  2.   
  3. import java.io.IOException;  
  4. import java.net.InetSocketAddress;  
  5. import java.nio.ByteBuffer;  
  6. import java.nio.channels.SocketChannel;  
  7. import java.net.InetAddress;  
  8.   
  9. /**   
  10.  * TCP/IP的NIO非阻塞方式   
  11.  * 客户端   
  12.  * */  
  13. public class Client {  
  14.   
  15.     //创建缓冲区     
  16.     private ByteBuffer buffer = ByteBuffer.allocate(512);  
  17.     //访问服务器     
  18.   
  19.     public void query(String host, int port) throws IOException {  
  20.         InetSocketAddress address = new InetSocketAddress(InetAddress.getByName(host), port);  
  21.         SocketChannel socket = null;  
  22.         byte[] bytes = new byte[512];  
  23.         while (true) {  
  24.             try {  
  25.                 System.in.read(bytes);  
  26.                 socket = SocketChannel.open();  
  27.                 socket.connect(address);  
  28.                 buffer.clear();  
  29.                 buffer.put(bytes);  
  30.                 buffer.flip();  
  31.                 socket.write(buffer);  
  32.                 buffer.clear();  
  33.             } catch (Exception e) {  
  34.                 e.printStackTrace();  
  35.             } finally {  
  36.                 if (socket != null) {  
  37.                     socket.close();  
  38.                 }  
  39.             }  
  40.         }  
  41.     }  
  42.   
  43.     public static void main(String[] args) throws IOException {  
  44.         new Client().query("localhost"8099);  
  45.   
  46.     }  
  47. }  


 

以上的服务端一个线程监听两个服务,整个服务端只有一个阻塞的方法:

//选择一组键,其相应的通道已为 I/O 操作准备就绪。  

this.selector.select();  

 

当客户请求服务器的时候,那么这造成了TCP没有面向连接的假象,其实至少在传输数据的时候是连接的,只是在一次I/O请求结束之后服务器端就把连接给断开,继而继续去处理更多的请求。而在客户端,可以看到也是遇到一次请求的时候就connect服务端一次。所以TCP还是面向连接的。

     现在终于知道了为什么叫非阻塞式IO了,大概就是这个意思。


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 合伙生意转让意见不合怎么办 租完房子后悔了怎么办 通过中介买房产生纠纷怎么办 天津公租房资格证到期怎么办 买大房子后悔了怎么办 公款私存了该怎么办 外地人怎么办身份证在北京东城区 申请公租房有车怎么办 租了公租房辞职怎么办 申请公租房收入明细没有怎么办 杭州公租房满3年怎么办 小学寄读不转学籍手续怎么办 炸东西的油糊了怎么办 赠送面积为违建怎么办 执法不管违建我怎么办 司法考试毕业院校写错了怎么办 家具店西安一直拖着不交货怎么办 派出所私自迁移了我家户口怎么办? 退房子不退押金怎么办 租房子中介不退押金怎么办 租房子押金不退怎么办 外地人签户口到武汉怎么办 开发商不给办土地证怎么办 房间里的油烟味怎么办 现金借款app还不了款怎么办 学校不允许实习生自己租房怎么办 盯盯拍开不了机怎么办 向私人借钱不还怎么办 微信好友借钱不还怎么办 支付宝借不了钱怎么办 支付宝借条关了怎么办 qq群不小心解散了怎么办 qq群解散了照片怎么办 出租屋没窗户很闷怎么办 二手房交税后房主不卖怎么办 二房东收不到租拖欠房租怎么办 房东不给换门锁怎么办 租房到期房东联系不到租客怎么办 廉租房名下有车怎么办 路边停车收忘记交费怎么办 考编忘记交费了怎么办