2021-10-02 周六
## 緣起
最近在看《Java NIO》這本書,書中詳細講解了`jdk1.4`內提供的關于實現`nio`的`API`。因為閱讀后,發現對于NIO還是學習的不夠深入,之前也僅僅是學習了Java的文件IO和Socket編程,再者也是用`Netty`框架編寫NIO代碼,并未用Java提供的NIO實踐。借此機會,把Java關于網絡IO的發展給整理清楚,并編寫Java代碼示例,加深理解!!!
IO其實分為文件IO和流IO,這里討論的是流IO,也就是Socket的IO。
## 圖解Java網絡IO發展歷程

* 1996年1月發布`jdk1.0`版本,支持Java BIO的`Socket`編程。
* *2001年1月`Linux`內核發布2.4版本,支持NIO,非阻塞IO。*
* 2002年2月發布`jdk1.4`版本,支持Java NIO的`SocketChannel`編程,支持非阻塞。
* 2011年7月發布`jdk1.7`版本,支持Java AIO的`AsynchronousServerSocketChannel`編程。
## Java實現BIO(阻塞)

### SocketServerDemo 服務端
``` java
package org.mango.demo.bio;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 1連接1線程模型(線程資源有限,不適用)
* 將連進來的連接交給新的線程處理,這樣主線程負責處理客戶端連接,工作線程處理連接數據讀取和寫入
* 支持客戶端多連接
* @Description socket server 編寫的bio服務端 jdk1.0
* @Date 2021-10-02 15:19
* @Created by mango
*/
public class SocketServerDemo {
public static void main(String[] args) throws IOException {
// jdk1.0 版本,基于ServerSocket
ServerSocket serverSocket = new ServerSocket();
int port = 9000;
serverSocket.bind(new InetSocketAddress(port));
System.out.println("server listen on port " + port);
while (true) {
// 調用accept()方法,會阻塞
Socket socket = serverSocket.accept();
System.out.println(socket + " connect success!!");
// 將連進來的連接交給新的線程處理,這樣主線程負責處理客戶端連接,工作線程處理連接數據讀取和寫入
// 支持客戶端多連接
new WorkThread(socket).start();
}
}
}
/**
* 工作線程
*/
class WorkThread extends Thread{
private Socket socket;
public WorkThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
String tn = Thread.currentThread().getName();
String sn = socket.toString();
// 將接收到的數據后跟 處理線程名稱 發送給客戶端
boolean loop = true;
while(loop){
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
// 從inputStream流讀取數據,會阻塞
String data = dis.readUTF();
System.out.println(tn + ":" + sn + " receive data:" + data);
String rData = data + ":" +tn;
dos.writeUTF(rData);
} catch (IOException e) {
// 連接異常關閉,線程結束
try {
socket.close();
loop = false;
System.out.println(tn + ":" + sn + " exception close!");
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
/**
* 執行結果:
* server listen on port 9000
* Socket[addr=/127.0.0.1,port=60357,localport=9000] connect success!!
* Socket[addr=/127.0.0.1,port=60387,localport=9000] connect success!!
* Thread-0:Socket[addr=/127.0.0.1,port=60357,localport=9000] receive data:你好
* Thread-1:Socket[addr=/127.0.0.1,port=60387,localport=9000] receive data:中國
* Thread-0:Socket[addr=/127.0.0.1,port=60357,localport=9000] exception close!
* Thread-1:Socket[addr=/127.0.0.1,port=60387,localport=9000] exception close!
*/
```
執行結果打印:
``` java
server listen on port 9000
Socket[addr=/127.0.0.1,port=60357,localport=9000] connect success!!
Socket[addr=/127.0.0.1,port=60387,localport=9000] connect success!!
Thread-0:Socket[addr=/127.0.0.1,port=60357,localport=9000] receive data:你好
Thread-1:Socket[addr=/127.0.0.1,port=60387,localport=9000] receive data:中國
Thread-0:Socket[addr=/127.0.0.1,port=60357,localport=9000] exception close!
Thread-1:Socket[addr=/127.0.0.1,port=60387,localport=9000] exception close!
```
### SocketClientDemo 客戶端
``` java
package org.mango.demo.bio;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Scanner;
/**
* @Description TODO
* @Date 2021-10-02 23:15
* @Created by mango
*/
public class SocketClientDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket();
// 連接服務端
String host = "127.0.0.1";
int port = 9000;
socket.connect(new InetSocketAddress(host,port));
System.out.println("connect server " + host + ":" + port + " success!");
// 接收命令
Scanner scanner = new Scanner(System.in);
System.out.println("輸入exit:客戶端退出;其他為發送數據!");
// 發送數據
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
DataInputStream dis = new DataInputStream(socket.getInputStream());
boolean loop = true;
while(loop) {
System.out.print("請輸入:");
String input = scanner.next();
if("exit".equals(input)){
socket.close();
loop = false;
}else{
dos.writeUTF(input);
System.out.println("send success,data:" + input);
// 等待服務端發送數據,readUTF 方法會阻塞
String sData = dis.readUTF();
System.out.println("receive data:" + sData);
}
}
System.out.println("client exit!");
}
}
執行結果打印:
``` java
執行結果1:
connect server 127.0.0.1:9000 success!
輸入exit:客戶端退出;其他為發送數據!
請輸入:你好
send success,data:你好
receive data:你好:Thread-0
請輸入:exit
client exit!
```
``` java
執行結果2:
connect server 127.0.0.1:9000 success!
輸入exit:客戶端退出;其他為發送數據!
請輸入:中國
send success,data:中國
receive data:中國:Thread-1
請輸入:exit
client exit!
```
***總結:***
***1. Socket默認就是阻塞的,并不支持設置非阻塞。***
***2. accept() 和 從流讀取數據的read() 都是阻塞的。***
## Java實現NIO
### 單線程版本
`NonBlockServerDemo` - 服務端
``` java
package org.mango.demo.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* @Description (服務端非阻塞)主動遍歷 一個線程處理客服端連接,數據讀取
* @Date 2021-10-01 15:26
* @Created by mango
*/
public class NonBlockServerDemo {
public static void main(String[] args) throws IOException, InterruptedException {
// 存放連接進來的SocketChannel
List<SocketChannel> scList = new ArrayList<>();
// 服務端,使用ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
int port = 9000;
// 綁定本地端口
serverSocketChannel.bind(new InetSocketAddress(port));
System.out.println("server listen on " + port);
// 設置服務端通道非阻塞
serverSocketChannel.configureBlocking(false);
while (true){
// accept方法非阻塞,立即返回,null表示沒有client連接進來
SocketChannel sc = serverSocketChannel.accept();
if(null == sc){
Thread.sleep(200);
}else{
System.out.println(Thread.currentThread().getName() + " client enter " + sc.toString());
// 設置連接非阻塞
sc.configureBlocking(false);
// 維護到集合中
scList.add(sc);
}
// 遍歷連接,讀取數據
for(SocketChannel temp : scList){
// 創建一個緩存區
ByteBuffer buffer = ByteBuffer.allocate(1 * 1024);
// read方法非阻塞,返回值為數據長度
int len = temp.read(buffer);
String socketName = temp.toString();
if(len > 0){
System.out.println(Thread.currentThread().getName() + " " + socketName + " receive data size is " + len);
ByteBuffer data = ByteBuffer.allocate(len);
// 翻轉緩沖區,使得能被put
buffer.flip();
data.put(buffer);
System.out.println(Thread.currentThread().getName() + " " + socketName + " receive data is " + new String(data.array(), StandardCharsets.UTF_8));
}
}
}
}
}
```
執行結果:
``` java
/**
* 執行結果:
* server listen on 9000
* main client enter java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50039]
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50039] receive data size is 3
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50039] receive data is 123
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50039] receive data size is 6
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50039] receive data is 你好
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50039] receive data size is 6
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50039] receive data is 中國
* main client enter java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50308]
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50308] receive data size is 3
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50308] receive data is 123
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50308] receive data size is 3
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50308] receive data is xxx
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50039] receive data size is 4
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50039] receive data is exit
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50308] receive data size is 4
* main java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:50308] receive data is exit
*/
```
`NonBlockClientDemo` - 客戶端
``` java
package org.mango.demo.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* @Description 非阻塞測試 客戶端
* @Date 2021-10-01 15:48
* @Created by mango
*/
public class NonBlockClientDemo {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open();
String host = "127.0.0.1";
int port = 9000;
boolean isConnect = sc.connect(new InetSocketAddress(host,port));
if(isConnect){
System.out.println("connect server " + host + ":" + port + " success!");
// 從輸入流中讀取數據
Scanner scanner = new Scanner(System.in);
System.out.println("輸入exit:客戶端退出;其他為發送數據!");
boolean loop = true;
while (loop){
System.out.print("請輸入:");
String input = scanner.next();
ByteBuffer data = ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8));
// 發送數據
sc.write(data);
if("exit".equals(input)){
loop = false;
}
}
}
System.out.println("client exit!");
}
}
```
執行結果:
``` java
/**
* 執行結果1:
* connect server 127.0.0.1:9000 success!
* 輸入exit:客戶端退出;其他為發送數據!
* 請輸入:123
* 請輸入:你好
* 請輸入:中國
* 請輸入:exit
* client exit!
*/
```
``` java
/**
* 執行結果2:
* connect server 127.0.0.1:9000 success!
* 輸入exit:客戶端退出;其他為發送數據!
* 請輸入:123
* 請輸入:xxx
* 請輸入:exit
* client exit!
*/
```
### 多線程版本
`MTNonBlockServerDemo` - 服務端
``` java
package org.mango.demo.mtnio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
/**
* @Description (服務端非阻塞)主動遍歷 一線程1連接,數據讀取
* @Date 2021-10-01 15:26
* @Created by mango
*/
public class MTNonBlockServerDemo {
public static void main(String[] args) throws IOException, InterruptedException {
// 服務端,使用ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
int port = 9000;
// 綁定本地端口
serverSocketChannel.bind(new InetSocketAddress(port));
System.out.println("server listen on " + port);
// 設置服務端通道非阻塞
serverSocketChannel.configureBlocking(false);
while (true){
// accept方法非阻塞,立即返回,null表示沒有client連接進來
SocketChannel sc = serverSocketChannel.accept();
if(null == sc){
Thread.sleep(200);
}else{
System.out.println(Thread.currentThread().getName() + " client enter " + sc.toString());
// 設置連接非阻塞
sc.configureBlocking(false);
new WorkThread(sc).start();
}
}
}
}
/**
* 工作線程
*/
class WorkThread extends Thread{
private SocketChannel socketChannel;
public WorkThread(SocketChannel socketChannel){
this.socketChannel = socketChannel;
}
@Override
public void run() {
boolean loop = true;
String tn = Thread.currentThread().getName();
String sn = socketChannel.toString();
try{
// 將接收到的數據后跟 處理線程名稱 發送給客戶端
while(loop){
ByteBuffer buffer = ByteBuffer.allocate(1024);
// read() 不會阻塞
int len = socketChannel.read(buffer);
if(len > 0) {
System.out.println(tn + ":" + sn + " receive data size :" + len);
ByteBuffer data = ByteBuffer.allocate(len);
// 翻轉緩沖區,使得能被put
buffer.flip();
data.put(buffer);
String sData = new String(data.array(),StandardCharsets.UTF_8);
System.out.println(tn + ":" + sn + " receive data:" + sData);
String rData = sData + ":" + tn;
socketChannel.write(ByteBuffer.wrap(rData.getBytes(StandardCharsets.UTF_8)));
}
}
} catch (IOException e) {
// 異常退出
try {
socketChannel.close();
} catch (IOException e1) {
e1.printStackTrace();
}
System.out.println(tn + ":" + sn + " exception close!");
}
}
}
```
``` java
/**
* 執行結果:
* server listen on 9000
* main client enter java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54386]
* Thread-0:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54386] receive data size :3
* Thread-0:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54386] receive data:123
* Thread-0:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54386] receive data size :6
* Thread-0:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54386] receive data:你好
* main client enter java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54480]
* Thread-1:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54480] receive data size :6
* Thread-1:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54480] receive data:中國
* Thread-1:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54480] receive data size :3
* Thread-1:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54480] receive data:123
* Thread-1:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54480] receive data size :4
* Thread-1:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54480] receive data:exit
* Thread-1:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54480] exception close!
* Thread-0:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54386] receive data size :4
* Thread-0:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54386] receive data:exit
* Thread-0:java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:54386] exception close!
*/
```
`MTNonBlockClientDemo` - 客戶端
``` java
package org.mango.demo.mtnio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* @Date 2021-10-03 15:26
* @Created by mango
*/
public class MTNonBlockClientDemo {
public static void main(String[] args) throws IOException, InterruptedException {
SocketChannel sc = SocketChannel.open();
String host = "127.0.0.1";
int port = 9000;
sc.connect(new InetSocketAddress(host,port));
// 設置非阻塞
sc.configureBlocking(false);
System.out.println("connect server " + host + ":" + port + " success!");
// 從輸入流中讀取數據
Scanner scanner = new Scanner(System.in);
System.out.println("輸入exit:客戶端退出;其他為發送數據!");
boolean loop = true;
while (loop){
System.out.print("請輸入:");
String input = scanner.next();
ByteBuffer data = ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8));
// 發送數據
sc.write(data);
if("exit".equals(input)){
loop = false;
}
}
System.out.println("client exit");
}
}
```
執行結果:
``` java
/**
* 執行結果1:
* connect server 127.0.0.1:9000 success!
* 輸入exit:客戶端退出;其他為發送數據!
* 請輸入:123
* 請輸入:你好
* 請輸入:exit
* client exit
*/
```
``` java
/**
* 執行結果2:
* connect server 127.0.0.1:9000 success!
* 輸入exit:客戶端退出;其他為發送數據!
* 請輸入:中國
* 請輸入:123
* 請輸入:exit
* client exit
*/
```
***總結:***
***1. 通過channel的configureBlocking(flase)設置連接為非阻塞后,accept()或者read()方法就會直接返回***
***2. 1連接1線程雖然能提高服務端處理客戶端并發連接數,但是線程資源有限,不易多開。因此多線程版本再優化就可以放到線程池管理。***
***3. 將線程用線程池管理起來,就能改為線程池版本非阻塞IO模型。***
``` java
// 創建工作線程池
ExecutorService executorService = Executors.newFixedThreadPool(8);
// 提交到線程池
executorService.submit(new WorkThread(sc));
```
### 多路復用器版本

`SelectNIOServerDemo` - 服務端
``` java
package org.mango.demo.selectnio;
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.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
/**
* @Description 多路復用器版本 服務端
* @Date 2021-10-03 13:40
* @Created by mango
*/
public class SelectNIOServerDemo {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
int port = 9000;
serverSocketChannel.bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
System.out.println("server listen on port " + port);
// 設置多路復用器
Selector selector = Selector.open();
// 將serverSocketChannel的accept事件注冊到多路復用器
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
// 查看selector.select()是否有事件準備就緒
selector.select();
Set<SelectionKey> selectionKeySet = selector.selectedKeys();
Iterator<SelectionKey> iterable = selectionKeySet.iterator();
while(iterable.hasNext()){
SelectionKey selectionKey = iterable.next();
// 將事件remove掉,防止重復處理
iterable.remove();
if(selectionKey.isAcceptable()){
// 得到連接的通道
SocketChannel socketChannel = serverSocketChannel.accept();
String socketName = socketChannel.toString();
System.out.println(socketName + " connect success!");
// 設置為非阻塞
socketChannel.configureBlocking(false);
// 注冊讀寫事件到多路復用器
socketChannel.register(selector,SelectionKey.OP_READ);
// socketChannel.register(selector,SelectionKey.OP_WRITE);
}else if(selectionKey.isReadable()){
// 得到可讀的通道
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
String socketName = socketChannel.toString();
try {
// 讀取數據并回寫數據給客戶端
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = socketChannel.read(buffer);
if (len == -1) {
System.out.println(socketName + " closed!");
}
if (len > 0) {
ByteBuffer data = ByteBuffer.allocate(len);
buffer.flip();
data.put(buffer);
String sData = new String(data.array(), StandardCharsets.UTF_8);
System.out.println(socketName + " receive data:" + sData);
// 回寫數據
String tn = Thread.currentThread().getName();
String rData = sData + " - " + tn;
socketChannel.write(ByteBuffer.wrap(rData.getBytes(StandardCharsets.UTF_8)));
}
}catch (Exception e){
// 異常推出
socketChannel.close();
System.out.println(socketName + " exception close!");
}
}
}
}
}
}
```
執行結果:
``` java
/**
* 執行結果:
* server listen on port 9000
* java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:57934] connect success!
* java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:57934] receive data:123
* java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:57934] receive data:你好
* java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:57934] receive data:中國
* java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:58056] connect success!
* java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:58056] receive data:你呀
* java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:58056] receive data:搞毛線哦
* java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:58056] receive data:exit
* java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:58056] exception close!
* java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:57934] receive data:exit
* java.nio.channels.SocketChannel[connected local=/127.0.0.1:9000 remote=/127.0.0.1:57934] exception close!
*/
```
`SelectNIOClientDemo` - 服務端
``` java
package org.mango.demo.selectnio;
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.StandardCharsets;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
/**
* @Date 2021-10-03 15:26
* @Created by mango
*/
public class SelectNIOClientDemo {
public static void main(String[] args) throws IOException, InterruptedException {
SocketChannel sc = SocketChannel.open();
String host = "127.0.0.1";
int port = 9000;
sc.connect(new InetSocketAddress(host,port));
// 設置非阻塞
sc.configureBlocking(false);
System.out.println("connect server " + host + ":" + port + " success!");
// 設置多路復用器,注冊讀事件
Selector selector = Selector.open();
sc.register(selector, SelectionKey.OP_READ);
sc.register(selector, SelectionKey.OP_WRITE);
// 從輸入流中讀取數據
Scanner scanner = new Scanner(System.in);
System.out.println("輸入exit:客戶端退出;其他為發送數據!");
boolean loop = true;
while (loop){
// 查看多路復用器
selector.select();
Set<SelectionKey> selectionKeySet = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeySet.iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
// 移除掉事件,防止重復處理
iterator.remove();
if(selectionKey.isWritable()){
System.out.print("請輸入:");
String input = scanner.next();
ByteBuffer data = ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8));
// 發送數據
sc.write(data);
if("exit".equals(input)){
loop = false;
}
}else if(selectionKey.isReadable()){
// 得到可讀的通道
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
String socketName = socketChannel.toString();
// 讀取數據并回寫數據給客戶端
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = socketChannel.read(buffer);
if(len == -1){
System.out.println(socketName + " closed!");
}
if(len > 0){
ByteBuffer data = ByteBuffer.allocate(len);
buffer.flip();
data.put(buffer);
String sData = new String(data.array(), StandardCharsets.UTF_8);
System.out.println(socketName + " receive data:" + sData);
}
}
}
}
// 關閉資源
selector.close();
sc.close();
System.out.println("client exit");
}
}
```
執行結果:
``` java
/**
* 執行結果1:
* connect server 127.0.0.1:9000 success!
* 輸入exit:客戶端退出;其他為發送數據!
* 請輸入:你呀
* 請輸入:搞毛線哦
* 請輸入:exit
* client exit
*/
```
``` java
/**
* 執行結果2:
* connect server 127.0.0.1:9000 success!
* 輸入exit:客戶端退出;其他為發送數據!
* 請輸入:123
* 請輸入:你好
* 請輸入:中國
* 請輸入:exit
* client exit
*/
```
***總結***
***1. 使用Selector選擇器來實現多路復用器,調用select()方法得到就緒事件。***
***2. socket關閉時,多路復用器會收到read事件,read到的字節-1。***
- Redis來回摩擦
- redis的數據結構SDS和DICT
- redis的持久化和事件模型
- Java
- 從何而來之Java IO
- 發布Jar包到公共Maven倉庫
- Java本地方法調用
- 面試突擊
- Linux
- Nginx
- SpringBoot
- Springboot集成Actuator和SpringbootAdminServer監控
- SpringCloud
- Spring Cloud初識
- Spring Cloud的5大核心組件
- Spring Cloud的注冊中心
- Spring Cloud注冊中心之Eureka
- Spring Cloud注冊中心之Consul
- Spring Cloud注冊中心之Nacos
- Spring Cloud的負載均衡之Ribbon
- Spring Cloud的服務調用之Feign
- Spring Cloud的熔斷器
- Spring Cloud熔斷器之Hystrix
- Spring Cloud的熔斷器監控
- Spring Cloud的網關
- Spring Cloud的網關之Zuul
- Spring Cloud的配置中心
- Spring Cloud配置中心之Config Server
- Spring Cloud Config配置刷新
- Spring Cloud的鏈路跟蹤
- Spring Cloud的鏈路監控之Sleuth
- Spring Cloud的鏈路監控之Zipkin
- Spring Cloud集成Admin Server
- Docker
- docker日常基本使用
- docker-machine的基本使用
- Kubernetes
- kubernetes初識
- kubeadm安裝k8s集群
- minikube安裝k8s集群
- k8s的命令行管理工具
- k8s的web管理工具
- k8s的相關發行版
- k3s初識及安裝
- rancher的安裝及使用
- RaspberryPi
- 運維
- 域名證書更新
- 騰訊云主機組建內網
- IDEA插件開發
- 第一個IDEA插件hello ide開發
- 千呼萬喚始出來的IDEA筆記插件mdNote
- 大剛學算法
- 待整理
- 一些概念和知識點
- 位運算
- 數據結構
- 字符串和數組
- LC242-有效的字母異位詞
- 鏈表
- LC25-K個一組翻轉鏈表
- LC83-刪除有序單鏈表重復的元素
- 棧
- LC20-有效的括號
- 隊列
- 雙端隊列
- 優先隊列
- 樹
- 二叉樹
- 二叉樹的遍歷
- 二叉樹的遞歸序
- 二叉樹的前序遍歷(遞歸)
- 二叉樹的前序遍歷(非遞歸)
- 二叉樹的中序遍歷(遞歸)
- 二叉樹的中序遍歷(非遞歸)
- 二叉樹的后序遍歷(遞歸)
- 二叉樹的后序遍歷(非遞歸)
- 二叉樹的廣度優先遍歷(BFS)
- 平衡二叉樹
- 二叉搜索樹
- 滿二叉樹
- 完全二叉樹
- 二叉樹的打印(二維數組)
- 樹的序列化和反序列化
- 前綴樹
- 堆
- Java系統堆優先隊列
- 集合數組實現堆
- 圖
- 圖的定義
- 圖的存儲方式
- 圖的Java數據結構(鄰接表)
- 圖的表達方式及對應場景創建
- 圖的遍歷
- 圖的拓撲排序
- 圖的最小生成樹之Prim算法
- 圖的最小生成樹之Kruskal算法
- 圖的最小單元路徑之Dijkstra算法
- 位圖
- Java實現位圖
- 并查集
- Java實現并查集
- 滑動窗口
- 單調棧
- 排序
- 冒泡排序BubbleSort
- 選擇排序SelectSort
- 插入排序InsertSort
- 插入排序InsertXSort
- 歸并排序MergeSort
- 快速排序QuickSort
- 快速排序優化版QuickFastSort
- 堆排序HeapSort
- 哈希Hash
- 哈希函數
- guava中的hash函數
- hutool中的hash函數
- 哈希表實現
- Java之HashMap的實現
- Java之HashSet的實現
- 一致性哈希算法
- 經典問題
- 荷蘭國旗問題
- KMP算法
- Manacher算法
- Go