2014-12-19 36 views
0

我使用的SocketChannel像這樣單個連接:如何在Java中使用套接字進行快速數據讀取?

int sampleBufferSize = 50; 
StringBuilder data = new StringBuilder(); 
ByteBuffer bf = ByteBuffer.allocate(sampleBufferSize); 
SocketChannel sc = new SocketChannel(); 
while(true) 
    if(sc.read(bf) > 0){ 
     bf.flip(); 
     while(bf.hasRemaining()) 
      data.append((char) bf.get()); 
     bf.clear(); 
    }else{ 
     fireDataReceived(data.toString()); 
     data.delete(0, data.length()); 
    } 

此代碼是不是很有效,但它從0.05秒同一PC讀取HTTP POST請求130 KB。現在我正在嘗試編寫一個類似功能但使用Socket的類。下面是代碼:

private static final int TIMEOUT_MILLIS = 50; 
private boolean reading = false; 
private long readBeginTime = 0; 
private StringBuilder buffer = new StringBuilder(); 
private Thread thread = new Thread(){ 
    public void run(){ 
     while(!isInterrupted()){ 
      try { 
       int b = getInputStream().read(); 
       if(b == -1){ 
        if(reading) 
         fireDataReceived(); 
        close(); 
       }else{ 
        if(!reading){ 
         reading = true; 
         readBeginTime = System.currentTimeMillis(); 
         setSoTimeout(TIMEOUT_MILLIS); 
        } 
        buffer.append((char) b); 
       } 
      } catch (SocketTimeoutException e){ 
       fireDataReceived(); 
      } catch (IOException e) { 
       e.printStackTrace(); 
       if(reading) 
        fireDataReceived(); 
       close(); 
      } 
     } 
    } 
}; 
private void fireDataReceived(){ 
    BufferedSocketEvent e = new BufferedSocketEvent(this, System.currentTimeMillis() - readBeginTime, buffer.toString()); 
    buffer.setLength(0); 
    reading = false; 
    try { 
     setSoTimeout(0); 
    } catch (SocketException e1) { 
     e1.printStackTrace(); 
    } 
    for(BufferedSocketListener listener: listeners) 
     listener.dataReceived(e); 
} 

而問題是,它需要0.4秒同一請求,我不知道爲什麼要花這麼長。請解釋我的代碼有什麼問題。

+1

'TIMEOUT_MILLIS = 50'爲什麼會超時如此之小:

我會是這樣的開始?在互聯網上延遲50毫秒並不罕見。 –

+0

@Banthar,這是一個例子。我已經在一臺PC的範圍內進行了測試。對於本地主機來說,這是一個巨大的超時 –

+0

如果將其設置爲30秒,您的程序是否工作得更慢? –

回答

0

您的流代碼的問題在於,您一次只讀一個字節,速度很慢,並且一次只能追加一個字節,同上。試試這個:

BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()); 
char[] chars = new char[8192]; 
int count; 
while ((count = in.read(chars)) > 0) 
{ 
    buffer.append(chars, 0, count); 
    fireDataReceived(); 
} 

請注意,使用讀取超時作爲分隔請求的方法是不正確的。你需要解析數據來做到這一點。 TCP是一種沒有消息邊界的流媒體協議,並且沒有關於單獨接收單獨發送的保證。

+0

BufferedReader是一個很好的解決方案,但如果我提出錯誤的請求(根本沒有換行/回車符),我將不會收到這些數據。 –

+0

並且在SocketTimeoutException catch子句中討論了fireDataReceived,只有當套接字超時值大於0時,纔會拋出此類異常。並且只有當收到數據的第一個字節時超時設置爲TIMEOUT_MILLIS。這意味着當沒有數據已經​​被緩衝時,SocketTimeoutException子句無法到達。 –

+0

@AntonOnipko你錯了。此代碼不依賴回車或換行符,因爲它不調用'readLine()'。 – EJP

0

你必須使用readLine()或類似的方法。 HTTP標頭的末尾用空行標記。如果您未檢測到行,則無法知道何時停止讀取數據。依靠超時或TCP片段不是一個正確的解決方案。如果請求不包含新行,則需要等到出現一個或連接終止/超時。你應該等待至少幾秒鐘。

我也擺脫了那些聽衆。能夠爲單個套接字添加多個偵聽器似乎是一個好主意,但使用HTTP頭唯一明智的做法是解析它。解析器還需要通知讀者何時停止閱讀。

ServerSocket serverSocket = new ServerSocket(8888); 
Socket clientSocket = serverSocket.accept(); 
BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "ASCII")); 
String[] r = reader.readLine().split(" "); 
String type = r[0]; 
String resource = r[1]; 
String version = r[2]; 
Map<String, String> headers = new HashMap<String,String>(); 
while(true) { 
    String line = reader.readLine(); 
    if(line.isEmpty()) { 
     break; 
    } 
    String headerLine[] = line.split(":",2); 
    headers.put(headerLine[0],headerLine[1].trim()); 
} 
processHeader(type, resource, version, headers); 
+0

如果每個人都遵循標準,並且沒有惡意的人,readLine()將是一個不錯的選擇。我的問題開始很簡單:一旦我創建了一個帶有Web界面的應用程序,並發現在HTTP POST正文後沒有行結束。然後我想:「也許客戶端可能會發送錯誤的內容長度標題,我將無法閱讀整個內容,或者我會卡住等待未提供的其餘數據。」然後我認爲「OMG,任何人都可以做出不結束的請求,並且會」掛起「。」這就是爲什麼我需要閱讀一些最低限度所需的數據,而不管格式。 –

+0

如果您擔心掛起的連接,您應該將超時設置爲幾秒,然後放慢連接。 Apache這樣做(默認情況下60秒超時)。在目前的方法中,如果是LAN或撥號,每個客戶端都必須等待相同的時間。增加超時以使慢速客戶端工作將會降低快速客戶端的性能。它不能快速工作。 –

+0

不必在HTTP POST正文後面有一行結尾。這個答案只涉及標題。一旦你按照這個答案閱讀了標題,你應該按照我的答案使用'read()'。 – EJP

相關問題