文档章节

NIO同步非阻塞

刘付kin
 刘付kin
发布于 2016/12/07 11:40
字数 1367
阅读 15
收藏 0

#jdk1.5版本新引入的socket编程!!

##它与BIO的区别如下图:

输入图片说明

#为什么需要引入NIO?

主要是传统的BIO在服务器端以阻塞的形式来接受(accept)客户端的请求,每接受一个请求就会创建一个线程(thread)来和这个客户端进行交互,每个线程只对接一个客户端,而且很多时候这个线程都会在inputstream和outputstream哪里阻塞了,严重浪费线程资源。

#模型分析:

其实NIOserver和操作系统的关系就是=====》消费者和生产者的关系。既然是消费者和生产者的关系,就可以在他们之间添加一个消息队列来缓解这种速度不匹配的问题。

模型图:

输入图片说明

如上图所示,NIO服务器端内部的Selector里面维护一个“事件队列”,时间包括accept、read、write等。也就是说这是的服务器端不再需要阻塞式地等待连接请求(accept),只需要做一下步骤:

  • 1:不断的去轮询这个消息队列,拿到事件。
  • 2:判断事件类型,如果是op_accept就连接、如果是op_read就去操作系统哪里读取到本身的ByteBuff中。

#总结

NIO是同步(轮询)非阻塞的socket通信编程。主要是借助了:生产者和消费者问题模型。

它里面只要有两个东西:

  • 1.)ByteBuffer。其实就是对byte[]和一些操作byte[]的方法的封装,包括编码和解码等。
  • 2.)Selector。多路复用器。内部维护一个“事件消息队列”,server不断的轮询这个Selector的消息队列,获取事件类型后,进行处理。
  • 3.)NIO由于没法使用数据流的概念,所以不适合做文件传输。只适合于做响应式的请求,比如RPC

#具体的实现:

NIOServer

package org.java.io.mynio;

import java.io.IOException;
import java.net.InetSocketAddress;
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.nio.charset.Charset;
import java.util.Iterator;

public class NIOServer {

	ByteBuffer buffer = null;
	Selector selector = null;
	
	public void init(){
		buffer = ByteBuffer.allocate(1024);
		try {
			selector = Selector.open();
			
			ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
			serverSocketChannel.bind(new InetSocketAddress(4700));
			serverSocketChannel.configureBlocking(false);
			
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
			System.out.println("服务器启动了,--->4700");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void run(){
		while (true) {
			try {
				selector.select();
				Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
				while (ite.hasNext()) {
					SelectionKey key = ite.next();
					ite.remove();
					handlerKey(key);
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public void handlerKey(SelectionKey key){
		if(key.isAcceptable()){
			try {
				ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
				SocketChannel channel = serverSocketChannel.accept();
				channel.configureBlocking(false);
				channel.register(selector, SelectionKey.OP_READ);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else if(key.isReadable()){
			try {
				SocketChannel channel = (SocketChannel) key.channel();
				buffer.clear();
				int count = channel.read(buffer);
				if(count > 0){
					buffer.flip();
					System.out.println("Server:收到了" + Charset.forName("utf-8").newDecoder().decode(buffer).toString());
					//同时向客户端返回一个信息
					buffer.clear();
					buffer.put("I am Server".getBytes());
					buffer.flip();
//这里中间一定不能够打印buffer中的信息,否则的话,会是的buffer中的position和limit指针移位,
//最后buffer中就“相当于”没有信息了,(这一定要记住!!!)
					channel.write(buffer);
				}else{
					channel.close();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		NIOServer server = new NIOServer();
		server.init();
		server.run();
	}
	
	public ByteBuffer getBuffer() {
		return buffer;
	}
	public void setBuffer(ByteBuffer buffer) {
		this.buffer = buffer;
	}
	public Selector getSelector() {
		return selector;
	}
	public void setSelector(Selector selector) {
		this.selector = selector;
	}
}

NioClient

package org.java.io.mynio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

public class NIOClient {

	private ByteBuffer buffer = null;
	private Selector selector = null;
	private SocketChannel socketChannel = null;
	
	public static void main(String[] args) {
		NIOClient nioClient = new NIOClient();
		nioClient.init();
		nioClient.connectServer();
		nioClient.select();
	}
	
	public void init(){
		buffer = ByteBuffer.allocate(1024);
		try {
			selector = Selector.open();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void connectServer(){
		try {
			socketChannel = SocketChannel.open();
			socketChannel.configureBlocking(false);
			socketChannel.connect(new InetSocketAddress("localhost", 4700));
			socketChannel.register(selector, SelectionKey.OP_CONNECT);
			System.out.println("向服务器发送连接请求,等等请求结果!!");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void select(){
		while (true) {
			try {
				selector.select();
				Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
				while (ite.hasNext()) {
					SelectionKey key = ite.next();
					ite.remove();
					handleKey(key);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void handleKey(SelectionKey key){
		SocketChannel channel = (SocketChannel) key.channel();
		try{
			if(key.isConnectable()){
	            if( channel.isConnectionPending()){
	                if(channel.finishConnect()){
	                	//只有当连接成功后才能注册OP_READ事件
	                	channel.register(selector, SelectionKey.OP_READ);
	                    buffer.clear();
	    				buffer.put("I am Client".getBytes("utf-8"));
	    				buffer.flip();
	    				channel.write(buffer);
	                }else{
	                    key.cancel();
	                }
	            }
	        }else if(key.isReadable()){
				buffer.clear();
				int count = channel.read(buffer);
				if(count > 0){
					buffer.flip();
					System.out.println("Client:收到了" + Charset.forName("utf-8").newDecoder().decode(buffer));
					
					//重新设置ByteBuffer,将发送的信息发送过去。
					buffer.clear();
					buffer.put("liufu".getBytes("utf-8"));
					buffer.flip();
//这里一定要注意,不能够对buffer来进行任何的读操作,否则会是的它里面的position和limit指针便宜了,postion和limit相等了,也就是buffer里面的信息相当于没有了。
//System.out.println(Charset.forName("utf-8").newDecoder().decode(buffer));
//System.out.println(Charset.forName("utf-8").newDecoder().decode(buffer));
					channel.write(buffer);
				}else{
					channel.close();
				}
			}
		}catch(Exception e){
			
		}
	}
}

#总结:

Nio主要借助于:ByteBuffer和Selector。

  • 1.)其实ByteBuffer里面就是封装了byte[]以及对这个数组进行操作的方法。
  • 2.)Selector里面就维护一个事件的“消息队列”。操作系统往里面添加事件,而服务器端不断的轮询这个Selector中的消息队列,拿到事件后在对这个事件进行处理。

#注意的问题:

  • ByteBuffer数组与byte[]的转换

    ByteBuffer在 buffer.clear();

    buffer.put("liufu".getBytes("utf-8"));

    buffer.flip();

    之后一定不能够对这个数组进行“读”操作,否则的话,这个ByteBuffer就会出现position和limit重叠的现象, 也就相当于ByteBuffer中没有数据了。如图:

输入图片说明

  • 而byte[]就没有这种问题

    byte[] bt = "byte Test".getBytes();

    System.out.println(new String(bt));

    System.out.println(new String(bt));


#后续追加::

其实上述的ByteBuf问题,只需要发送前byffer.flip();就解决了。

输入图片说明


#思考:

服务器端启动后,只有一个主线程在轮询那个Selector。而且拿到一个key后,就会对它进行处理,处理完后再去遍历Selector中的其他时间,所有事情都有这个线程处理,速度很慢,怎么解决??

#答案:

引入线程池。但是需要解决Selector同步的问题。

有这种需求就会有相应的解决方案框架:netty , mino

© 著作权归作者所有

共有 人打赏支持
刘付kin
粉丝 6
博文 100
码字总数 72832
作品 0
深圳
『IO』BIO,NIO和AIO以及相关名词解析

网络通信中经常用到IO操作,IO操作主要有BIO,NIO以及AIO等几种模式,要弄清这几种IO模式,又需要弄懂阻塞/非阻塞,同步/异步概念。网上对这几种概念的解释各不相同,我在本文中记录下我自己...

dejunz
2017/09/19
0
0
Linux IO模型与Java NIO

概述 看Java NIO一篇文章的时候又看到了“异步非阻塞”这个概念,一直处于似懂非懂的状态,想解释下到底什么是异步 什么是非阻塞,感觉抓不住重点。决定仔细研究一下。 本文试图研究以下问题...

yingtju
06/29
0
0
浅谈“阻塞同步”,“BIO、NIO、AIO”

一、阻塞?同步? 可能大家平常会经常听到这两个名词,但是没花太多心思详细了解,今天就来揭开这层面纱。 一次IO操作,以read方法举例,会经历两个阶段: (1)等待数据准备(Waitingfor the...

叫我宫城大人
2017/09/04
0
0
Java IO: BIO, NIO, AIO(含代码实现)

BIO, NIO, AIO,本身的描述都是在Java语言的基础上的。 而描述IO,我们需要从三个层面: 编程语言 实现原理 底层基础 从编程语言层面 BIO, NIO, AIO以Java的角度理解: BIO,同步阻塞式IO,简...

tantexian
2016/05/11
385
0
Java NIO那点事

什么是NIO? New Input/Output 基于通道和缓冲区的I/O方式 可使用Native函数库直接分配堆外内存 通过存储于Java堆的DirectByteBuffer对象直接操作已分配的堆外内存 同步非阻塞模型 与传统IO区...

爱做梦的胖子
2017/06/15
0
0

没有更多内容

加载失败,请刷新页面

加载更多

70.shell的函数 数组 告警系统需求分析

20.16/20.17 shell中的函数 20.18 shell中的数组 20.19 告警系统需求分析 20.16/20.17 shell中的函数: ~1. 函数就是把一段代码整理到了一个小单元中,并给这个小单元起一个名字,当用到这段...

王鑫linux
今天
0
0
分布式框架spring-session实现session一致性使用问题

前言:项目中使用到spring-session来缓存用户信息,保证服务之间session一致性,但是获取session信息为什么不能再服务层获取? 一、spring-session实现session一致性方式 用户每一次请求都会...

WALK_MAN
今天
5
0
C++ yield()与sleep_for()

C++11 标准库提供了yield()和sleep_for()两个方法。 (1)std::this_thread::yield(): 线程调用该方法时,主动让出CPU,并且不参与CPU的本次调度,从而让其他线程有机会运行。在后续的调度周...

yepanl
今天
4
0
Java并发编程实战(chapter_3)(线程池ThreadPoolExecutor源码分析)

这个系列一直没再写,很多原因,中间经历了换工作,熟悉项目,熟悉新团队等等一系列的事情。并发课题对于Java来说是一个又重要又难的一大块,除非气定神闲、精力满满,否则我本身是不敢随便写...

心中的理想乡
今天
31
0
shell学习之获取用户的输入命令read

在运行脚本的时候,命令行参数是可以传入参数,还有就是在脚本运行过程中需要用户输入参数,比如你想要在脚本运行时问个问题,并等待运行脚本的人来回答。bash shell为此提 供了read命令。 ...

woshixin
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部