文章目录
重要概念
同步和异步
概念 | 含义 |
---|---|
同步 |
IO 时,Java 发起 IO 的线程自己处理 IO 读写,因此需要等待 IO 操作完成或者轮询地去查看 IO 操作是否就绪 |
异步 |
IO 时,Java 发起 IO 的线程将 IO 读写委托给操作系统,并将数据缓冲区地址和大小传给操作系统,当 IO 完成时 Java 线程会得到通知 |
阻塞和非阻塞
概念 | 含义 |
---|---|
阻塞 |
使用阻塞 IO 时,Java 发起 IO 的线程会一直阻塞到读写完成才返回 |
非阻塞 |
使用非阻塞 IO 时,不管是否能读写,发起 IO 的线程会马上得到一个返回值,进而继续执行其他代码 |
1. BIO(同步阻塞IO)
1.1 BIO 的处理流程
BIO
的流程通常是服务端启动一个ServerSocket
,然后在客户端启动Socket
来连接服务端进行通信,默认情况下服务端需要对每个请求建立一个线程进行处理,而客户端发送请求后线程会等待获得响应后才继续执行
Blocking IO
的特点就是在IO执行的两个阶段,等待数据和拷贝数据,都被阻塞了。大部分的socket
接口都是阻塞型的,而所谓阻塞型接口是指系统调用(一般是IO接口)不返回调用结果并让当前线程一直阻塞,直到该系统调用获得结果或者超时出错时才返回
1.2 使用示例
1.2.1 客户端
public class Client {
//默认的端口号
private static int DEFAULT_SERVER_PORT = 10086;
private static String DEFAULT_SERVER_IP = "127.0.0.1";
private static String QUIT_MSG = "q";
private static Socket socket;
public static void start() {
try {
socket = new Socket(DEFAULT_SERVER_IP, DEFAULT_SERVER_PORT);
System.out.println("Client: start, port " + DEFAULT_SERVER_PORT);
} catch (IOException e) {
e.printStackTrace();
}
}
public static boolean send(String exp) {
if (QUIT_MSG.equals(exp)) {
System.out.println("Client: quit");
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
System.out.println("Client: send " + exp);
InputStreamReader inputStreamReader;
BufferedReader in;
PrintWriter out;
try {
inputStreamReader = new InputStreamReader(socket.getInputStream());
in = new BufferedReader(inputStreamReader);
out = new PrintWriter(socket.getOutputStream(), true);
out.println(exp);
System.out.println("Client: receive " + in.readLine());
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
1.2.2 服务端
-
服务器类
public class Server { //默认的端口号 private static int DEFAULT_PORT = 10086; private static ServerSocket server; public static void start() { start(DEFAULT_PORT); } public synchronized static void start(int port) { if (server != null) return; new Thread(() -> { try { // 通过构造函数创建ServerSocket,如果端口合法且空闲,服务端就监听成功 server = new ServerSocket(port); System.out.println("Server: start, port " + port); // 无限循环监听客户端连接,如果没有客户端接入,将阻塞在accept操作上 while (true) { Socket socket = server.accept(); //当有新的客户端接入时,会执行下面的代码,创建一个新的线程处理这条 Socket 连接 new Thread(new ServerHandler(socket)).start(); } } catch (IOException e) { e.printStackTrace(); } finally { //一些必要的清理工作 if (server != null) { System.out.println("Server: close"); try { server.close(); } catch (IOException e) { e.printStackTrace(); } server = null; } } }, "Server").start(); } }
-
服务器处理者
public class ServerHandler implements Runnable { private Socket socket; public ServerHandler(Socket socket) { this.socket = socket; } @Override public void run() { InputStreamReader inputStreamReader; BufferedReader in; PrintWriter out; try { inputStreamReader = new InputStreamReader(socket.getInputStream()); in = new BufferedReader(inputStreamReader); out = new PrintWriter(socket.getOutputStream(), true); String exp; while (null != (exp = in.readLine())) { // 通过BufferedReader读取一行,如果已经读到输入流尾部或者对端关闭,返回null,退出循环 System.out.println("Server: receive " + exp); String result = "Server: send " + exp; out.println(result); } System.out.println("Server: handler quit"); } catch (Exception e) { e.printStackTrace(); } finally { try { if (null != socket) socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
1.2.3 测试类
public class Test {
public static void main(String[] args) throws InterruptedException {
Server.start();
//避免客户端先于服务器启动前执行代码
Thread.sleep(100);
//运行客户端
Client.start();
while (Client.send(new Scanner(System.in).nextLine()));
}
}
2. NIO(同步非阻塞IO)
NIO
本身是基于事件驱动思想来完成的,主要解决的是BIO
的大并发问题:
在使用
同步 I/O
的网络应用中,如果同时处理多个客户端请求,就必须使用多线程来处理,也就是将每一个客户端请求分配给一个线程来单独处理。这样做会带来一个问题,由于每创建一个线程就要为这个线程分配一定的内存空间,而操作系统本身资源是有限的,这样就对线程的总数构成了一定的限制。如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。
NIO 解决了线程创建过多的问题,不过在 NIO 的处理方式中,当一个请求进来,开启线程进行处理可能会等待后端应用的资源(JDBC连接等),其实这个线程就被阻塞了,当并发量上来时还是会有和 BIO 一样的问题
NIO 基于 Reactor,当socket
有流可读或可写入 socket
时,操作系统会相应的通知应用程序进行处理,应用再将流读取到缓冲区或写入操作系统。 也就是说,这个时候已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的
BIO 与 NIO 比较重要的不同点:
- 使用 BIO 的时候往往会引入多线程,每个连接对应一个单独的线程;而 NIO 线程处理的都是有效连接(数据就绪),且一个线程可以分管处理多个连接上的就绪数据,节省线程资源开销
- BIO 面向流,NIO 面向缓冲区
2.1 NIO 的处理流程
当用户线程发起 I/O
请求后,会将 socket
连接及关注事件注册到服务端的selector(多路复用器,os级别线程)
上,selector
循环遍历注册其上的 socket
连接,看是否有其关注事件就绪,如果连接有数据就绪后,就通知应用程序建立线程进行数据读写,也就是实际的读写操作是由应用程序完成的,而不是操作系统
2.2 使用示例
2.2.1 客户端
-
客户端
public class Client { private static String DEFAULT_HOST = "127.0.0.1"; private static int DEFAULT_PORT = 10086; private static String QUIT_MSG = "q"; private static ClientHandler clientHandle; public static void start() { start(DEFAULT_HOST, DEFAULT_PORT); } public static synchronized void start(String ip, int port) { if (clientHandle != null) { return; } clientHandle = new ClientHandler(ip, port); new Thread(clientHandle, "Client").start(); } //向服务器发送消息 public static boolean sendMsg(String msg) throws Exception { if (QUIT_MSG.equals(msg)) { System.out.println("Client: exit"); clientHandle.stop(); return false; } clientHandle.sendMsg(msg); return true; } }
-
客户端处理者
public class ClientHandler implements Runnable { private String host; private int port; private Selector selector; private SocketChannel socketChannel; private volatile boolean started; public ClientHandler(String ip, int port) { this.host = ip; this.port = port; try { //创建选择器 selector = Selector.open(); //打开监听通道 socketChannel = SocketChannel.open(); //如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式 socketChannel.configureBlocking(false); started = true; System.out.println("Client: start, port " + port); } catch (IOException e) { e.printStackTrace(); System.exit(1); } } public void stop() { started = false; } @Override public void run() { try { doConnect(); } catch (IOException e) { e.printStackTrace(); System.exit(1); } //循环遍历selector while (started) { try { //无论是否有读写事件发生,selector每隔1s被唤醒一次 selector.select(1000); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> it = keys.iterator(); SelectionKey key; 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); } } //selector关闭后会自动释放里面管理的资源 if (selector != null) try { selector.close(); } catch (Exception e) { e.printStackTrace(); } } private void handleInput(SelectionKey key) throws IOException { if (key.isValid()) { SocketChannel sc = (SocketChannel) key.channel(); if (key.isConnectable()) { if (sc.finishConnect()) { System.out.println("Client: connect to server"); } else { System.exit(1); } } //读消息 if (key.isReadable()) { //创建ByteBuffer,并开辟一个1M的缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); //读取请求码流,返回读取到的字节数 int readBytes = sc.read(buffer); //读取到字节,对字节进行编解码 if (readBytes > 0) { //将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作 buffer.flip(); //根据缓冲区可读字节数创建字节数组 byte[] bytes = new byte[buffer.remaining()]; //将缓冲区可读字节数组复制到新建的数组中 buffer.get(bytes); String result = new String(bytes, StandardCharsets.UTF_8); System.out.println("Client: receive " + result); } else if (readBytes < 0) { //链路已经关闭,释放资源 key.cancel(); sc.close(); } } } } //异步发送消息 private void doWrite(SocketChannel channel, String request) throws IOException { //将消息编码为字节数组 byte[] bytes = request.getBytes(); //根据数组容量创建ByteBuffer ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length); //将字节数组复制到缓冲区 writeBuffer.put(bytes); //flip操作 writeBuffer.flip(); //发送缓冲区的字节数组 channel.write(writeBuffer); } private void doConnect() throws IOException { if (socketChannel.connect(new InetSocketAddress(host, port))) { System.out.println("Client: already connect to Server"); } else { socketChannel.register(selector, SelectionKey.OP_CONNECT); } } public void sendMsg(String msg) throws Exception { socketChannel.register(selector, SelectionKey.OP_READ); doWrite(socketChannel, msg); } }
2.2.2 服务端
-
服务器
public class Server { private static int DEFAULT_PORT = 10086; private static ServerHandler serverHandle; public static void start() { start(DEFAULT_PORT); } public static synchronized void start(int port) { if (serverHandle != null) { return; } serverHandle = new ServerHandler(port); new Thread(serverHandle, "Server").start(); } }
-
服务端处理者
public class ServerHandler implements Runnable { private Selector selector; private ServerSocketChannel serverChannel; private volatile boolean started; /** * 构造方法 * * @param port 指定要监听的端口号 */ public ServerHandler (int port) { try { //创建选择器 selector = Selector.open(); //打开监听通道 serverChannel = ServerSocketChannel.open(); //如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式 serverChannel.configureBlocking(false); //绑定端口 backlog设为1024 serverChannel.socket().bind(new InetSocketAddress(port), 1024); //监听客户端连接请求 serverChannel.register(selector, SelectionKey.OP_ACCEPT); //标记服务器已开启 started = true; System.out.println("Server: start, port " + port); } catch (IOException e) { e.printStackTrace(); System.exit(1); } } public void stop() { started = false; } @Override public void run() { //循环遍历selector while (started) { try { //无论是否有读写事件发生,selector每隔1s被唤醒一次 selector.select(1000); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> it = keys.iterator(); SelectionKey key; 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(); } } //selector关闭后会自动释放里面管理的资源 if (selector != null) try { selector.close(); } catch (Exception e) { e.printStackTrace(); } } private void handleInput(SelectionKey key) throws IOException { if (key.isValid()) { //处理新接入的请求消息 if (key.isAcceptable()) { ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); //通过ServerSocketChannel的accept创建SocketChannel实例 //完成该操作意味着完成TCP三次握手,TCP物理链路正式建立 SocketChannel sc = ssc.accept(); //设置为非阻塞的 sc.configureBlocking(false); //注册处理读就绪事件 sc.register(selector, SelectionKey.OP_READ); } // 读就绪事件触发读消息 if (key.isReadable()) { SocketChannel sc = (SocketChannel) key.channel(); //创建ByteBuffer,并开辟一个1M的缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); //读取请求码流,返回读取到的字节数 int readBytes = sc.read(buffer); //读取到字节,对字节进行编解码 if (readBytes > 0) { //将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作 buffer.flip(); //根据缓冲区可读字节数创建字节数组 byte[] bytes = new byte[buffer.remaining()]; //将缓冲区可读字节数组复制到新建的数组中 buffer.get(bytes); String exp = new String(bytes, StandardCharsets.UTF_8); System.out.println("Server: receive " + exp); //处理数据 String result; try { result = "Server: send " + exp; } catch (Exception e) { result = "Error: " + e.getMessage(); } //发送应答消息 doWrite(sc, result); } else if (readBytes < 0) { //链路已经关闭,释放资源 key.cancel(); sc.close(); } } } } //异步发送应答消息 private void doWrite(SocketChannel channel, String response) throws IOException { //将消息编码为字节数组 byte[] bytes = response.getBytes(); //根据数组容量创建ByteBuffer ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length); //将字节数组复制到缓冲区 writeBuffer.put(bytes); //flip操作 writeBuffer.flip(); //发送缓冲区的字节数组 channel.write(writeBuffer); } }
2.2.3 测试类
public class Test {
public static void main(String[] args) throws InterruptedException {
Server.start();
//避免客户端先于服务器启动前执行代码
Thread.sleep(100);
//运行客户端
Client.start();
while (Client.send(new Scanner(System.in).nextLine()));
}
}
3. AIO(异步非阻塞IO)
3.1 AIO 的处理流程
与NIO
不同,AIO 基于 Proactor,进行读写操作时,只须直接调用 API
的read/write
方法。这两种方法都是异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read
方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write
方法传递的流写入完毕时,也会主动通知应用程序。 即可以理解为,read/write
方法都是异步的,完成后会主动调用数据接收方的回调函数
3.2 使用示例
3.2.1 客户端
-
客户端
public class Client { private static String DEFAULT_HOST = "127.0.0.1"; private static int DEFAULT_PORT = 10086; private static String QUIT_MSG = "q"; private static AsyncClientHandler clientHandle; public static void start() { start(DEFAULT_HOST, DEFAULT_PORT); } public static synchronized void start(String ip, int port) { if (clientHandle != null) { return; } clientHandle = new AsyncClientHandler(ip, port); new Thread(clientHandle, "Client").start(); } //向服务器发送消息 public static boolean sendMsg(String msg) { if (QUIT_MSG.equals(msg)) { clientHandle.stop(); return false; } clientHandle.sendMsg(msg); return true; } }
-
客户端处理者
public class AsyncClientHandler implements CompletionHandler<Void, AsyncClientHandler>, Runnable { private AsynchronousSocketChannel clientChannel; private String host; private int port; private CountDownLatch latch; public AsyncClientHandler(String host, int port) { this.host = host; this.port = port; try { //创建异步的客户端通道 clientChannel = AsynchronousSocketChannel.open(); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { //创建CountDownLatch等待 latch = new CountDownLatch(1); //发起异步连接操作,回调接口就是这个类本身,如果连接成功会回调completed方法 clientChannel.connect(new InetSocketAddress(host, port), this, this); try { latch.await(); } catch (InterruptedException e1) { e1.printStackTrace(); } try { clientChannel.close(); System.out.println("Client: close"); } catch (IOException e) { e.printStackTrace(); } } public void stop() { latch.countDown(); } //连接服务器成功 //意味着TCP三次握手完成 @Override public void completed(Void result, AsyncClientHandler attachment) { System.out.println("Client: connect to server"); } //连接服务器失败 @Override public void failed(Throwable exc, AsyncClientHandler attachment) { System.err.println("Client: connect to server fail"); exc.printStackTrace(); try { clientChannel.close(); latch.countDown(); } catch (IOException e) { e.printStackTrace(); } } //向服务器发送消息 public void sendMsg(String msg) { byte[] req = msg.getBytes(); ByteBuffer writeBuffer = ByteBuffer.allocate(req.length); writeBuffer.put(req); writeBuffer.flip(); //异步写 clientChannel.write(writeBuffer, writeBuffer, new ClientWriteHandler(clientChannel, latch)); } }
-
客户端写处理
public class ClientWriteHandler implements CompletionHandler<Integer, ByteBuffer> { private AsynchronousSocketChannel clientChannel; private CountDownLatch latch; public ClientWriteHandler(AsynchronousSocketChannel clientChannel, CountDownLatch latch) { this.clientChannel = clientChannel; this.latch = latch; } @Override public void completed(Integer result, ByteBuffer buffer) { //完成全部数据的写入 if (buffer.hasRemaining()) { clientChannel.write(buffer, buffer, this); } else { //读取数据 ByteBuffer readBuffer = ByteBuffer.allocate(1024); clientChannel.read(readBuffer, readBuffer, new ClientReadHandler(clientChannel, latch)); } } @Override public void failed(Throwable exc, ByteBuffer attachment) { System.err.println("Client: send fail"); try { clientChannel.close(); } catch (IOException e) { e.printStackTrace(); } finally { latch.countDown(); } } }
-
客户端读处理
public class ClientReadHandler implements CompletionHandler<Integer, ByteBuffer> { private AsynchronousSocketChannel clientChannel; private CountDownLatch latch; public ClientReadHandler(AsynchronousSocketChannel clientChannel, CountDownLatch latch) { this.clientChannel = clientChannel; this.latch = latch; } @Override public void completed(Integer result, ByteBuffer buffer) { buffer.flip(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); String body; body = new String(bytes, StandardCharsets.UTF_8); System.out.println("Client: receive " + body); } @Override public void failed(Throwable exc, ByteBuffer attachment) { System.err.println("Client: receive fail"); try { clientChannel.close(); } catch (IOException e) { e.printStackTrace(); } finally { latch.countDown(); } } }
3.2.2 服务端
-
服务器
public class Server { private static int DEFAULT_PORT = 10086; private static AsyncServerHandler SERVER_HANDLER; public static void start() { start(DEFAULT_PORT); } public static synchronized void start(int port) { if (SERVER_HANDLER != null) { System.out.println("Server: already start"); return; } SERVER_HANDLER = new AsyncServerHandler(port); new Thread(SERVER_HANDLER, "Server").start(); } }
-
服务端处理者
public class AsyncServerHandler implements Runnable { public CountDownLatch latch; public AsynchronousServerSocketChannel serverSocketChannel; public AsyncServerHandler(int port) { try { //创建服务端通道 serverSocketChannel = AsynchronousServerSocketChannel.open(); //绑定端口 serverSocketChannel.bind(new InetSocketAddress(port)); System.out.println("Server: start, port " + port); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { // CountDownLatch 初始化 // 作用:在完成一组正在执行的操作之前,允许当前的线程一直阻塞,防止服务端执行完成后退出, 也可以使用 while(true) + sleep latch = new CountDownLatch(1); //用于接收客户端的连接 serverSocketChannel.accept(this, new AcceptHandler()); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } }
-
服务端接受连接处理
public class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncServerHandler> { private static int CLIENT_COUNT = 0; @Override public void completed(AsynchronousSocketChannel channel, AsyncServerHandler attachment) { //继续接受其他客户端的请求 CLIENT_COUNT++; System.out.println("Server: client num " + CLIENT_COUNT); attachment.serverSocketChannel.accept(attachment, this); //创建新的Buffer ByteBuffer buffer = ByteBuffer.allocate(1024); //异步读 第三个参数为接收消息回调的业务Handler channel.read(buffer, buffer, new ReadHandler(channel)); } @Override public void failed(Throwable exc, AsyncServerHandler serverHandler) { exc.printStackTrace(); serverHandler.latch.countDown(); } }
-
服务端读处理
public class ReadHandler implements CompletionHandler<Integer, ByteBuffer> { //用于读取半包消息和发送应答 private AsynchronousSocketChannel channel; public ReadHandler(AsynchronousSocketChannel channel) { this.channel = channel; } //读取到消息后的处理 @Override public void completed(Integer result, ByteBuffer buffer) { //flip操作 buffer.flip(); byte[] message = new byte[buffer.remaining()]; buffer.get(message); String expr = new String(message, StandardCharsets.UTF_8); System.out.println("Server: receive " + expr); String resultMsg; try { resultMsg = "Server: send " + expr; } catch (Exception e) { resultMsg = "Error:" + e.getMessage(); } //向客户端发送消息 doWrite(resultMsg); } //发送消息 private void doWrite(String result) { byte[] bytes = result.getBytes(); ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length); writeBuffer.put(bytes); writeBuffer.flip(); //异步写数据 参数与前面的read一样 channel.write(writeBuffer, writeBuffer, new WriteHandler(channel)); } @Override public void failed(Throwable exc, ByteBuffer attachment) { try { this.channel.close(); } catch (IOException e) { e.printStackTrace(); } } }
-
服务端写处理
public class WriteHandler implements CompletionHandler<Integer, ByteBuffer> { private AsynchronousSocketChannel channel; public WriteHandler(AsynchronousSocketChannel channel) { this.channel = channel; } @Override public void completed(Integer result, ByteBuffer buffer) { //如果没有发送完,就继续发送直到完成 if (buffer.hasRemaining()) { channel.write(buffer, buffer, this); } else { //发送完毕,准备读取数据 ByteBuffer readBuffer = ByteBuffer.allocate(1024); //异步读 第三个参数为接收消息回调的业务 Handler channel.read(readBuffer, readBuffer, new ReadHandler(channel)); } } @Override public void failed(Throwable exc, ByteBuffer attachment) { try { channel.close(); } catch (IOException ignored) { } } }
3.2.3 测试类
public class Test {
public static void main(String[] args) throws Exception {
//运行服务器
Server.start();
//避免客户端先于服务器启动前执行代码
Thread.sleep(100);
//运行客户端
Client.start();
System.out.println("please input:");
Scanner scanner = new Scanner(System.in);
while (Client.sendMsg(scanner.nextLine()));
scanner.close();
}
}