1. 程式人生 > 實用技巧 >socket流讀取read阻塞和readLine阻塞問題解決方案

socket流讀取read阻塞和readLine阻塞問題解決方案

場景:編寫一個簡單的httpserver,請求一直無響應。

分析:經排查,發現是在對socket的inputStream的最後一行讀取時阻塞了。程式碼大概如下:

        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        String line = "";
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }    

在網上搜索得出問題的根本原因:socket流沒有結束符

。我們對流的讀取大概分兩種,read()和readLine()。

阻塞場景:read()            沒有讀取到任何資料
        readLine()        沒有讀取到結束符或者換行符

正是因為socket流沒有結束符,而我們又不能強求請求體最後一定加上換行符,所以導致在readLine最後一行阻塞了。

1、換成read方法讀取也不行,比如下面;結果read永遠不會返回-1,因為沒有結束符,最後在沒有讀取到資料的情況沒有返回-1,而是選擇了等待。

        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        
char[] bt = new char[1024]; while (br.read(bt) != -1) { System.out.println(bt); }

2、對於流資料比較小的情況,我們可以給bt初始化一個足夠大的長度,一次將所有資料讀取出來;http請求可以保證請求資料不會為空,繼而保證了第一次讀取不會阻塞。但這只是個妥協的辦法,因為我們請求長度不可控。

        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        
char[] bt = new char[足夠大]; br.read(bt); System.out.println(bt);

解決方案:在讀取前,使用ready()方法判斷是否還有資料沒有讀取。http請求不會為空,所以使用do-while保證能讀取到資料。注意:這裡如果使用while,可能會在第一次ready()時資料還沒傳輸過來導致直接跳過。

        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        char[] bt = new char[1024];
        do {
            br.read(bt);
            System.out.println(bt);
        } while (br.ready());

最後再看看最終http請求讀取、解析的程式碼。為了方便解析,請求頭使用readLine(),請求體使用read。而且readLine()效率高於read()。

    public void parse() throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        int lineNum = 0;
        String line = "";
        while ((line = br.readLine()) != null) {
            if ("".equals(line)) {
                System.out.println("");
                //請求體
                parseContent(br);
                break;
            }
            if (lineNum++ == 0) {
                //首行
                parseFirstLine(line);
            } else {
                //請求頭
                parseHeader(line);
            }
        }
    }

    private void parseFirstLine(String line) {
        String[] first = line.split("\\s");
        method = first[0];
        url = first[1];
        agree = first[2];
        System.out.println(line);
    }

    private void parseHeader(String line) {
        //TODO 待實現
        System.out.println(line);
    }

    private void parseContent(BufferedReader br) throws IOException {
        StringBuilder sb = new StringBuilder();
        char[] bt = new char[1024];
        while (br.ready())  {
            br.read(bt);
            sb.append(bt);
        };
        this.content = sb.toString();
        System.out.println(content);
    }

結果與http請求格式: