netty网络编程(一)之Java IO与NIO

Linux I/O模型介绍

Linux IO流程

一个socket进程进行一次read可以分成两个阶段,等待数据是否准备好,以及数据从内核copy到用户空间
在这里插入图片描述

Linux IO模型

网络IO的本质是socket的读取socket在linux系统被抽象为流IO可以理解为对流的操作。对于一次IO访问(以read为例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。对于socket流而言

  • 通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。
  • 把数据从内核缓冲区复制到应用进程缓冲区。

网络应用需要处理2类问题:网络IO和数据计算,相对于数据计算,网络IO的延迟,给应用程序带来的性能瓶颈较大。所以需要选择一种合理的网络IO模型,常见的网络IO模型有5种。分别是阻塞式I/O模型、非阻塞式I/O模型、I/O多路复用、信号驱动式I/O、异步I/O

阻塞式I/O模型

  • 阻塞就是一直等待到事情做完才返回
  • 如果是阻塞进程read一直等待,进程控制权就没了,要阻塞两个阶段,数据到来和数据从内核copy到进程缓冲区。

非阻塞式I/O模型

  • 非阻塞就是立马返回一个消息(没做好或者已经做好)
  • 非阻塞则是read一发出立马返回一个错误信息,程序里必须不断去read直到数据到达返回可读消息,然后进程阻塞把数据从内核缓冲区copy到进程缓冲区,实际阻塞的是后面取数据的阶段。

阻塞式IO和非阻塞式IO的对比

针对一个IO,非阻塞IO没有阻塞IO的效率高。而且相对于阻塞而言,非阻塞是轮询要发出很多次系统调用的,而且很多次都是空轮询,看起来毫无优势可言。但是非阻塞IO因为等待数据阶段是非阻塞的,所以可以同时进行很多个IO操作

I/O多路复用

尽管非阻塞支持同时多IO,但是资源上还是很浪费,存在很多空轮询。于是出现了一种新的IO模型,叫做IO多路复用模型。例如select模型、poll模型、epoll模型

select模型

select模型支持一次性监控一大堆感兴趣的IO事件发生的描述符集合,一旦有一个或多个IO准备就绪,就返回所有描述符集合。然后扫描返回列表,找到有可读或者可写的描述符,可能是一个也可能多个,进行相应第二阶段读取或者写入数据。select模型调用的时候也是阻塞的,只是它支持多路IO前提下减少了不必要的空轮询。
优点:
跨平台性好
缺点:

  • 首先支持的监控的描述符集合数量有限制,内核参数决定的。
  • 每次select调用进程是阻塞的,并且需要把描述符集合从用户态copy到内核态,一旦数量增加,资源消耗线性增加。
  • select返回的描述符是没有经过筛选的

信号驱动式I/O

首先我们允许Socket进行信号驱动IO,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。
数据准备阶段:未阻塞,当数据准备完成之后,会主动的通知用户进程数据已经准备完成,对用户进程做一个回调。
数据拷贝阶段阻塞用户进程,等待数据拷贝。

异步I/O

相对于同步IO,异步IO不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的

小结

在这里插入图片描述

Java I/O

单线程程序

客户端

package com.demo.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class EchoClient {
	public static void main(String[] args) {

		Socket echoSocket = null;
		PrintWriter out = null;
		BufferedReader in = null;

		try {

			echoSocket = new Socket("127.0.0.1", 8080);
 			out = new PrintWriter(echoSocket.getOutputStream(), true);
 			in = new BufferedReader(new InputStreamReader(
					echoSocket.getInputStream()));
			System.out.println("连接到服务器......");
			System.out.println("请输入消息[输入\"Quit\"]退出:");
			BufferedReader stdIn = new BufferedReader(new InputStreamReader(
					System.in));
			String userInput;

			while ((userInput = stdIn.readLine()) != null) {
				out.println(userInput);
				System.out.println(in.readLine());

				if (userInput.equals("Quit")) {
					System.out.println("关闭客户端......");
					out.close();
					in.close();
					stdIn.close();
					echoSocket.close();
					System.exit(1);
				}
				System.out.println("请输入消息[输入\"Quit\"]退出:");
			}
		} catch (UnknownHostException e) {
			System.err.println("Don't know about host: PallaviÕs MacBook Pro.");
			System.exit(1);
		} catch (IOException e) {
			System.err.println("Couldn't get I/O for "
					+ "the connection to: PallaviÕs MacBook Pro.");
			System.exit(1);
		}

	}
}

服务端

package com.demo.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class SingleThreadEchoServer {

	private int port;

	public SingleThreadEchoServer(int port) {
		this.port = port;
	}

	public void startServer() {
		ServerSocket echoServer = null;
		int i = 0;
		System.out.println("服务器在端口[" + this.port + "]等待客户请求......");
		try {
			echoServer = new ServerSocket(this.port);
			while (true) {
				Socket clientRequest = echoServer.accept();
				handleRequest(clientRequest, i++);
			}
		} catch (IOException e) {
			System.out.println(e);
		}
	}

	private void handleRequest(Socket clientSocket, int clientNo) {
		PrintStream os = null;
		BufferedReader in = null;
		try {
			in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
			os = new PrintStream(clientSocket.getOutputStream());
			String inputLine;
			while ((inputLine = in.readLine()) != null) {

				// 输入'Quit'退出
				if (inputLine.equals("Quit")) {
					System.out.println("关闭与客户端[" + clientNo + "]......" + clientNo);
					os.close();
					in.close();
					clientSocket.close();
					break;
				} else {
					System.out.println("来自客户端[" + clientNo + "]的输入: [" + inputLine + "]!");
					os.println("来自服务器端的响应:" + inputLine);
				}
			}
		} catch (IOException e) {
			System.out.println("Stream closed");
		}
	}

	public static void main(String[] args) throws IOException {
		new SingleThreadEchoServer(8080).startServer();
	}
}

运行结果如下
在这里插入图片描述
在这里插入图片描述

多线程程序

在这里插入图片描述

客户端

package com.demo.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class EchoClient {
	public static void main(String[] args) {

		Socket echoSocket = null;
		PrintWriter out = null;
		BufferedReader in = null;

		try {

			echoSocket = new Socket("127.0.0.1", 8080);
 			out = new PrintWriter(echoSocket.getOutputStream(), true);
 			in = new BufferedReader(new InputStreamReader(
					echoSocket.getInputStream()));
			System.out.println("连接到服务器......");
			System.out.println("请输入消息[输入\"Quit\"]退出:");
			BufferedReader stdIn = new BufferedReader(new InputStreamReader(
					System.in));
			String userInput;

			while ((userInput = stdIn.readLine()) != null) {
				out.println(userInput);
				System.out.println(in.readLine());

				if (userInput.equals("Quit")) {
					System.out.println("关闭客户端......");
					out.close();
					in.close();
					stdIn.close();
					echoSocket.close();
					System.exit(1);
				}
				System.out.println("请输入消息[输入\"Quit\"]退出:");
			}
		} catch (UnknownHostException e) {
			System.err.println("Don't know about host: PallaviÕs MacBook Pro.");
			System.exit(1);
		} catch (IOException e) {
			System.err.println("Couldn't get I/O for "
					+ "the connection to: PallaviÕs MacBook Pro.");
			System.exit(1);
		}

	}

}

服务端

package com.demo.io;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
 

public class MultiThreadedEchoServerV1 {
	private int port;
	
	public MultiThreadedEchoServerV1(int port) {
		this.port = port;
	}
	
	public void startServer() {
 		ServerSocket echoServer = null;
		int i = 0;
		System.out.println("服务器在端口[" + this.port + "]等待客户请求......");
		try {
			echoServer = new ServerSocket(8080);
			while (true) {
				Socket clientRequest = echoServer.accept();
				new Thread(new ThreadedServerHandler(clientRequest, i++)).start();
			}
		} catch (IOException e) {
			System.out.println(e);
		}

	}
 
    public static void main(String[] args) throws IOException {
    	new MultiThreadedEchoServerV1(8080).startServer();
     }
}
package com.demo.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

public class ThreadedServerHandler implements Runnable {
	Socket clientSocket = null;
	int clientNo = 0;

	ThreadedServerHandler(Socket socket, int i) {
		if (socket != null) {
			clientSocket = socket;
			clientNo = i;
			System.out.println("创建线程为[" + clientNo + "]号客户服务...");
		}
	}

	@Override
	public void run() {
		PrintStream os = null;
		BufferedReader in = null;
		try {
			in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
			os = new PrintStream(clientSocket.getOutputStream());
			String inputLine;
			while ((inputLine = in.readLine()) != null) {

				// 输入'Quit'退出
				if (inputLine.equals("Quit")) {
					System.out.println("关闭与客户端[" + clientNo + "]......" + clientNo);
					os.close();
					in.close();
					clientSocket.close();
					break;
				} else {
					System.out.println("来自客户端[" + clientNo + "]的输入: [" + inputLine + "]!");
					os.println("来自服务器端的响应:" + inputLine);
				}
			}
		} catch (IOException e) {
			System.out.println("Stream closed");
		}
	}
}

运行结果如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Java NIO

3大核心组件:Buffer、Channel、Selector

Java IO和Java NIO对比

在这里插入图片描述

Buffer

一个 Buffer 本质上是内存中的一块, 可以将数据写入这块内存, 从这块内存获取数据。
在这里插入图片描述
Java NIO Buffer三大核心概念:position、limit、capacity

  • 代表这个缓冲区的容量,一旦设定就不可以更改。比如 capacity 为 1024 的 IntBuffer,代表其一次可以存放 1024 个 int 类型的值。
  • 一旦 Buffer 的容量达到 capacity,需要清空 Buffer,才能重新写入值。
  • 从写操作模式到读操作模式切换的时候(flip),position 都会归零,这样就可以从头开始读写了。
  • 写操作模式下,limit 代表的是最大能写入的数据,这个时候 limit 等于 capacity。
  • 写结束后,切换到读模式,此时的 limit 等于 Buffer 中实际的数据大小,因为 Buffer 不一定被写满了。

案例

理解capacity、limit、position、mark
0 – mark – position – limit – capacity

package com.demo.nio.buffers;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;

public class BufferCreate {
	public static void main(String[] args) {
		ByteBuffer buffer0 = ByteBuffer.allocate(10);
		if (buffer0.hasArray()) {
			System.out.println("buffer0 array: " + buffer0.array());
			System.out.println("Buffer0 array offset: " + buffer0.arrayOffset());

		}
		System.out.println("Capacity: " + buffer0.capacity());
		System.out.println("Limit: " + buffer0.limit());
		System.out.println("Position: " + buffer0.position());
		System.out.println("Remaining: " + buffer0.remaining());
		System.out.println();

		ByteBuffer buffer1 = ByteBuffer.allocateDirect(10);
		if (buffer1.hasArray()) {
			System.out.println("buffer1 array: " + buffer1.array());
			System.out.println("Buffer1 array offset: " + buffer1.arrayOffset());
		}
		System.out.println("Capacity: " + buffer1.capacity());
		System.out.println("Limit: " + buffer1.limit());
		System.out.println("Position: " + buffer1.position());
		System.out.println("Remaining: " + buffer1.remaining());
		System.out.println();

		byte[] bytes = new byte[10];
		ByteBuffer buffer2 = ByteBuffer.wrap(bytes);
		if (buffer2.hasArray()) {
			System.out.println("buffer2 array: " + buffer2.array());
			System.out.println("Buffer2 array offset: " + buffer2.arrayOffset());

		}
		System.out.println("Capacity: " + buffer2.capacity());
		System.out.println("Limit: " + buffer2.limit());
		System.out.println("Position: " + buffer2.position());
		System.out.println("Remaining: " + buffer2.remaining());
		System.out.println();

		byte[] bytes2 = new byte[10];
		ByteBuffer buffer3 = ByteBuffer.wrap(bytes2, 2, 3);
		if (buffer3.hasArray()) {
			System.out.println("buffer3 array: " + buffer3.array());
			System.out.println("Buffer3 array offset: " + buffer3.arrayOffset());
		}
		System.out.println("Capacity: " + buffer3.capacity());
		System.out.println("Limit: " + buffer3.limit());
		System.out.println("Position: " + buffer3.position());
		System.out.println("Remaining: " + buffer3.remaining());

	}
}

在这里插入图片描述

package com.demo.nio.buffers;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;


public class BufferAccess {
	public static void main(String[] args) {
		ByteBuffer buffer = ByteBuffer.allocate(10);
		printBuffer(buffer);
		
		buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'0');
		printBuffer(buffer);

		buffer.flip();
		printBuffer(buffer);

		//取buffer
		System.out.println("" + (char) buffer.get() + (char) buffer.get());
		printBuffer(buffer);

		buffer.mark();
		printBuffer(buffer);

		//读取两个元素后,恢复到之前mark的位置处
		System.out.println("" + (char) buffer.get() + (char) buffer.get());
		printBuffer(buffer);

		buffer.reset();
		//buffer.rewind();

		printBuffer(buffer);


		buffer.compact();
		printBuffer(buffer);
		

		buffer.clear();
		printBuffer(buffer);

	}
	
	private static void printBuffer(Buffer buffer) {
		System.out.println("[limit=" + buffer.limit() 
				+", position = " + buffer.position()
				+", capacity = " + buffer.capacity()
 				+", array = " + buffer.toString()+"]");
	}
}

在这里插入图片描述

package com.demo.nio.buffers;

import java.nio.Buffer;
import java.nio.ByteBuffer;

/**
 * 缓冲区分片与数据共享
 */
public class BufferSlice {
	public static void main(String[] args) {
		ByteBuffer buffer = ByteBuffer.allocate(10);
		for(int i = 0; i < buffer.capacity(); i++) {
			buffer.put((byte) i);
		}
		
		printBuffer(buffer);
		//
		buffer.position(3).limit(7);
		
		printBuffer(buffer);

		ByteBuffer sliceBuffer = buffer.slice();
		for(int i = 0;i < sliceBuffer.capacity();i++) {
			byte b = sliceBuffer.get();
			b *= 11;
			sliceBuffer.put(i, b);
		}
		printBuffer(sliceBuffer);

		buffer.position(0).limit(buffer.capacity());
		while(buffer.hasRemaining()) {
			System.out.println(buffer.get());
		}
		
		printBuffer(buffer);

	}
	
	private static void printBuffer(Buffer buffer) {
		System.out.println("[limit=" + buffer.limit() 
				+", position = " + buffer.position()
				+", capacity = " + buffer.capacity()
				+", array = " + buffer.toString()+"]");
	}
}

在这里插入图片描述

package com.demo.nio.buffers;

import java.nio.Buffer;
import java.nio.CharBuffer;


public class DuplicateBuffer {

	public static void main(String[] args) {
		CharBuffer buffer = CharBuffer.allocate(8);
		for(int i= 0 ; i < buffer.capacity() ; i++) {
			buffer.put(String.valueOf(i).charAt(0));
		}
		printBuffer(buffer);

		buffer.flip();
		printBuffer(buffer);
		
 		buffer.position(3).limit(6).mark().position(5);
 		printBuffer(buffer);

		CharBuffer dupeBuffer = buffer.duplicate();
		buffer.clear();
 		printBuffer(buffer);
 		
		printBuffer(dupeBuffer);
		
 		dupeBuffer.clear();
		printBuffer(dupeBuffer);
		
	}
	
	private static void printBuffer(Buffer buffer) {
		System.out.println("[limit=" + buffer.limit() 
				+", position = " + buffer.position()
				+", capacity = " + buffer.capacity()
				+", array = " + buffer.toString()+"]");
	}

}

在这里插入图片描述

package com.demo.nio.buffers;

import java.nio.*;

public class SliceBuffer {
    static public void main(String args[]) throws Exception {
        ByteBuffer buffer = ByteBuffer.allocate(10);

        for (int i = 0; i < buffer.capacity(); ++i) {
            buffer.put((byte) i);
        }

        buffer.position(3);
        buffer.limit(7);

        ByteBuffer slice = buffer.slice();

        for (int i = 0; i < slice.capacity(); ++i) {
            byte b = slice.get(i);
            b *= 11;
            slice.put(i, b);
        }

        buffer.position(0);
        buffer.limit(buffer.capacity());

        while (buffer.remaining() > 0) {
            System.out.println(buffer.get());
        }
    }
}

在这里插入图片描述

DirectByteBuffer和non-direct ByteBuffer对比

在这里插入图片描述

小结

Buffer创建
1、allocate/allocateDirect方法
2、wrap方法
Buffer读取
1、put/get方法
2、flip方法
3、mark/reset方法
4、compact方法
5、rewind/clear
Buffer复制 – 浅复制
1、duplicate方法
2、asReadOnlyBuffer方法
3、slice方法

Channel

NIO 操作始于通道,通道是数据来源数据写入的目的地
在这里插入图片描述
FileChannel:文件通道,用于文件的读和写
DatagramChannel:用于 UDP 连接的接收和发送
SocketChannel:把它理解为 TCP 连接通道,简单理解就是 TCP 客户端
ServerSocketChannel:TCP 对应的服务端,用于监听某个端口进来的请求
在这里插入图片描述
在这里插入图片描述

案例

package com.demo.nio.channels;

import java.io.*;
import java.nio.*;
import java.nio.channels.*;


public class CopyFile {
    static public void main(String args[]) throws Exception {
        String infile = "H:\\workspace\\learning-nio\\src\\main\\resources\\CopyFile.java";
        String outfile = "H:\\workspace\\learning-nio\\src\\main\\resources\\CopyFile.java.copy";


        // 从流中获取通道
        FileInputStream fin = new FileInputStream(infile);
        FileOutputStream fout = new FileOutputStream(outfile);

        FileChannel fcin = fin.getChannel();
        FileChannel fcout = fout.getChannel();

        // 创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        while (true) {
            // 读入之前要清空
            buffer.clear();

            // position自动前进
            int r = fcin.read(buffer);

            if (r == -1) {
                break;
            }

            // position = 0; limit=读到的字节数
            buffer.flip();

            // 从 buffer 中读
            fcout.write(buffer);
        }
    }
}

在这里插入图片描述

Selector

Selector是Java NIO中的一个组件,用于检查一个或多个NIO Channel的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络连接。
在这里插入图片描述
在这里插入图片描述

创建Selector(Creating a Selector)
Selector selector = Selector.open();

注册Channel到Selector上(Registering Channels with the Selector)
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
register的第二个参数,这个参数是一个“关注集合”,代表关注的channel状态,
有四种基础类型可供监听, 用SelectionKey中的常量表示如下:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE

从Selector中选择channel(Selecting Channels via a Selector)
一旦向Selector注册了一个或多个channel后,就可以调用select来获取channel
select方法会返回所有处于就绪状态的channel
select方法具体如下:
int select()
int select(long timeout)
int selectNow()
select()方法的返回值是一个int,代表有多少channel处于就绪了。也就是自上一次select后有多少channel进入就绪。

selectedKeys()
在调用select并返回了有channel就绪之后,可以通过选中的key集合来获取channel,这个操作通过调用selectedKeys()方法:
Set<SelectionKey> selectedKeys = selector.selectedKeys();    
Set<SelectionKey> selectedKeys = selector.selectedKeys();

Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {

    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {
        // a connection was established with a remote server.

    } else if (key.isReadable()) {
        // a channel is ready for reading

    } else if (key.isWritable()) {
        // a channel is ready for writing
    }

    keyIterator.remove();
}

案例

客户端

package com.demo.nio.demo;

public class NIOEchoClient {

    public static void main(String[] args) {

        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
            }
        }
        new Thread(new NIOEchoClientHandler("127.0.0.1", port), "NIOEchoClient-001").start();
    }
}
package com.demo.nio.demo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
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.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NIOEchoClientHandler implements Runnable {

    private String host;
    private int port;

    private Selector selector;
    private SocketChannel socketChannel;

    private ExecutorService executorService;

    private volatile boolean stop;

    public NIOEchoClientHandler(String host, int port) {
        this.host = host == null ? "127.0.0.1" : host;
        this.port = port;
        this.executorService = Executors.newSingleThreadExecutor();

        try {
            selector = Selector.open();
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    @Override
    public void run() {
        try {
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
            socketChannel.connect(new InetSocketAddress(host, port));
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        while (!stop) {
            try {
                selector.select(1000);
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectedKeys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    key = it.next();
                    it.remove();
                    try {
                        handleInput(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null)
                                key.channel().close();
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }

        // 多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源
        if (selector != null) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (executorService != null) {
            executorService.shutdown();
        }
    }

    private void handleInput(SelectionKey key) throws IOException {
        if (key.isValid()) {
            // 判断是否连接成功
            SocketChannel sc = (SocketChannel) key.channel();
            if (key.isConnectable()) {
                if (sc.finishConnect()) {
                    System.out.println("连接到服务器......");

                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    System.out.println("请输入消息[输入\"Quit\"]退出:");

                    executorService.submit(() -> {
                        while (true) {
                            try {
                                buffer.clear();
                                InputStreamReader input = new InputStreamReader(System.in);
                                BufferedReader br = new BufferedReader(input);

                                String msg = br.readLine();

                                if (msg.equals("Quit")) {
                                    System.out.println("关闭客户端......");
                                    key.cancel();
                                    sc.close();
                                    this.stop = true;
                                    break;
                                }

                                buffer.put(msg.getBytes());
                                buffer.flip();

                                sc.write(buffer);

                                System.out.println("请输入消息[输入\"Quit\"]退出:");

                            } catch (Exception ex) {
                                ex.printStackTrace();
                            }
                        }
                    });
                    sc.register(selector, SelectionKey.OP_READ);
                } else {
                    // 连接失败,进程退出
                    System.exit(1);
                }
            }

            if (key.isReadable()) {
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int readBytes = sc.read(readBuffer);
                if (readBytes > 0) {
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    String body = new String(bytes, "UTF-8");
                    System.out.println(body);

                    if (body.equals("Quit")) {
                        this.stop = true;
                    }
                } else if (readBytes < 0) {
                    // 对端链路关闭
                    key.cancel();
                    sc.close();
                }
            }
            if (key.isWritable()) {
                System.out.println("The key is writable");
            }
        }
    }
}

服务端

package com.demo.nio.demo;

import java.io.IOException;


public class NIOEchoServer {


    public static void main(String[] args) throws IOException {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                // 采用默认值
            }
        }
        EchoHandler timeServer = new EchoHandler(port);
        new Thread(timeServer, "NIO-MultiplexerTimeServer-001").start();
    }
}
package com.demo.nio.demo;

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.util.Iterator;
import java.util.Set;

public class EchoHandler implements Runnable {

	private Selector selector;
	private ServerSocketChannel servChannel;
	private volatile boolean stop;
	private int num = 0;

	public EchoHandler(int port) {
		try {
			selector = Selector.open();
			servChannel = ServerSocketChannel.open();
			servChannel.configureBlocking(false);
			servChannel.socket().bind(new InetSocketAddress(port), 1024);
			servChannel.register(selector, SelectionKey.OP_ACCEPT);
			System.out.println("服务器在端口[" + port + "]等待客户请求......");
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	public void stop() {
		this.stop = true;
	}

	@Override
	public void run() {
		while (!stop) {
			try {
				selector.select(1000);
				Set<SelectionKey> selectedKeys = selector.selectedKeys();
				Iterator<SelectionKey> it = selectedKeys.iterator();
				SelectionKey key = null;
				while (it.hasNext()) {
					key = it.next();
					it.remove();
					try {
						handleInput(key);
					} catch (Exception e) {
						if (key != null) {
							key.cancel();
							if (key.channel() != null) {
								key.channel().close();
							}
						}
					}
				}
			} catch (Throwable t) {
				t.printStackTrace();
			}
		}

		// 多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源
		if (selector != null) {
			try {
				selector.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private void handleInput(SelectionKey key) throws IOException {

		if (key.isValid()) {
			// 处理新接入的请求消息
			if (key.isAcceptable()) {
				ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
				// Non blocking, never null
				SocketChannel socketChannel = ssc.accept();
				socketChannel.configureBlocking(false);
				SelectionKey sk = socketChannel.register(selector, SelectionKey.OP_READ);

				sk.attach(num++);
			}
			if (key.isReadable()) {
				// 读取数据
				SocketChannel sc = (SocketChannel) key.channel();
				ByteBuffer readBuffer = ByteBuffer.allocate(1024);
				int readBytes = sc.read(readBuffer);
				if (readBytes > 0) {
					readBuffer.flip();
					byte[] bytes = new byte[readBuffer.remaining()];
					readBuffer.get(bytes);
					String body = new String(bytes, "UTF-8");
					System.out.println("来自客户端[" + key.attachment() + "]的输入: [" + body.trim() + "]!");

					if (body.trim().equals("Quit")) {
						System.out.println("关闭与客户端[" + key.attachment() + "]......");
						key.cancel();
						sc.close();
					} else {
						String response = "来自服务器端的响应:" + body;
						doWrite(sc, response);
					}

				} else if (readBytes < 0) {
					// 对端链路关闭
					key.cancel();
					sc.close();
				} else {

				}
			}
		}
	}

	private void doWrite(SocketChannel channel, String response) throws IOException {
		if (response != null && response.trim().length() > 0) {
			byte[] bytes = response.getBytes();
			ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
			writeBuffer.put(bytes);
			writeBuffer.flip();
			channel.write(writeBuffer);
		}
	}
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

NIO的优点

  • 事件驱动模型
  • 单线程处理多任务,避免多线程
  • 非阻塞IO,IO读写不再阻塞,而是返回0
  • 基于block的传输,通常比基于流的传输更高效
  • 更高级的IO函数,zero-copy
  • IO多路复用大大提高了java网络应用的可伸缩性和实用性

NIO的缺点

  • NIO不一定更快的场景
    客户端应用
    连接数<1000
    并发程度不高
    局域网环境下
  • NIO仍然是基于各个OS平台的IO系统实现的,差异仍然存在
  • 离散的事件驱动模型,编程困难
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章