Java,InputStream,Socket阻塞.(關於HTTP請求的IO問題自我總結)

ansondroider發表於2016-12-08

前言:

由於專案的需求,需要實現以下流程:

1. Client傳送HTTP請求到Server.

2. Server接收HTTP請求並顯示出請求的內容(包含請求的頭及Content的內容)

服務端實現: 

Server部分程式碼如下:

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

/**
 *  
 * @author Anson
 */
public class Server{

    private String ServerIP;
    private int ServerPort;
    private boolean shutdown = false;

    /**
       * Init Server
       */
    public void init(){
        
        ServerIP = "192.168.0.1";
        ServerPort = 8082;
    }
    
    /**
     *  
     */
    public void await(){
        
        ServerSocket serverSocket = null;
        
        try {
            serverSocket =  new ServerSocket(ServerPort, 1, InetAddress.getByName(ServerIP));
        }catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        
        // Loop waiting for a request
        while(!shutdown){
            Socket          socket  = null;
            InputStream     input   = null;
            OutputStream    output  = null;

            try {
                socket  = serverSocket.accept();
                input   = socket.getInputStream();
                output  = socket.getOutputStream();
                
                this.parse(input);
                 
             
                 // Close the socket
                socket.close();
                
            }catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
    }
    public void parse(InputStream input) {
        // Read a set of characters from the socket
        StringBuffer request = new StringBuffer(2048);
        int i;
        byte[] buffer = new byte[2048];
        try {
            i = input.read(buffer);
        }catch (IOException e) {
            e.printStackTrace();
            i = -1;
        }
        for (int j=0; j<i; j++) {
            request.append((char) buffer[j]);
        }
        System.out.println(request.toString());
    }
         
}

 再編寫StartServer.java

....
public class StartServer{
    public static void main(String[] args){
        Server server = new Server();
        server.init();
        server.await();
        .....
    }
}

跟著,客戶端的程式碼做法有很多,

可以用最簡單的Socket,

也可以用HttpClient,

或其它客戶端如:OC

具體做法,網上有很多,在此不細述.

我用的是OC,

當然,可能有些做法,並不會出現這樣的問題,

畢竟,我,並非一個高手.

我只描述我遇到的狀況,

成功實現的可以忽略.

 

問題描述:

當客戶端傳送的資料量小於一定長度(如2048byte)的時候,

Server執行正常,即可以正常列印出請求的內容.

如:GET index HTTP/1.1

    ........

請求的頭加上一些Content.

但當資料量大於2048的時候,奇怪的問題出現了.

有時會發現資料讀不全.

例如,在請求的頭下面,Content的內容是一串XML的String:

<?xml version="1.0" encoding="UTF-8" ?><label1>label1></label1>......

當請求的頭加上XML的String的總長度超過2048,那麼,可能出現在情況就是超過的部分丟失.

解決辦法:

可能已經有人發覺,在上面的Server裡面的parse(InputStream input)這裡的處理有問題.

因為很大程度上這個2048在StringBuffer的定義時就出現了.

所以,第一個失敗的嘗試便是修改2048.

第一次變成10*2048

跟著是100*2048....

最後是1000*2048....

但結果可以發現:資料量的限制並不會隨著這個數值的成倍增長而以相同倍數增長.有時可能是為了增加一倍,

而這個數值必需增加20倍.....

最後發現這個辦法不可能,

於是,改變了讀取的辦法.

用了其它的如StreamBufferReader還有很多其它的來嘗試讀取,

結果卻令人失望,

甚至於這時候出現了阻塞.

即,在讀取的過停中,卡住了,

直到等到客戶端的請求超時,才跳出來.

所以這些方法也失敗了.

具體的情況也記不大清楚了.

 

之後的另一個方法:

         String request = "";

           //如果不先讀一次,那麼下面的input.available()有可能是0值.
           char firstbyte = (char) input.read();
            request += firstbyte;
            int ava = 0;
            while ((ava = input.available()) > 0) {
                try {

                    // 在此睡眠0.1秒,很重要
                    Thread.sleep(100);
                } catch (Exception t) {
                    t.printStackTrace();
                }
                byte[] bufferedbyte = new byte[ava];
                input.read(bufferedbyte, 0, ava);
                request += new String(bufferedbyte, "UTF-8");
            }

 這是一個成功解決的方法,

 之所以要睡眠0.1秒,等待高手幫忙解答,

這也許跟網路有關係,

當然,資料量越大,睡眠的時應該有小小的加長.(緩衝時間).

雖然以上的做法成功實現了,但對於伺服器來說,效率是個問題,所以只能尋找更優的辦法.

 

第三次更改:

 

                int readInt = 0;
    		int availableCount = 0;
    		char requestHead = (char)(input.read());
    		request += requestHead;
//取得請求的型別是PUT或GET
    		if(requestHead == 'P') {//主要是PUT的方法裡面會帶有XML.
	    		while((readInt=input.read()) != -1) {
	    			request += (char)readInt;
	    			if(request.indexOf("</endXML>") != -1){
	    				break;
	    			}
	    		}
	    		System.out.println("this is put\n" +request);
    		} else if(requestHead=='G') {//GET的方法內容比較少,用以下的方法可以正常實現
    			while((availableCount=input.available()) > 0){
    				byte[] requestbuffer = new byte[availableCount];
    				input.read(requestbuffer);
    				request += new String(requestbuffer, "UTF-8");
    			} 

*程式碼並不完整.

這種做法,我在讀取XML的String里加上了一個結束的判斷即對"</endXML>"的判斷(當然,這是在知道Content內容的基礎上這種做法才可行).

雖然暫時解決了問題,但仍然不能完美解決存在的問題.

 

第四次更改:

這也是最後一次更改,辦法是:

在PUT的請求裡面,先取得一個Content-Length:的請求頭的值length.

這裡的大小就是Content的長度.

在這個基礎上,

先判斷是否開始讀取Content:

if(requestString.indexOf("\r\n\r\n") != -1)

.....

 

之後再迴圈讀取:

    for(int i=0; i < length; i++){

        requestString += (char)input.read();

    }

當讀完length後,跳出迴圈,

並跳出讀取input的讀取.

.......

問題在此告一段落.

 

若有哪位朋友懂得其中原理,懇請告知,

知其然而不知其所以然,心裡不免有個結.

結言:

以上做法,僅供參考.

若有錯誤,請不嗇指出.

 

歡迎轉載

轉載進請註明轉自:http://ansonlai.iteye.com

 

相關文章