《Nio系列二》- Bio實現(xiàn)時間查詢服務(wù)

在接下的文章中,將會分別使用Bio,Nio,Aio,Netty來實現(xiàn)時間查詢服務(wù)器,比較并分析各種版本的優(yōu)缺點。

Bio-客戶端版本

針對Bio模式下的不同的服務(wù)器版本,本節(jié)使用統(tǒng)一的客戶端版本,客戶端的處理邏輯如下:

  1. 根據(jù)hostname和port連接服務(wù)器 connect()方法
  2. 獲取socket的輸入輸出流,通過輸出流發(fā)送請求,通過輸入流讀取服務(wù)端的響應(yīng)
  3. 只要socket沒有斷開,就一直可以進行請求操作

代碼如下:

public class BioClient {

    private String hostname;
    private int port;

    public BioClient(String hostname, int port) {
        this.hostname = hostname;
        this.port = port;
    }

    public void start(){
        Socket socket = new Socket();
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            //鏈接服務(wù)端
            socket.connect(new InetSocketAddress(hostname, port));
            br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            Scanner scanner = new Scanner(System.in);
            while(true){
                //請求服務(wù)端
                String message = scanner.nextLine();
                bw.write(message);
                bw.newLine();
                bw.flush();

                System.out.println("發(fā)送請求:" + message);

                String response = br.readLine();
                System.out.println("服務(wù)端響應(yīng):" + response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(br!=null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if(bw!=null){
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        BioClient bioClient = new BioClient("127.0.0.1", 8080);
        bioClient.start();
    }
}

對于上述代碼,需要注意: 通過socket的輸出流發(fā)送請求時,需要在消息末尾添加換行符,因為服務(wù)端都是根據(jù)換行符來區(qū)分每一條請求消息的。

Bio-串行接收請求版本

Bio模式下的服務(wù)器,在綁定服務(wù)端口成功之后,需要調(diào)用accept()方法,監(jiān)聽客戶端的鏈接,當客戶端經(jīng)過三次握手成功連接到服務(wù)器時,該方法才會返回,accept()方法一次只能接收一個鏈接(實際上就是去已完成鏈接隊列中取一個socket對象,上一節(jié)中已經(jīng)講過該知識點,不再累述)。下面我們就看一下在不另外開辟線程執(zhí)行連接的情景。服務(wù)端啟動的步驟如下:

  1. 綁定端口號,bind(),其中默認的backlog(上一節(jié)中介紹過)為50
  2. 調(diào)用accep()方法,監(jiān)聽端口,獲取鏈接。該方法調(diào)用一次只能返回一個連接,需要不斷的執(zhí)行才能不斷的獲取鏈接
  3. 處理連接,獲取鏈接的請求數(shù)據(jù),解析請求,根據(jù)請求進行響應(yīng)

代碼如下:

public class BioSimpleServer {

    private int port;

    public BioSimpleServer(int port) {
        this.port = port;
    }

    public void start(){
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            while(true) {
                Socket socket = serverSocket.accept();
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

                String message = br.readLine();
                System.out.println("接收到請求:" + message);

                String response = null;
                if("時間".equals(message)){
                    response = (new Date()).toString();
                }else{
                    response = "該請求為錯誤請求";
                }

                bw.write("服務(wù)器時間為:" + response);
                bw.newLine();
                bw.flush();
                System.out.println("服務(wù)端回復(fù)請求:" + response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        BioSimpleServer bioSimpleServer = new BioSimpleServer(8080);
        bioSimpleServer.start();
    }
}

對于上述代碼,對于資源的釋放并沒有關(guān)閉,只是demo而已,注意即可。有幾點需要指出:

  1. 將serverSocket.accept()方法一起后續(xù)操作,放在循環(huán)中,保證能夠不斷的接收新的請求
  2. 注意代碼中使用的是readLine()讀取數(shù)據(jù),意味著客戶端發(fā)送數(shù)據(jù)時,最后必須多添加一個換行符。用于區(qū)分不同的請求消息。

該中實現(xiàn)存在的問題:

  1. 如果多個客戶端請求,所有的請求都是串行執(zhí)行的,即第一個請求執(zhí)行完之后,才能接收并處理后一個請求。因此將接收到的socket扔到一個新的線程去執(zhí)行,而不阻塞當前的邏輯是非常重要的。
  2. 該版本,對于每一個連接只能處理一次,因為進入第二次循環(huán)的時候,輸入輸出流的對象就被改變了。這也是必須要開辟一個新線程執(zhí)行的原因。

綜上,對于Bio服務(wù)的實現(xiàn),為了不阻塞接收請求的邏輯,必須開辟一個新的線程執(zhí)行和處理連接。

Bio-IO線程版本

為了解決請求串行接收執(zhí)行的問題,我們需要開辟新的線程去執(zhí)行連接。
首先定義一個執(zhí)行socket的類,代碼如下:

public class BioRunnable implements Runnable {

    private Socket socket;
    private BufferedReader br;
    private BufferedWriter bw;

    public BioRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {

        try {

            br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

            while(true){
                String line = br.readLine();
                System.out.println("獲取客戶端請求:" + line);

                //處理業(yè)務(wù)邏輯
                String response = null;
                if("時間".equals(line)){
                    response = (new Date()).toString();
                }else{
                    response = "不能識別該請求";
                }
                bw.write(response);
                bw.newLine();
                bw.flush();
                System.out.println("返回客戶端結(jié)果:" + response);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(br!=null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if(bw!=null){
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

服務(wù)端的主流程如下:

public class BioStandardServer {

    private int port;

    public BioStandardServer(int port) {
        this.port = port;
    }

    public void start(){
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            while(true) {
                Socket socket = serverSocket.accept();
                new Thread(new BioRunnable(socket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        BioStandardServer bioStandardServer = new BioStandardServer(8080);
        bioStandardServer.start();
    }
}

通過開辟新線程執(zhí)行處理每一個連接,服務(wù)端可以并發(fā)的處理每個請求。此外,每一個請求不再是只能發(fā)送一次請求,服務(wù)端可以處理一個連接的多次請求,只要鏈接不斷開。
該版本存在的問題:針對每一個新的鏈接,服務(wù)端都需要開辟一個新的線程執(zhí)行,服務(wù)端會存在大量線程創(chuàng)建和銷毀的過程,當客戶端較多時,線程的創(chuàng)建和銷毀的開銷是不能忽略的。因此我們將會引出下一個Bio的版本,也是我們以往最經(jīng)常使用的版本-線程池版本

Bio-線程池版本

為了避免線程頻繁的銷毀和創(chuàng)建,使用線程池來達到線程重用的效果。連接的處理類不變。只修改服務(wù)器處理的主邏輯,代碼如下:

public class BioThreadPoolServer {

    private int port;
    private ThreadPoolExecutor executor = new ThreadPoolExecutor(50, 50, 0, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(1000));

    public BioThreadPoolServer(int port) {
        this.port = port;
    }

    public void start(){
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            while(true) {
                Socket socket = serverSocket.accept();
                executor.execute(new BioRunnable(socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        BioThreadPoolServer bioThreadPoolServer = new BioThreadPoolServer(8080);
        bioThreadPoolServer.start();
    }
}

通過使用線程池,達到了線程重用的目的

Bio的缺陷

線程池版本在并發(fā)量不大的情況下,可以很好的運行,但是當并發(fā)量過大時,其性能表現(xiàn)不佳,其主要的問題有如下幾方面:

  1. Bio的服務(wù)端對于連接個數(shù)有上限限制,默認為1024
  2. Bio為阻塞模式,當進行讀取操作,且讀取操作不滿足時(例如使用readLine()方法,找不到換行符就不會返回;例如讀出數(shù)據(jù),但數(shù)據(jù)接收隊列中沒有數(shù)據(jù),也會阻塞),操作就會則塞,阻塞時不會釋放已占有資源,對資源(例如線程資源,線程并沒有做事情,卻要一直等待)的使用造成浪費。
  3. Bio的處理模式會受到對方(客戶端影響服務(wù)端,服務(wù)端影響客戶端)的影響。例如客服端發(fā)送數(shù)據(jù)緩慢,就會影響到服務(wù)端對數(shù)據(jù)的接收處理,數(shù)據(jù)讀取不完,就會一直占用線程資源,與第2點本質(zhì)上是一點??蛻舳撕头?wù)端的性能相互影響,耦合性較大。

上面將的集中模式本質(zhì)上都是Bio的實現(xiàn),其中都存在Bio存在的問題。下一節(jié),將會講解Nio是如何解決Bio存在的問題。

歡迎掃描下方二維碼,關(guān)注公眾號,我們可以進行技術(shù)交流,共同成長

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

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,564評論 19 139
  • 生命的意義在于從容,在于從容之中眺望未來,在于從容之中成就人生。一個人能夠閑庭信步般走完一生,云舒云卷般進退自如,...
    三十三只貓閱讀 838評論 0 2
  • so sad 哪有那么多是非對錯,法律的存在是為了維穩(wěn),道德的存在是為了調(diào)和。只有人性,說來信誓旦旦,卻絕不容他人...
    辛辛辛烷閱讀 476評論 0 0
  • 昨晚都很晚了,下著雨。冷。宅在家挺舒服。即使這樣還是出去看了夏洛克。 我是典型的偵探迷,初中時期,男同學(xué)沉迷武俠金...
    秘記馬星星閱讀 531評論 0 1

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