場景:編寫一個簡單的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請求格式: