1. 程式人生 > >漫談Java IO之普通IO流與BIO服務器

漫談Java IO之普通IO流與BIO服務器

com 釋放 膨脹 AR oca mar ace 暴露 pos

今天來復習一下基礎IO,也就是最普通的IO。

  1. 網絡IO的基本知識與概念
  2. 普通IO以及BIO服務器
  3. NIO的使用與服務器Hello world
  4. Netty入門與服務器Hello world
  5. Netty深入淺出

輸入流與輸出流

Java的輸入流和輸出流,按照輸入輸出的單元不同,又可以分為字節流和字符流的。
技術分享圖片

JDK提供了很多輸入流和輸出流,比如:

技術分享圖片

字節流可以按照不同的變量類型進行讀寫,而字符流則是基於字符編碼的。不同的字符編碼包含的字節數是不一樣的,因此在使用字符流時,一定要註意編碼的問題。

讀寫

字節的輸入輸出流操作

// 字節輸入流操作
InputStream input = new ByteArrayInputStream("abcd".getBytes());
int data = input.read();
while(data != -1){
    System.out.println((char)data);
    data = input.read();
}

// 字節輸出流
ByteArrayOutputStream output = new ByteArrayOutputStream();
output.write("12345".getBytes());
byte[] ob = output.toByteArray();

字符的輸入輸出流操作

// 字符輸入流操作
Reader reader = new CharArrayReader("abcd".toCharArray());
data = reader.read();
while(data != -1){
    System.out.println((char)data);
    data = reader.read();
}
// 字符輸出流
CharArrayWriter writer = new CharArrayWriter();
writer.write("12345".toCharArray());
char[] wc = writer.toCharArray();

關閉流

流打開後,相當於占用了一個文件的資源,需要及時的釋放。傳統的標準關閉的方式為:

// 字節輸入流操作
InputStream input = null;
try {
    input = new ByteArrayInputStream("abcd".getBytes());
    int data = input.read();
    while (data != -1) {
        System.out.println((char) data);
        // todo
        data = input.read();
    }
}catch(Exception e){
    // todo
}finally {
    if(input != null) {
        try {
            input.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在JDK1.7後引入了try-with-resources的語法,可以在跳出try{}的時候直接自動釋放:

try(InputStream input1 = new ByteArrayInputStream("abcd".getBytes())){
   //

}catch (Exception e){
    //
}

IOUtils

直接使用IO的API還是很麻煩的,網上的大多數教程都是各種while循環,操作很麻煩。其實apache common已經提供了一個工具類——IOUtils,可以方便的進行IO操作。

比如IOUtils.readLines(is, Charset.forName("UTF-8"));可以方便的按照一行一行讀取.

BIO阻塞服務器

基於原始的IO和Socket就可以編寫一個最基本的BIO服務器。

技術分享圖片

概要: 這個模型很簡單,就是主線程(Acceptor)負責接收連接,然後開啟新的線程專門負責連接處理客戶端的請求。


import io.netty.util.CharsetUtil;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class PlainOioServer {
    public void serve(int port) throws IOException {
        // 開啟Socket服務器,並監聽端口
        final ServerSocket socket = new ServerSocket(port);
        try{
            for(;;){
                // 輪訓接收監聽
                final Socket clientSocket = socket.accept();
                try {
                    Thread.sleep(500000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("accepted connection from "+clientSocket);
                // 創建新線程處理請求
                new Thread(()->{
                   OutputStream out;
                   try{
                       out = clientSocket.getOutputStream();
                       out.write("Hi\r\n".getBytes(CharsetUtil.UTF_8));
                       out.flush();
                   } catch (IOException e) {
                       e.printStackTrace();
                   } finally {
                       try{
                           clientSocket.close();
                       } catch (IOException e) {
                           e.printStackTrace();
                       }
                   }
                }).start();
            }
        } catch (IOException e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws IOException {
        PlainOioServer server = new PlainOioServer();
        server.serve(5555);
    }
}

然後執行telnet localhost 5555,就能看到返回結果了。

這種阻塞模式的服務器,原理上很簡單,問題也容易就暴露出來:

  1. 服務端與客戶端的連接相當於1:1,因此如果連接數上升,服務器的壓力會很大
  2. 如果主線程Acceptor阻塞,那麽整個服務器將會阻塞,單點問題嚴重
  3. 線程數膨脹後,整個服務器性能都會下降

改進的方式可以基於線程池或者消息隊列,不過也存在一些問題:

  1. 線程池的數量、消息隊列後端服務器並發處理數,都是並發數的限制
  2. 仍然存在Acceptor的單點阻塞問題

接下來,將會介紹基於Nio的非阻塞服務器模式,如果忘記什麽是IO多路復用,可以回顧前面一篇分享。敬請期待吧...

漫談Java IO之普通IO流與BIO服務器