一、BIO通訊模型
採用BIO通信模型的服務端,通常都會使用一個Acceptor線程負責監聽客戶端的連接!接收到客戶端的連接請求之後,爲每一個客戶端創建一個新的線程進行鏈路處理,處理完成之後,通過輸出流返回應答給客戶端!線程銷燬!典型的一請求一應答的通信模型!
弊端
該模型的最大問題就是缺乏彈性伸縮的能力,當客戶端併發量增加後,服務端的線程數和客戶端併發訪問數呈1:1的正比關係!當線程數膨脹之後系統的性能將急劇下降,隨着併發訪問的訪問量繼續增大,不能對外提供服務!
二、模擬時鐘服務請求(BIO方式) 僞異步IO操作
時間服務器 服務端
package com.netty.bio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @program: nodes-netty->TimeServer
* @description: BIO服務端
* @author: huangfu
* @date: 2019/11/25 12:40
**/
public class TimeServer {
/**
* 定義一個線程池
*/
private static final ThreadPoolExecutor fastTriggerPool = new ThreadPoolExecutor(50, 200, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
r -> new Thread(r, "TimeServer" + r.hashCode()));
public static void main(String[] args) {
int port = 8080;
ServerSocket serverSocket = null;
try{
serverSocket = new ServerSocket(port);
System.out.println("這個時間服務已經啓動了,端口是8080");
Socket socket = null;
while (true){
//等待獲取連接
socket = serverSocket.accept();
fastTriggerPool.execute(new TimeServerHandler(socket));
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(serverSocket != null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace( );
}
serverSocket = null;
}
}
}
}
class TimeServerHandler implements Runnable{
private Socket socket;
public TimeServerHandler(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;
}
System.out.println("客戶端發送請求:"+body );
String dateTimeStr = LocalDateTime.now( ).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
currentTime = "查詢時間".equals(body)?dateTimeStr:"END QUERY";
out.println(currentTime);
}
} catch (IOException e) {
e.printStackTrace( );
}finally {
if(in != null){
try {
in.close();
in = null;
} catch (IOException e) {
e.printStackTrace( );
}
}
if(out != null){
out.close();
out = null;
}
if(socket != null){
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace( );
}
this.socket = null;
}
}
}
}
時間服務器 客戶端
package com.netty.bio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* @program: nodes-netty->TimeClient
* @description: 時間客戶端調用
* @author: huangfu
* @date: 2019/11/25 13:08
**/
public class TimeClient {
public static void main(String[] args) throws IOException {
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try{
socket = new Socket("127.0.0.1",8080);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(),true);
out.println("查詢時間");
String timeDate = in.readLine( );
System.out.println(timeDate );
}catch (Exception e){
e.printStackTrace();
}finally {
if(out != null){
out.close();
}
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace( );
}
}
if(socket!=null){
socket.close();
}
}
}
}
僞異步IO分析
僞異步IO操作五大從根源上解決線程阻塞的問題,我們試着分析一下他的弊端:
JAVA輸入流的弊端
/**
* This method blocks until input data is available, end of file is detected, or an exception is thrown.
**/
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
當然爲了節省閱讀時間,我只摘錄出比較重要的一段,有興趣可以看看java源碼java.io.InputStream#read(byte[])
這句註釋的中文意思是,該方法會一直處於阻塞的狀態,除非有三種情況發生
- 有數據可讀
- 可用數據已經讀取完畢
- 發生異常
這意味着什麼?當對方發送的請求或者應答消息過慢時,再或者網絡傳輸慢,讀取輸入流的一方將被阻塞!知道上面那三種情況發生!
JAVA輸出流的弊端
當調用OutputStream的write方法,寫出輸出流的時候,他也會被阻塞,知道所有要發送的數據全部寫入完畢,或者發生異常。當消息接收方處理緩慢時,不能及時的從TCP緩衝區讀取數據,將會導致發送方的TCP window size不斷介紹,直到爲0.雙發處於Keep-Alive狀態;消息發送方加個不會向TCP緩衝區寫入任何數據,直至windiw size大於0或者發生異常!
JAVA BIO讀寫都是同步阻塞的,阻塞的時間依賴於雙方IO線程的處理速度和網絡速度!網絡不可靠,我們無法保證其處理速度,所以即使是僞異步IO線程,也無法保證不發生同步線程阻塞
所以這就是他不好的原因!那麼偉大的工程師們是如何引入NIO的概念呢?NIO又是如何解決的呢?歡迎關注作者呀!我們一點一點的去了解!
歡迎關注公衆號 關注公衆號,回覆架構師,提供各類技術的學習資料提供參閱!