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系统实现的,差异仍然存在
- 离散的事件驱动模型,编程困难