Java之NIO教程二
Java NIO 是一种New IO的简称,也被称为非阻塞IO(Non-blocking I/O),严格来说NIO与并发没有直接的关系,但是使用NIO技术可以大大提高线程的使用效率。
NIO是一种基于通道和缓冲区的I/O方式,他可以使用Native函数库直接分配堆外内存(区别JVM的运行时数据区),然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的直接引用进行操作
NIO主要有三个核心部分:Channel(通道)、Buffer(缓冲区)、Selector(选择器)。还有一些其他组件,如Pipe和FileLock,是与核心组件共同使用的工具类。
1.Buffer:缓冲区是一个用来存储基础类型数的容器,java NIO里主要的Buffer实现:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer覆盖了常见的基础数据类型,还有MapperBuffer用于表示内存映射文件。
2.Channel:有点类似于流,数据可以从Channel读到Buffer,也可以从Buffer写到Channel中;通道还可以异步读写;通道中的数据总是先读到一个Buffer,或者从一个Buffer中写入。Channel常见的实现类有:FileChannel(对应于文件IO)、DatagramChannel(对应于UDP)、SocketChannel(对应于TCP客户端)、ServerSocketChannel(对应于TCP服务端)。
3.Selector:选择器用于监听多个通道的事件。单个线程可以监听多个数据通道.即使用选择器,借助单一线程就可以对多个活动I/O通道实施监控和维护。
在传统的阻塞式I/O多线程模型中,服务器为每一个客户端创建一个线程,用来处理请求。当系统负载增大(并发请求过多)时,服务端的线程数会增多,线程之间的切换开销变大,每一个线程都要占用系统的资源(内存等)。现在操作系统和CPU在多任务方面表现得越来越好,多线程的开销也变小,在多核CPU中,如果不使用多任务就是在浪费CPU的能力。
传统的I/O处理方式,一个线程处理一个网络请求。
BIO同步阻塞,例如:A去饭店吃饭,但是饭店人很多,要等一段时间,在此期间,A什么都不做一直等着,直到饭来。
//传统BIO服务端
public class BIOServer {
static class ClientHandler implements Runnable{
private Socket socket;
private Scanner scanner;
private PrintStream out;
private boolean flag = true;
public ClientHandler(Socket socket){
this.socket = socket;
try {
scanner = new Scanner(socket.getInputStream());
scanner.useDelimiter("\n");
out = new PrintStream(socket.getOutputStream());
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void run() {
while (flag){
if(scanner.hasNext()){
String msg = scanner.next().trim();
System.out.println("收到客户端发送的:"+msg);
if("over".equalsIgnoreCase(msg)){
this.out.println("over");
this.flag = false;
}else {
out.println("[echo]"+msg);
}
}
}
try {
scanner.close();
out.close();
socket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception{
ServerSocket socket = new ServerSocket(8080);
System.out.println("服务端已经启动,监听端口:8080");
boolean flag = true;
ExecutorService executorService = Executors.newCachedThreadPool();
while (flag){
Socket client = socket.accept();
executorService.submit(new ClientHandler(client));
}
executorService.shutdown();
socket.close();
}
}
//BIO的客户端
public class BIOClient {
public static void main(String[] args) throws Exception{
Socket client = new Socket("localhost",8080);
Scanner scanner = new Scanner(client.getInputStream());
scanner.useDelimiter("\n");
PrintStream out = new PrintStream(client.getOutputStream());
boolean flag = true;
while (flag){
String inputData = getString("请输入要发送的内容:");
out.println(inputData);
if(scanner.hasNext()){
String str = scanner.next();
System.out.println(str);
}
if("over".equalsIgnoreCase(inputData)){
flag = false;
}
}
client.close();
}
static String getString(String ss){
boolean flag = true;
String str = null;
while (flag){
System.out.println(ss);
try {
str = new BufferedReader(new InputStreamReader(System.in)).readLine();
if(str == null || "".equalsIgnoreCase(str)){
System.out.println("数据输入不能为空");
}else {
flag = false;
}
}catch (Exception e){
e.printStackTrace();
}
}
return str;
}
}
NIO的处理方式,一个线程通过selector选择器可以管理多个网络。
NIO同步非阻塞,例如:A去饭店吃饭,但是饭店人多,A就出去逛街,等到饭做好了,饭店老板通知A回来吃饭。
同步和异步:
同步和异步通常用来形容一次方法的调用。同步方法一旦调用,调用者必须等到方法结束返回后,才能继续后续的操作。
异步方法调用,一旦开始,调用就会立即返回,调用者就可以干其他的事。此时异步方法会在其他线程继续“真实”的进行,等方法结束会通知调用者。
阻塞和非阻塞:
它们主要形容多线程间的相互影响,比如一个线程占用了临界区的资源,其他线程如果要用这些资源必须在临界区等待,这就是阻塞。
非阻塞恰恰相反,强调没有一个线程会妨碍其他线程执行,所有线程都会尝试不断执行。
NIO服务器如何实现非阻塞:
服务器上所有Channel都要向Selector注册,而Selector则负责监视这些Socket的IO状态(观察者),当其中任意一个或者多个Channel具有可用的IO操作时,该Selector的select()方法将会返回大于0的整数,该整数值就表示该Selector上有多少个Channel具有可用的IO操作,并提供selectedKeys()方法来返回这些Channel对应的SelectionKey集合(一个SelectionKey对应一个就绪的通道)。正是通过Selector,使得服务端只需要不断地调用Selector实例的select()方法就可以知道所有Channel是否有需要处理的IO操作。
//NIO服务端
public class JavaNIO {
private int port;
private Selector selector;
private ExecutorService tp = Executors.newFixedThreadPool(5);
public JavaNIO(int port){
this.port = port;
}
public void init(){
ServerSocketChannel ssc = null;
try {
ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(port));
selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIOServer start...");
}catch (Exception e){
e.printStackTrace();
}
}
public void accept(SelectionKey sk){
try {
ServerSocketChannel ssc = (ServerSocketChannel) sk.channel();
SocketChannel sc= ssc.accept();
sc.configureBlocking(false);
sc.register(selector,SelectionKey.OP_READ);
System.out.println("accept client"+sc.socket().getInetAddress().getHostName());
}catch (Exception e){
e.printStackTrace();
}
}
public void start(){
this.init();
while (true){
try {
int events = selector.select();
if(events > 0){
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
while (selectionKeys.hasNext()){
SelectionKey key = selectionKeys.next();
selectionKeys.remove();
if(key.isAcceptable()){
accept(key);
}else {
tp.submit(new NIOServerHandle(key));
}
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
static class NIOServerHandle implements Runnable{
SelectionKey sk;
public NIOServerHandle(SelectionKey sk){
this.sk = sk;
}
@Override
public void run() {
try {
if(sk.isReadable()){
SocketChannel socketChannel = (SocketChannel) sk.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer);
buffer.flip();
System.out.println("收到客户端"+socketChannel.socket().getInetAddress().getHostName());
//将数据添加到key
ByteBuffer outBuffer = ByteBuffer.wrap(buffer.array());
//将消息回送给客户端
socketChannel.write(outBuffer);
sk.cancel();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new JavaNIO(8080).start();
}
}
//NIO客户端
public class NIOClient {
private static final String host = "127.0.0.1";
static final int port = 8080;
private Selector selector;
public void connect(String host,int port){
try {
SocketChannel sc = SocketChannel.open();
sc.configureBlocking(false);
this.selector = Selector.open();
sc.register(selector, SelectionKey.OP_CONNECT);
sc.connect(new InetSocketAddress(host,port));
}catch (Exception e){
e.printStackTrace();
}
}
public void listen(){
while (true){
try {
int events = selector.select();
if(events>0){
Iterator<SelectionKey> sks = selector.selectedKeys().iterator();
while (sks.hasNext()){
SelectionKey selectionKey = sks.next();
sks.remove();
//连接事件
if(selectionKey.isConnectable()){
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
if(socketChannel.isConnectionPending()){
socketChannel.finishConnect();
}
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ);
socketChannel.write(ByteBuffer.wrap(("hello this is"+Thread.currentThread().getName()).getBytes()));
}else if(selectionKey.isReadable()){
SocketChannel sc = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
sc.read(buffer);
buffer.flip();
System.out.println("收到服务端数据"+new String(buffer.array()));
}
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
for(int i=0;i<3;i++){
new Thread(new Runnable() {
@Override
public void run() {
NIOClient client = new NIOClient();
client.connect(host,port);
client.listen();
}
}).start();
}
}
}