A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 我是楠楠 黑马粉丝团   /  2018-1-30 10:02  /  1195 人查看  /  2 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

【郑州校区】详解Java nio
Java nio是在jdk4.0之后引入的,他和我们传统的BIO不同的地方在于,BIO是同步阻塞的,而NIO是同步非阻塞的,在NIO中用到了多路复用技术(这个是借鉴linux的select模式的),通过selector监听多个channel的事件。同时,IO是面向流的,NIO是面向缓冲区的。面向流没有用到缓冲,因此效率上没有NIO高。同时在Java的IO模型中jdk1.7之后的AIO是异步的,但在本章不做介绍,大家有兴趣的可以自己研究下。
        首先说一下Channel,通道,NIO中Channel的主要实现有:FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel。本片主要讲解SocketChannel、ServerSocketChannel,对应的是TCP(Server和Client),前两者分别对应IO、UDP本章不做讲解,有兴趣的可以自己看看。
        Buffer,NIO中的关键Buffer实现有:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分别对应基本数据类型: byte, char, double, float, int, long, short。当然NIO中还有MappedByteBuffer, HeapByteBuffer, DirectByteBuffer等。Buffer顾名思义:缓冲区,实际上是一个容器,一个连续数组,buffer中有四个标记位,capacity, position, limit, mark。Buffer的api主要就是操作这几个标记位的值,判断标记位之间是否有数据等。        Selector,Selector运行单线程处理多个Channel,如果你的应用打开了多个通道,但每个连接的流量都很低,使用Selector就会很方便。
        NIO的强大功能部分来自于Channel的非阻塞特性,套接字的某些操作可能会无限期地阻塞。例如,对accept()方法的调用可能会因为等待一个客户端连接而阻塞;对read()方法的调用可能会因为没有数据可读而阻塞,直到连接的另一端传来新的数据。总的来说,创建/接收连接或读写数据等I/O调用,都可能无限期地阻塞等待,直到底层的网络实现发生了什么。慢速的,有损耗的网络,或仅仅是简单的网络故障都可能导致任意时间的延迟。然而不幸的是,在调用一个方法之前无法知道其是否阻塞。NIO的channel抽象的一个重要特征就是可以通过配置它的阻塞行为,以实现非阻塞式的信道。channel.configureBlocking(false)。
        在非阻塞式信道上调用一个方法总是会立即返回。这种调用的返回值指示了所请求的操作完成的程度。例如,在一个非阻塞式ServerSocketChannel上调用accept()方法,如果有连接请求来了,则返回客户端SocketChannel,否则返回null。
下面直接上代码。
客户端:
[AppleScript] 纯文本查看 复制代码
public class Client {
	
	@Test
	public void startClient() {
		ByteBuffer buffer = ByteBuffer.allocate(1024);
        SocketChannel socketChannel = null;
        try {
        	//打开socketChannel,客户端通道
            socketChannel = SocketChannel.open();
            //设置非阻塞
            socketChannel.configureBlocking(false);
            //建立连接
            socketChannel.connect(new InetSocketAddress("自己电脑ip",8081));
            /**
             * socketChannel.finishConnect():建立连接后返回true
             */
            if(socketChannel.finishConnect()) {
                int i=0;
                while(true) {
                    TimeUnit.SECONDS.sleep(1);
                    String info = "I'm "+i+++"-th information from client";
                    //清空缓存区,其实就是设置标志位
                    buffer.clear();
                    buffer.put(info.getBytes());
                    //和clear()方法配合调整buffer数组中 position, limit, mark的值。
                    buffer.flip();
                    //判断position和limit之间是否有数据,其实就是buffer中是否有元素
                    /**
                     * buffer.clear();
                     * buffer.put(info.getBytes());
                     * buffer.flip();
                     * 只要info中有数据,这时候buffer中就有数据
                     */
                    while(buffer.hasRemaining()){
                        System.out.println(buffer);
                        /**
                         *将数据写入客户端通道,socketChannel,同时清空buffer中数据,其实buffer中还是有数据的,只是此时
                         *position和limit是同一个位置,导致position和limit之间没有数据,
                         *所以下次调用buffer.hasRemaining()返回就是false
                         */
                        socketChannel.write(buffer);
                    }
                }
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            try {
                if(socketChannel!=null) {
                    socketChannel.close();
                }
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
	}
}

服务端代码:
public class Server {
	private static final int BUF_SIZE=1024;
    private static final int PORT = 8081;
    private static final int TIMEOUT = 3000;
    
    @Test
    public void selector() {
    	Selector selector = null;
        ServerSocketChannel ssc = null;
        
        try {
			selector = Selector.open();
			ssc= ServerSocketChannel.open();
			//设置服务端socket监听端口
			ssc.socket().bind(new InetSocketAddress(PORT));
			//设置非阻塞模式
			ssc.configureBlocking(false);
			//将通道channel注册到选择器上,
			/**
			 * 第二个参数:
			 * SelectionKey.OP_CONNECT。连接就绪
			 * SelectionKey.OP_ACCEPT。接收就绪
			 * SelectionKey.OP_READ。读就绪
			 * SelectionKey.OP_WRITE。写就绪
			 */
			ssc.register(selector, SelectionKey.OP_ACCEPT);
			
			while (true) {
				/**
				 * select()阻塞到至少有一个通道在你注册的事件上就绪了。 
				 * select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。 
				 * selectNow()不会阻塞,不管什么通道就绪都立刻返回
				 * (此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。)。
				 */
				if(selector.select(TIMEOUT) == 0){
                    System.out.println("==");
                    continue;
                }
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while(iter.hasNext()){
                    SelectionKey key = iter.next();
                    if(key.isAcceptable()){
                    	System.out.println("handleAccept");
                        handleAccept(key);
                    }
                    if(key.isReadable()){
                    	System.out.println("handleRead");
                        handleRead(key);
                    }
                    if(key.isWritable() && key.isValid()){
                    	System.out.println("handleWrite");
                        handleWrite(key);
                    }
                    if(key.isConnectable()){
                        System.out.println("isConnectable = true");
                    }
                    iter.remove();
                }
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
            try{
                if(selector!=null){
                    selector.close();
                }
                if(ssc!=null){
                    ssc.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
    
    public void handleWrite(SelectionKey key) throws IOException{
        ByteBuffer buf = (ByteBuffer)key.attachment();
        buf.flip();
        SocketChannel sc = (SocketChannel) key.channel();
        while(buf.hasRemaining()){
            sc.write(buf);
        }
        buf.compact();
    }
    
    public void handleAccept(SelectionKey key) throws IOException{
        ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();
        SocketChannel sc = ssChannel.accept();
        sc.configureBlocking(false);
        sc.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocateDirect(BUF_SIZE));
    }
 
    public void handleRead(SelectionKey key) throws IOException{
        SocketChannel sc = (SocketChannel)key.channel();
        ByteBuffer buf = (ByteBuffer)key.attachment();
        long bytesRead = sc.read(buf);
        while(bytesRead>0){
            buf.flip();
            while(buf.hasRemaining()){
                System.out.print((char)buf.get());
            }
            System.out.println();
            buf.clear();
            bytesRead = sc.read(buf);
        }
        if(bytesRead == -1){
            sc.close();
        }
    }
}
本篇讲的是nio的一些基本的原理以及基本的使用介绍,nio api的使用大家要通过查阅开发文档来熟悉。同时建议大家熟悉nio后可以看看netty框架或者mina框架。


传智播客·黑马程序员郑州校区地址
河南省郑州市 高新区长椿路11号大学科技园(西区)东门8号楼三层
联系电话 0371-56061160/61/62
来校路线  地铁一号线梧桐街站A口出

2 个回复

倒序浏览
来看看
回复 使用道具 举报

来看看
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马