IO程式設計和NIO程式設計簡介
個人部落格:haichenyi.com。感謝關注
傳統的同步阻塞I/O通訊模型,導致的結果就是隻要有一方處理資料緩慢,都會影響另外一方的處理效能。按照故障設計原則,一方的處理出現問題,不應該影響到另外一方才對。但是,在同步阻塞的模式下面,這樣的情況是無法避免的,很難通過業務層去解決。既然同步無法避免,為了避免就產生了非同步。Netty框架就一個完全非同步非阻塞的I/O通訊方式
同步阻塞式I/O程式設計
簡單的來說,傳統同步阻塞的I/O通訊模式,伺服器端處理的方式是,每當有一個新使用者接入的時候,就new一個新的執行緒,一個執行緒只能處理一個客戶端的連線,在高效能方面,併發高的情景下無法滿足。虛擬碼如下:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 海晨憶
* @date 2018/2/9
* @desc
*/
public class SocketServer {
private int port = 8080;
private Socket socket = null;
public SocketServer(int port) {
this.port = port;
}
public void connect() {
ServerSocket server = null;
try {
server = new ServerSocket(port);
while (true) {
socket = server.accept();
new Thread(new Runnable() {
@Override
public void run() {
new TimerServerHandler(socket).run();
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//釋放資源
if (server != null) {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
server = null;
}
}
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* @author 海晨憶
* @date 2018/2/9
* @desc
*/
public class TimerServerHandler implements Runnable {
private Socket socket;
public TimerServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);
String currentTime = null;
String body = null;
while (true) {
body = in.readLine();
if (body == null)
break;
}
} catch (IOException e) {
e.printStackTrace();
//釋放in,out,socket資源
}
}
}
上面這個就是最原始的服務端IO的程式碼,這裡我就給出的是最簡化的,當有新的客戶端接入的時候,服務端是怎麼處理執行緒的,可以看出,每當有新的客戶端接入的時候,總是回新建立一個執行緒去服務這個新的客戶端
偽非同步式程式設計
後來慢慢演化出一個版本“偽非同步”模型,新增加一個執行緒池或者訊息佇列,滿足一個執行緒或者多個執行緒滿足N個客戶端,通過執行緒池可以靈活的呼叫執行緒資源。通過設定執行緒池的最大值,防止海量併發接入造成的執行緒耗盡,它的底層實現依然是同步阻塞模型,虛擬碼如下:
import com.example.zwang.mysocket.server.TimerServerHandler;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 海晨憶
* @date 2018/2/9
* @desc
*/
public class SocketServer {
private int port = 8080;
private Socket socket = null;
public SocketServer(int port) {
this.port = port;
}
private void connect() {
ServerSocket server = null;
try {
server = new ServerSocket(port);
TimeServerHandlerExecutePool executePool = new TimeServerHandlerExecutePool(50, 1000);
while (true) {
socket = server.accept();
executePool.execute(new TimerServerHandler(socket));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//釋放資源
}
}
}
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author 海晨憶
* @date 2018/2/9
* @desc
*/
public class TimeServerHandlerExecutePool {
private ExecutorService executor;
public TimeServerHandlerExecutePool(int maxPoolSize, int queueSize) {
executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), maxPoolSize,
120L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize));
}
public void execute(Runnable task) {
executor.execute(task);
}
}
“偽非同步”的程式碼和傳統同步的唯一區別就是在於,首先先建立了一個時間服務處理類的執行緒池,當有新的客戶端接入的時候,先將socket請求封裝成task,然後呼叫執行緒池的execute方法執行,從而避免了每一個新請求建立一個新執行緒。由於執行緒池和訊息佇列都是有限的,因此,無論客戶端的併發量多大,它都不會導致執行緒個數過於大,而造成的記憶體溢位。相對於傳統的同步阻塞,是一種改良。
但是他沒有從更本上解決同步的問題,偽非同步的問題在於,他還是有一方處理出現問題還是會影響到另一方。因為:
* 當對socket的輸入流進行讀取操作的時候,它會一直阻塞直到一下三種方式發生:
1. 有資料可讀
2. 可讀資料已經讀取完
3. 發生空指標或者I/O異常。
這意味者,當讀取inputstream方處理速度緩慢(不管是什麼原因造成的速度緩慢),另一方會一直同步阻塞,直到這一方把資料處理完.
* 當呼叫outputstream的write方法寫輸出流的時候,它將會被阻塞,直到所有要傳送的位元組全部寫入完畢,或者發生異常。學過TCP/IP相關知識的人都直到,當訊息的接收方處理訊息緩慢,不能及時的從TCP緩衝區讀取資料,這將會導致傳送方的TCP緩衝區的size一直減少,直到0.緩衝區為0,那麼發訊息的一方將無法將訊息寫入緩衝區,直到緩衝區的size大於0
通過以上。我們瞭解到讀和寫的操作都是同步阻塞的,阻塞的時間取決於對方的I/O執行緒的處理速度和網路I/O的傳送速度。從本質上面看,我們無法保證對方的處理速度和網路傳送速度。如果,我們的程式依靠與對方的處理速度,那麼,他的可靠性將會非常差。
NIO程式設計
官方叫法new I/O,也就是新的IO程式設計,更多的人喜歡稱它為:Non-block IO即非阻塞IO。
與Socket和serverSocket類對應,NIO提供了SocketChannel和ServerSocketChannel兩種不同的套接字通道實現,這兩種都支援阻塞式程式設計和非阻塞式程式設計。開發人員可以根據自己的需求選擇合適的程式設計模式。一般低負載,低併發的應用程式選擇同步阻塞的方式以降低程式設計的複雜度。高負載,高併發的不用想了,非阻塞就是為了解決這個問題的
1. 緩衝區Buffer
Buffer是一個物件,它包含一些寫入或者讀出的資料。再NIO中加入buffer物件,體現了新庫和舊庫的一個重要區別。在面向流的io中,可以直接把資料讀取或者寫入到stream物件中。在NIO庫中,所有資料操作都是通過緩衝區處理的。
緩衝區實質上是一個陣列,通常是一個位元組陣列(ByteBuffer),基本資料型別除了boolean沒有,其他都有,如ShortBuffer,CharBuffer等等
2. 通道Channel
Channel是一個通道,雙向通道,網路資料都是通過Channel讀取,寫入的。是的,沒錯,Channel它既可以進行讀操作,也可以進行寫操作。而流只能是一個方向。只能讀操作或者只能寫操作,而channel是全雙工,讀寫可以同時進行。channel可以分為兩大類:網路讀寫的SelectableChannel和檔案操作的FileChannel。我們前面提到的SocketChannel和ServerSocketChannel都是SelectableChannel的子類。
3. 多路複用器Selector
selector多路複用器,他是java NIO程式設計的基礎,熟練的掌握selector對於NIO程式設計至關重要。多路複用器提供選擇已經就緒的任務的能力。簡單的講就是他會不斷的輪詢註冊的channel,如果一個Channel發生了讀寫操作,這個Chnnel就會處於就緒狀態,會被selector輪詢出來,通過SelectorKey獲取就緒Channel集合,進行後續的IO操作。一個selector對應多個Channel
由於原生NIO編碼比較麻煩和複雜,我這裡就給出了思路的虛擬碼。下一篇我們將用NIO中的Netty框架實現Socket通訊,編碼簡單,一行程式碼解決煩人粘包、拆包問題。
/**
* 服務端nio過程的虛擬碼
*
* @param port 埠號
* @throws IOException IOException
*/
private void init(int port) throws IOException {
//第一步:開啟ServerSocketChannel,用於監聽客戶端連線,它是所有客戶端連線的父管道
ServerSocketChannel socketChannel = ServerSocketChannel.open();
//第二步:監聽繫結埠,設定連線模式為非阻塞模式,
socketChannel.socket().bind(new InetSocketAddress(InetAddress.getByName("IP"), port));
socketChannel.configureBlocking(false);
//第三步:建立Reactor執行緒,建立多路複用器,並啟動執行緒。
Selector selector = Selector.open();
new Thread().start();
//第四步:將ServerSocketChannel註冊到Reactor執行緒的多路複用器上,監聽accept事件
SelectionKey key = socketChannel.register(selector, SelectionKey.OP_ACCEPT/*,ioHandler*/);
//第五步:多路複用器線上程run方法的無線迴圈體內輪詢準備就緒的key
int num = selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()) {
SelectionKey next = it.next();
//deal with io event...
}
//第六步:多路複用器檢測到有新客戶端接入,處理新的接入請求,完成TCP三次握手,建立物理鏈路
SocketChannel channel = socketChannel.accept();
//第七步:設定客戶端為非阻塞模式
channel.configureBlocking(false);
channel.socket().setReuseAddress(true);
//第八步:將新接入的客戶端註冊到reactor執行緒的多路複用器上,監聽讀操作,讀取客戶端傳送的訊息
SelectionKey key1 = socketChannel.register(selector, SelectionKey.OP_ACCEPT/*,ioHandler*/);
//第九步:非同步讀取客戶端訊息到緩衝區,
/*int readNumber = channel.read("receivebuff");*/
//第十步:對byteBuffer進行編解碼,如果有半包資訊指標reset,繼續讀取到後續的報文,將解碼成功訊息封裝成task,投遞到業務執行緒池,進行業務邏輯編排
Object massage = null;
while (buff.hasRemain()) {
buff.mark();
Object massage1 = decode(btyeBuffer);
if (massage1 == null) {
byteBuffer.reset();
break;
}
massageList.add(massage1);
}
if (!byteBuffer.hasRemain()) {
byteBuffer.clean();
} else {
byteBuffer.compact();
}
if (massageList != null && !massageList.isEmpty()) {
for (Object massage3 : massageList){
handlerTask(massage3);
}
}
//第十一步:將POJO物件encode成ByteBuff,呼叫SocketChannel的非同步write介面,將非同步訊息傳送到客戶端
socketChannel.write(buffer);
}
結束!!!
相關文章
- NIO程式設計介紹程式設計
- 網路程式設計NIO:BIO和NIO程式設計
- IO和socket程式設計程式設計
- Java網路程式設計和NIO詳解3:IO模型與Java網路程式設計模型Java程式設計模型
- windows程式設計簡介Windows程式設計
- shell程式設計簡介程式設計
- Java網路程式設計和NIO詳解5:Java 非阻塞 IO 和非同步 IOJava程式設計非同步
- Linux Shell程式設計(1)——shell程式設計簡介Linux程式設計
- Java NIO程式設計示例Java程式設計
- shell程式設計—簡介(一)程式設計
- 結對程式設計簡介程式設計
- 【網路程式設計】阻塞IO程式設計的坑程式設計
- 網路程式設計框架t-io的程式設計基本知識介紹程式設計框架
- Nio程式設計模型總結程式設計模型
- Windows 程式設計簡介從C/C++到Windows程式設計Windows程式設計C++
- Java網路程式設計和NIO詳解9:基於NIO的網路程式設計框架NettyJava程式設計框架Netty
- Scala 簡介 [摘自 Scala程式設計 ]程式設計
- Linux Socket 程式設計簡介Linux程式設計
- WebGL程式設計指南(1)簡介Web程式設計
- BASH SHELL 程式設計簡介(轉)程式設計
- 01 Python3程式設計之程式設計語法簡介Python程式設計
- JAVA NIO程式設計入門(二)Java程式設計
- JAVA NIO程式設計入門(一)Java程式設計
- JAVA NIO 程式設計入門(三)Java程式設計
- NIO非阻塞程式設計小案例程式設計
- Socket 程式設計IO Multiplexing程式設計
- 使XML程式設計更簡單---JDOM介紹及程式設計指南 (轉)XML程式設計
- 函數語言程式設計簡介函數程式設計
- .NET泛型程式設計簡介 (轉)泛型程式設計
- Java 網路程式設計(TCP程式設計 和 UDP程式設計)Java程式設計TCPUDP
- 如何向新手程式設計師介紹程式設計?程式設計師
- 併發程式設計基礎——JMM簡介程式設計
- 響應式程式設計簡介之:Reactor程式設計React
- js DSL超程式設計簡單介紹JS程式設計
- JavaScript 模組化程式設計簡單介紹JavaScript程式設計
- 伯樂線上程式設計挑戰簡介程式設計
- Gtk+/Glade程式設計(一)--簡介程式設計
- Linux 程式設計工具簡單介紹Linux程式設計