BIO即Block IO,阻塞式IO。
網(wǎng)絡(luò)編程的基本模型是Client/Server模型,也就是兩個(gè)進(jìn)程之間進(jìn)行相互通信,其中服務(wù)端提供位置信息(綁定的IP地址和監(jiān)聽(tīng)端口),客戶端通過(guò)連接操作向服務(wù)端監(jiān)聽(tīng)的地址發(fā)起連接請(qǐng)求,通過(guò)三次握手建立連接,如果連接建立成功,雙方就可以通過(guò)網(wǎng)絡(luò)套接字(Socket)進(jìn)行通信。
在基于傳統(tǒng)同步阻塞模型開(kāi)發(fā)中,ServerSocket負(fù)責(zé)綁定IP地址,啟動(dòng)監(jiān)聽(tīng)端口:Socket負(fù)責(zé)發(fā)起連接操作。連接成功之后,雙方通過(guò)輸入和輸出流進(jìn)行同步阻塞式通信。
傳統(tǒng)阻塞式IO
BIO服務(wù)端通信模型:采用BIO通信模型的服務(wù)端,通常由一個(gè)獨(dú)立的Acceptor線程負(fù)責(zé)監(jiān)聽(tīng)客戶端的連接,它接收到客戶端連接請(qǐng)求之后為每個(gè)客戶端創(chuàng)建一個(gè)新的線程進(jìn)行鏈路處理,處理完之后,通過(guò)輸出流返回應(yīng)答給客戶端,線程銷毀。
該模型最大的問(wèn)題就是缺乏彈性伸縮能力,當(dāng)客戶端并發(fā)訪問(wèn)量則增加后,服務(wù)端的線程個(gè)數(shù)和客戶端并發(fā)訪問(wèn)數(shù)呈1:1的正比關(guān)系,由于線程是java虛擬機(jī)非常寶貴的系統(tǒng)資源,當(dāng)線程數(shù)膨脹之后,系統(tǒng)的性能 將急劇下降,隨著并發(fā)訪問(wèn)量的繼續(xù)增大,系統(tǒng)會(huì)發(fā)生線程堆棧溢出、創(chuàng)建新線程失敗等問(wèn)題,并最終導(dǎo)致進(jìn)程宕機(jī)或者僵死,不能對(duì)外提供服務(wù)。
傳統(tǒng)的BIO
代碼演示:
服務(wù)端
public class TimeServer {
public static void main(String[] args) {
int port = 8081;
if(args!=null&&args.length>0){
try{
port = Integer.valueOf(args[0]);
}catch (Exception e){
e.printStackTrace();
}
}
ServerSocket serverSocket = null;
try{
serverSocket = new ServerSocket(port);
System.out.println("服務(wù)器已經(jīng)啟動(dòng)--端口號(hào):"+port);
Socket socket = null;
while (true){
socket = serverSocket.accept();
new Thread(new TimeServerHandler(socket)).start();
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(serverSocket!=null){
System.out.println("服務(wù)器關(guān)閉");
try{
serverSocket.close();
}catch (Exception e1){
e1.printStackTrace();
}
serverSocket = null;
}
}
}
}
服務(wù)端處理器
public class TimeServerHandler implements Runnable {
public TimeServerHandler(Socket socket) {
this.socket = socket;
}
private 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("服務(wù)器收到消息:"+body);
currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)? new Date(System.currentTimeMillis()).toString():"BAD ORDER";
out.println(currentTime);
}
}catch (Exception e){
e.printStackTrace();
if(in!=null){
try{
in.close();
}catch (Exception e1){
e1.printStackTrace();
}
}
if(out!=null){
out.close();
out = null;
}
if(this.socket!=null){
try{
this.socket.close();
}catch (Exception e1){
e.printStackTrace();
}
this.socket = null;
}
}
}
}
客戶端
public class TimeClient {
public static void main(String[] args) {
int port = 8081;
if(args!=null&&args.length>0){
try{
port = Integer.valueOf(args[0]);
}catch (Exception e){
e.printStackTrace();
}
}
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try{
socket = new Socket("127.0.0.1",port);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(),true);
out.println("QUERY TIME ORDER");
System.out.println("發(fā)送命令成功");
String resp = in.readLine();
System.out.println("收到的消息:"+resp);
}catch (Exception e){
e.printStackTrace();
}finally {
if(out!=null){
out.close();
out = null;
}
if(in!= null){
try{
in.close();
}catch (Exception e2){
e2.printStackTrace();
}
in = null;
}
if(socket!= null){
try{
socket.close();
}catch (Exception e2){
e2.printStackTrace();
}
socket = null;
}
}
}
}


傳統(tǒng)的BIO每當(dāng)一個(gè)新的客戶端請(qǐng)求接入時(shí),服務(wù)端必須創(chuàng)建一個(gè)新的線程處理接入的客戶端鏈路,一個(gè)線程只能處理一個(gè)客戶端連接。在高性能服務(wù)器應(yīng)用領(lǐng)域,往往需要面向成千上萬(wàn)個(gè)客戶端的并發(fā)連接,所以這種模型肯定無(wú)法滿足高性能高并發(fā)的 場(chǎng)景。
偽異步IO編程
偽異步的原理就是后端通過(guò)一個(gè)線程池來(lái)處理過(guò)個(gè)客戶端的請(qǐng)求接入,形成客戶端個(gè)數(shù)M:線程池最大線程數(shù)N的比例關(guān)系,其中M可以遠(yuǎn)遠(yuǎn)大于N。通過(guò)線程池可以靈活的調(diào)配線程資源,設(shè)置線程的最大值,防止由于海量并發(fā)接入導(dǎo)致線程耗盡。
具體實(shí)現(xiàn):當(dāng)有新的客戶端接入時(shí),將客戶端的Socket封裝成一個(gè)Task(該任務(wù)實(shí)現(xiàn)Runnable接口)投遞到后端的線程池中進(jìn)行處理,JDK的線程池維護(hù)一個(gè)消息隊(duì)列和N個(gè)活躍線程,對(duì)消息隊(duì)列中的任務(wù)進(jìn)行處理。由于線程池可以設(shè)置消息隊(duì)列的大小和最大線程數(shù),因此,它的資源占用是可控的,無(wú)論多少個(gè)客戶端并發(fā)訪問(wèn),都不會(huì)導(dǎo)致資源的耗盡和宕機(jī)。
代碼演示:
線程池
public class TimeServerHandlerExecutePool {
private ExecutorService executorService;
public TimeServerHandlerExecutePool(int maxPoolSize,int queueSize){
this.executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
maxPoolSize,120L, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(queueSize));
}
public void execute(Runnable task){
executorService.execute(task);
}
}
server代碼和原來(lái)差不多,只是將原來(lái)的創(chuàng)建線程改為使用線程池來(lái)執(zhí)行這個(gè)任務(wù)。
serverSocket = new ServerSocket(port);
System.out.println("服務(wù)器已經(jīng)啟動(dòng)--端口號(hào):"+port);
Socket socket = null;
TimeServerHandlerExecutePool singleExecutor = new TimeServerHandlerExecutePool(50,10000);//創(chuàng)建IO任務(wù)線程池
while (true){
socket = serverSocket.accept();
singleExecutor.execute(new TimeServerHandler(socket));
}
偽異步IO弊端分析:
read
當(dāng)對(duì)Socket的輸入流進(jìn)行讀取操作的時(shí)候,它會(huì)一直阻塞下去,知道發(fā)生如下三種事件。
- 有數(shù)據(jù)可讀
- 可用數(shù)據(jù)已經(jīng)讀取完畢
- 發(fā)生空指針異?;蛘逫O異常
這意味著當(dāng)對(duì)方發(fā)送請(qǐng)求或者應(yīng)答消息比較緩慢,或者網(wǎng)絡(luò)傳輸較慢時(shí),讀取輸入流一方的通信線程將被長(zhǎng)時(shí)間阻塞,如果對(duì)方要60s才能夠?qū)?shù)據(jù)發(fā)送完畢,讀取一方的IO線程也將被同步阻塞60s,在此期間,其他接入消息只能在消息隊(duì)列中排隊(duì)。
write
當(dāng)調(diào)用OutputStream的write方法寫(xiě)輸出流的時(shí)候,它將會(huì)被阻塞,知道所有要發(fā)送的字節(jié)全部寫(xiě)入完畢,或者發(fā)生異常。學(xué)習(xí)過(guò)TCP/IP相關(guān)知識(shí)的人都知道,當(dāng)消息的接收方處理緩慢的時(shí)候,將不能及時(shí)的從TCP緩沖區(qū)讀取數(shù)據(jù),這將會(huì)導(dǎo)致發(fā)送方的TCP window size不斷減少,知道為0,雙方處于Keep-Alive狀態(tài),消息發(fā)送方將不能再向TCP緩沖區(qū)寫(xiě)入消息,這時(shí)如果采用的是同步阻塞IO,write操作將會(huì)被無(wú)線阻塞,知道TCP window size大于0或者發(fā)生IO異常。
通過(guò)對(duì)輸入和輸出流的API進(jìn)行分析,讀和寫(xiě)操作都是同步阻塞的,阻塞的時(shí)間取決于對(duì)方對(duì)方IO線程的處理速度和網(wǎng)絡(luò)IO的傳輸速度。本質(zhì)上來(lái)講,我們無(wú)法保證生產(chǎn)環(huán)境的網(wǎng)絡(luò)狀況和對(duì)端的應(yīng)用程序能足夠快,如果我們的應(yīng)用程序依賴對(duì)方的處理速度,它的可靠性就非常差。