2013-10-31 68 views
2

我正在運行一個多線程的簡約http服務器(不是一個web服務器,但是),它接受三個服務器套接字上的連接:local,internet和internet-ssl。從SSLSocket讀取最快或最好的方法

每個套接字的超時時間爲1000毫秒(可能會在未來降低)。

工作線程讀取請求是這樣的:

byte[] reqBuffer = new byte[512]; 
theSocket.getInputStream().read(reqBuffer); 

現在的問題是,隨着新實施的SSL插槽與1/N-1記錄分裂技術問題就出現了。還有些客戶一分爲其他奇怪的方式使用SSL(4/N-4等)的時候,所以我想我可能只是執行多個讀取這樣的:

byte[] reqBuffer = new byte[512]; 
InputStream is = theSocket.getInputStream(); 
int read = is.read(reqBuffer, 0, 128); // inital read - with x/n-x this is very small 
int pos = 0; 
if (read > 0) { 
    pos = read; 
} 
int i = 0; 
do { 
    read = is.read(reqBuffer, pos, 128); 
    if (read > 0) { 
     pos += read; 
    } 
    i++; 
} while(read == 128 && i < 3); // max. 3 more reads (4 total = 512 bytes) or until less than 128 bytes are read (request should be completely read) 

與像Firefox或Chrome等瀏覽器的客戶端工作使用該技術。

現在我的問題是,新方法要慢得多。對本地套接字的請求非常慢,以至於超時2秒的腳本超時請求(我不知道爲什麼)。也許我在我的代碼中有一些邏輯問題?

有沒有更好的方法來讀取SSL套接字?因爲每秒鐘有多達數百甚至上千個請求,並且新的讀取方法甚至會降低http請求的速度。

注意:ssl-socket目前沒有使用,直到我可以解決此問題纔會使用。

我也嘗試使用緩衝讀取器讀取行,因爲我們在這裏討論http,但服務器爆炸用盡文件描述符(限制爲20 000)。雖然可能是因爲我的實施。

我很感謝關於這個問題的每一個建議。如果您需要更多關於代碼的信息,請告訴我,我會盡快發佈。

編輯: 我實際上把更多的思想放在了我想要做的事情上,並且我意識到它歸結爲讀取HTTP標頭。所以最好的解決方案是實際讀取行(或字符的字符)的請求行,並停止讀取x行或直到到達空行(標記結尾)。 我目前的做法是將BufferedInputStream放在套接字的InputStream周圍,並用一個由BufferedReader「讀取」的InputStreamReader讀取它(問題:當我使用BufferedReader時使用BufferedInputStream是否合理?)。 此BufferedReader讀取字符的請求字符,檢測行結束符(\ r \ n)並繼續讀取,直到達到長度超過64個字符的行,讀取最多8行或到達空行(標記HTTP標頭的結尾)。我將在明天測試我的實現並相應地編輯此編輯。

編輯: 我幾乎忘了在這裏寫我的結果:它的工作原理。在每個套接字上,甚至比以前的工作方式更快。感謝大家指點我正確的方向。我最終實現這樣的:

List<String> requestLines = new ArrayList<String>(6); 
InputStream is = this.cSocket.getInputStream(); 
bis = new BufferedInputStream(is, 1024); 
InputStreamReader isr = new InputStreamReader(bis, Config.REQUEST_ENCODING); 
BufferedReader br = new BufferedReader(isr); 

/* read input character for character 
* maximum line size is 768 characters 
* maximum number of lines is 6 
* lines are defined as char sequences ending with \r\n 
* read lines are added to a list 
* reading stops at the first empty line => HTTP header end 
*/ 
int readChar; // the last read character 
int characterCount = 0; // the character count in the line that is currently being read 
int lineCount = 0; // the overall line count 
char[] charBuffer = new char[768]; // create a character buffer with space for 768 characters (max line size) 

// read as long as the stream is not closed/EOF, the character count in the current line is below 768 and the number of lines read is below 6 
while((readChar = br.read()) != -1 && characterCount < 768 && lineCount < 6) { 
    charBuffer[characterCount] = (char) readChar; // fill the char buffer with the read character 
    if (readChar == '\n' && characterCount > 0 && charBuffer[characterCount-1] == '\r') { // if end of line is detected (\r\n) 
     if (characterCount == 1) { // if empty line 
      break; // stop reading after an empty line (HTTP header ended) 
     } 
     requestLines.add(new String(charBuffer, 0, characterCount-1)); // add the read line to the readLines list (and leave out the \r) 
     // charBuffer = new char[768]; // clear the buffer - not required 
     characterCount = 0; // reset character count for next line 
     lineCount++; // increase read line count 
    } else { 
     characterCount++; // if not end of line, increase read character count 
    } 
} 
+1

你是什麼意思「* http(s)服務器(不是網絡服務器雖然)*」? – Bruno

+0

@布魯諾那麼它不是向瀏覽器提供網站或類似的東西。它僅實現HTTP協議的一小部分,並執行一些骯髒(但可靠)和快速的請求分析。 – Kazuo

+0

感謝您的建議。閱讀所有這些流和讀者,讓我得出結論,我應該依賴我正在處理的協議。由於我只需要HTTP頭,這應該是一個非常簡單的任務(在處理內容長度頭之後仔細閱讀內容也不會有太大問題)。 我編輯了我原來的帖子以反映這一點。 – Kazuo

回答

3

這是最有可能比較慢,你正在等待另一端發送更多數據,數據可能是永遠不會發送。

更好的方法是給它一個更大的緩衝區,比如32KB(128很小),只讀取一次可用的數據。如果需要在某種消息中重新組裝這些數據,則不應使用超時或固定數量的循環,因爲read()只能保證至少返回一個字節。

+0

是的,這可能是問題所在,也是爲什麼我在最後一次讀取沒有讀完整128字節後停止讀取。我也可以在兩次讀取中讀取128/384字節,但由於記錄分裂,我至少需要兩個字節。 據我瞭解,超時限制了讀取等待的時間並且不會增加或者我錯了嗎? 循環的固定數量用於限制讀取的總大小爲512字節。 由於我需要的所有信息都包含在這512個字節(如果不是請求無效)內,我總共只讀取了512字節的請求(正如您從緩衝區大小中看到的那樣)。 – Kazuo

+1

如果你想讀取512字節,爲什麼不從一開始就讀512字節? –

+2

@Kazuo,你應該總是假設你需要多次讀取。對於TLS記錄分割而言,這是更明顯的必要條件,但對於普通TCP來說也是如此。不要假設記錄將如何分裂(1/n-1,4/n-4,0/n,...),並且不要假設3足夠了。只是閱讀,直到它結束,或使用超時。使用'BufferedInputStream'可能會有所幫助,但不要忘記關閉它。 – Bruno

3

你應該肯定圍繞SSLSocket的輸入流包裝BufferedInputStream。

您一次讀取128個字節並推進偏移的技術是毫無意義的。一次只讀一次,然後處理。或者從緩衝流一次一個字節。

同樣,你應該將SSLSocket的輸出流包裝在BufferedOutputStream中。

+0

嗯,我不會認爲它毫無意義,因爲它按預期工作,只是方式太慢。我會嘗試你的建議。一次讀取一個字節似乎很慢。你介意推薦一個實現嗎? – Kazuo

+0

執行同樣的事情你已經做的額外代碼總是毫無意義的。由於緩衝,'BufferedInputStream.read()'不是很慢。 – EJP

+0

@EJC是的,它可能會做同樣的事情,但我只需要能夠處理拆分的請求(它允許我這樣做,只是速度不夠快)。但當然你說得對,這不是一個好的解決方案。但畢竟,這就是我來這裏問這個問題的原因。感謝您的建議,我想我正在找個地方。 – Kazuo