JAVA原生JDK網絡編程 BIO/NIO
1 原生 JDK 網絡編程 BIO
傳統的同步阻塞模型開發中,ServerSocket 負責綁定 IP 地址,啟動監聽端口;Socket 負責發起連接操作。連接成功后,雙方通過輸入和輸出流進行同步阻塞式通信。傳統 BIO 通信模型:采用 BIO 通信模型的服務端,通常由一個獨立的 Acceptor 線程負責監聽客戶端的連接,它接收到客戶端連接請求之后為每個客戶端創建一個新的線程進行鏈路處理,處理完成后,通過輸出流返回應答給客戶端,線程銷毀。即典型的一請求一應答模型,同時數據的讀取寫入也必須阻塞在一個線程內等待其完成。
該模型最大的問題就是缺乏彈性伸縮能力,當客戶端并發訪問量增加后,服務端的線程個數和客戶端并發訪問數呈 1:1 的正比關系,Java 中的線程也是比較寶貴的系統資源,線程數量快速膨脹后,系統的性能將急劇下降,隨著訪問量的繼續增大,系統最終就 死- 掉- 了。為了改進這種一連接一線程的模型,我們可以使用線程池來管理這些線程,實現 1 個或多個線程處理 N 個客戶端的模型(但是底層還是使用的同步阻塞 I/O),通常被稱為“偽異步 I/O 模型“。
public class BioServerPool {
private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("server is started......");
while (true) {
executorService.submit(new ServerTask(serverSocket.accept()));
}
}
/**
* 服務端先實例化輸入流,再實例話輸出流,確保流通道的建立
*/
static class ServerTask implements Runnable {
private Socket socket;
public ServerTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
System.out.println("ServerSocket accept socket");
try (ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())) {
//接受消息
String message = inputStream.readUTF();
System.out.println(Thread.currentThread().getName() + " receive message: " + message);
//返回消息
outputStream.writeUTF("server success receive message");
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class BioClient {
/**
* 客戶端先實例化輸出流,再實例化輸入流,確保流通道的建立
*/
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
try (ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());) {
outputStream.writeUTF("Hello World");
outputStream.flush();
System.out.println("server response message :" + inputStream.readUTF());
} catch (Exception e) {
e.printStackTrace();
} finally {
socket.close();
}
}
}
2 原生 JDK 網絡編程- NIO
2.1 什么是 NIO?
NIO 庫是在 JDK 1.4 中引入的。NIO 彌補了原來的 I/O 的不足,它在標準 Java 代碼
中提供了高速的、面向塊的 I/O。NIO 翻譯成 no-blocking io 或者 new io 都說得通。
2.2 和 BIO 的主要區別
(1) 面向流與面向緩沖
Java NIO 和 IO 之間第一個最大的區別是,IO 是面向流的,NIO 是面向緩沖區的。Java IO面向流意味著每次從流中讀一個或多個字節,直至讀取所有字節,它們沒有被緩存在任何地方。此外,它不能前后移動流中的數據。如果需要前后移動從流中讀取的數據,需要先將它緩存到一個緩沖區。 Java NIO 的緩沖導向方法略有不同。數據讀取到一個它稍后處理的緩沖區,需要時可在緩沖區中前后移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩沖區中包含所有需要處理的數據。而且,需確保當更多的數據讀入緩沖區時,不要覆蓋緩沖區里尚未處理的數據。
(2) 阻塞與非阻塞 IO
Java IO 的各種流是阻塞的。這意味著,當一個線程調用 read() 或 write()時,該線程被
阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再干任何事情了。
Java NIO 的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目
前可用的數據,如果目前沒有數據可用時,就什么都不會獲取。而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。線程通常將非阻塞 IO 的空閑時間用于在其它通道上執行 IO 操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道(channel)。
2.3 NIO 三大核心組件
NIO 有三大核心組件:Selector 選擇器、Channel 管道、buffer 緩沖區。
(1) 一個線程對應一個Selector。
(2) 一個Selector可以監聽多個Channel。
(3) 每個Channel對應一個Buffer。
(4) 程序切換到哪個channel是由事件決定的。
(5) Selector會根據不同的事件,在各個通道上切換。
(6) Buffer是一個內存塊(塊IO比流IO效率高)。
(7) BIO輸入流、輸出流是分開的,不能雙向。Channel是雙向的,Buffer也是雙向的可讀可寫但是要flip切換。
2.4 Code
(1) NioClient
public class NioClient {
private static NioClientHandle nioClientHandle;
public static void main(String[] args) throws IOException {
start();
Scanner scanner = new Scanner(System.in);
while (sendMsg(scanner.next())) ;
}
//開啟客戶端
private static void start() {
nioClientHandle = new NioClientHandle("127.0.0.1", 8000);
new Thread(nioClientHandle, "client").start();
}
//發送消息
public static boolean sendMsg(String msg) throws IOException {
nioClientHandle.sendMsg(msg);
return true;
}
}
(2) NioClientHandle
package com.io.nio;
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.util.Iterator;
import java.util.Set;
public class NioClientHandle implements Runnable {
private String host;
private int port;
private volatile boolean started;
private Selector selector;
private SocketChannel socketChannel;
public NioClientHandle(String ip, int port) {
//1、綁定IP、端口
this.host = ip;
this.port = port;
try {
//2、打開選擇器和管道 并且定義為非阻塞模式
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
started = true;
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
doConnect();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (started) {
try {
//阻塞方法,沒有事件返回的時候回阻塞
selector.select();
//拿到處理的事件和迭代器
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
//循環處理該事件
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
handleInput(key);
it.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
//查看key和哪個管道綁定的
SocketChannel sc = (SocketChannel) key.channel();
//如果是連接事件,如果連接不成功那么直接退出
if (key.isConnectable()) {
if (!sc.finishConnect()) {
System.exit(1);
}
}
if (key.isReadable()) {
//定義 buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//把channel的數據寫入buffer
int read = sc.read(buffer);
if (read > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String rs = new String(bytes);
System.out.println("accept messasge: " + rs);
} else if (read < 0) {
sc.close();
key.cancel();
}
}
}
}
private void doConnect() throws IOException {
//如果連接成功
//如果連接不成功,注冊一個連接事件
if (socketChannel.connect(new InetSocketAddress(host, port))) {
} else {
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
}
public void sendMsg(String msg) throws IOException {
socketChannel.register(selector, SelectionKey.OP_READ);
byte[] bytes=msg.getBytes();
ByteBuffer buffer=ByteBuffer.allocate(bytes.length);
buffer.put(bytes);
buffer.flip();
socketChannel.write(buffer);
}
}
(3) NioServer
package com.io.nio;
public class NioServer {
private static NioServerHandle nioServerHandle;
public static void start() {
nioServerHandle = new NioServerHandle(8000);
new Thread(nioServerHandle, "Server").start();
}
public static void main(String[] args) {
start();
}
}
(4) NioServerHandle
package com.io.nio;
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 NioServerHandle implements Runnable {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private volatile boolean started;
public NioServerHandle(int port) {
try {
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
started = true;
System.out.println("服務器已啟動,端口號:" + port);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//循環遍歷selector
while (started) {
try {
//阻塞,只有當至少一個注冊的事件發生的時候才會繼續.
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.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();
}
}
}
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
//處理新接入的請求消息
if (key.isAcceptable()) {
//獲得關心當前事件的channel
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//通過ServerSocketChannel的accept創建SocketChannel實例
//完成該操作意味著完成TCP三次握手,TCP物理鏈路正式建立
SocketChannel sc = ssc.accept();
System.out.println("======socket channel 建立連接");
//設置為非阻塞的
sc.configureBlocking(false);
//連接已經完成了,可以開始關心讀事件了
sc.register(selector, SelectionKey.OP_READ);
}
//讀消息
if (key.isReadable()) {
System.out.println("======socket channel 數據準備完成," + "可以去讀==讀取=======");
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 message = new String(bytes, "UTF-8");
System.out.println("服務器收到消息:" + message);
//處理數據
String result = "Hello," + message;
//發送應答消息
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);
}
}
(5) 測試
智能推薦
JAVA(4)-BIO-NIO
1.BIO BlockingIO的意思。還是從最簡單的說起,過程參看前篇,python版,本篇就是用JAVA再寫了一遍。 1.1一服務器端,一個客戶端 1.2.一個服務器端,多個客戶端 2.NIO,非阻塞IO 1.BIO BlockingIO的意思。還是從最簡單的說起。 1.1一服務器端,一個客戶端 &nb...
Netty一:Java bio nio
Java支持的I/O模型 Java共支持3種網絡編程模型/IO模式:BIO、NIO、AIO JavaBIO:同步并阻塞(傳統阻塞型),服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷 JavaNIO:同步非阻塞,服務器實現模式為一個線程處理多個請求(連接),即客戶端發送的連接請求都會注冊到多路復用器上,多路復用...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
猜你喜歡
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...
styled-components —— React 中的 CSS 最佳實踐
https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...