從零開(kāi)始netty學(xué)習(xí)筆記之BIO

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;
            }
        }
    }
}
57D68050-8C99-40EE-8760-18878851D2D7.png
55CC2938-2F41-4307-A8FF-265BF1165DE7.png

傳統(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ì)方的處理速度,它的可靠性就非常差。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、Netty基礎(chǔ)入門(mén) Netty是由JBOSS提供的一個(gè)java開(kāi)源框架。Netty提供異步的、事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)...
    我是嘻哈大哥閱讀 4,772評(píng)論 0 31
  • java中的幾種I/O模型 BIO通信模型 BIO通信模型圖 網(wǎng)絡(luò)編程的基本模型是C/S模型,兩個(gè)進(jìn)程之間進(jìn)行通信...
    adonisjph閱讀 611評(píng)論 0 1
  • 好天氣,只是身不在陽(yáng)光下。 漫長(zhǎng)的飛行中午結(jié)束,來(lái)到的地方是蘇南無(wú)錫。 抽了些時(shí)間,在太湖旁邊走了走,感受了...
    KAUNG閱讀 214評(píng)論 0 0
  • 感恩日記 感恩早上準(zhǔn)時(shí)起床5:50就醒,一看到群里有伙伴發(fā)冥想練習(xí),太棒,我就跟著練習(xí)去做,沒(méi)有老師的帶領(lǐng)下,顯得...
    生活就該甜甜蜜蜜閱讀 368評(píng)論 3 3

友情鏈接更多精彩內(nèi)容