2010-08-26 75 views
4

我正在試圖製作一個簡單的HTTP服務器,它能夠解析客戶端請求併發迴響應。緩衝來自套接字的數據?

現在我有一個問題。我要讀並在請求一次處理一個行,我不知道我是否應該:

  • 一次讀取一個字節,或在ň字節
  • 讀塊一段時間後,將它們放入緩衝區,然後逐個處理字節,然後再讀取新的字節塊。

什麼是最好的選擇,和爲什麼

另外,是否有一些替代解決方案呢?就像一個函數一次從套接字或其他東西讀取一行?

回答

4

你的問題的簡短答案是,我會一次讀取一個字節。不幸的是,這兩種情況都有利弊的情況之一。

對於緩衝區的使用是這樣一個事實,即從網絡IO的角度來看,實現可以更高效。反對使用緩衝區,我認爲代碼本質上比單字節版本更復雜。所以它的效率和複雜性是平衡的。好消息是,如果測試顯示它是值得的,那麼您可以首先實施簡單的解決方案,將結果歸檔並將其「升級」爲緩衝方法。

另外,只需要注意,作爲一個思想實驗我寫了一些僞代碼循環,執行基於緩衝區的HTTP數據包的讀取,包括在下面。實現緩衝讀取的複雜性似乎並不糟糕。但是請注意,我沒有對錯誤處理給予太多的考慮,或者測試它是否可以工作。但是,它應該避免過度的「雙重處理」數據,這很重要,因爲這會降低效率收益,這是此方法的目的。

#define CHUNK_SIZE 1024 

nextHttpBytesRead = 0; 
nextHttp = NULL; 
while (1) 
{ 
    size_t httpBytesRead = nextHttpBytesRead; 
    size_t thisHttpSize; 
    char *http = nextHttp; 
    char *temp; 
    char *httpTerminator; 

    do 
    { 
    temp = realloc(httpBytesRead + CHUNK_SIZE); 
    if (NULL == temp) 
     ... 
    http = temp; 

    httpBytesRead += read(httpSocket, http + httpBytesRead, CHUNK_SIZE); 
    httpTerminator = strstr(http, "\r\n\r\n"); 
    }while (NULL == httpTerminator) 

    thisHttpSize = ((int)httpTerminator - (int)http + 4; // Include terminator 
    nextHttpBytesRead = httpBytesRead - thisHttpSize; 

    // Adding CHUNK_SIZE here means that the first realloc won't have to do any work 
    nextHttp = malloc(nextHttpBytesRead + CHUNK_SIZE); 
    memcpy(nextHttp, http + thisHttpSize, nextHttpSize); 

    http[thisHttpSize] = '\0'; 
    processHttp(http); 
} 
+0

感謝您的一個很好的回答:) – Frxstrem 2010-08-26 14:48:29

0

從一次讀取一個字節開始(儘管注意到線以HTTP中的cr/lf結束),因爲它很簡單。如果這還不夠,請做更復雜的事情。

+0

但也準備處理單線cr和單線lf作爲行末,因爲有許多客戶端和服務器不在那裏真的HTTP compliants ... – 2010-08-26 20:57:35

4

一次一個字節會導致性能下降。考慮一個體面大小的循環緩衝區。

讀取緩衝區中任意大小的空閒塊。大多數時候你會得到簡短的閱讀。檢查讀取字節中http命令的結束。處理完成的命令和下一個字節成爲緩衝區的頭部。如果緩衝區已滿,請將其複製到備份緩衝區,使用第二個循環緩衝區,報告錯誤或任何適當的內容。

+0

你能解釋我「循環緩衝區」的概念? – mathieug 2012-04-18 18:34:33

+0

@Math - 它基本上是一個數組,將結尾包裹到開頭。指針會跟蹤頭部和尾部以及當前位置等。請參閱http://en.wikipedia.org/wiki/Circular_buffer – Duck 2012-04-19 02:01:33

1

TCP數據流一次進入一個IP數據包,根據IP層配置,TCP數據流可能高達1,500個左右字節。在Linux中,這將等待一個SKB等待應用層讀取隊列。如果一次讀取一個字節,則應用程序和內核之間的上下文切換開銷會將一個字節從一個結構複製到另一個結構。最佳解決方案是使用非阻塞IO來一次讀取一個SKB的內容,並儘量減少開關。

如果你是最佳帶寬之後,你可以爲了進一步降低的背景下讀出字節的尺寸較長的潛伏期昂貴的交換機爲更多的時候會花出去的應用程序複製內存。但是這隻適用於極端情況,並且在需要時應該執行這些代碼更改。

如果你檢查,你可以找到替代的辦法現有的HTTP技術,如使用多線程和阻止套接字,推動更多的工作回內核,以減少切換到應用程序和背部的開銷甚多。

我實現了一個HTTP服務器庫非常類似torak這裏的僞代碼,http://code.google.com/p/openpgm/source/browse/trunk/openpgm/pgm/http.c此實現最大速度的改進作出一切異步所以從來都沒有來塊。

1

Indy,例如,需要緩衝的方法。當代碼詢問印到讀取一行時,它首先檢查其當前緩衝區,看是否有斷線的存在。如果不是,則網絡讀入塊和直到出現換行符附加到緩衝器中。一旦完成,只有直到換行符的數據從緩衝區中移除並返回到應用程序,剩下的數據留在緩衝區中供下一次讀取操作。這使得在簡單的應用程序級API(ReadLine,ReadStream等)之間取得了很好的平衡,同時提供了高效的網絡I/O(讀取套接字中當前可用的所有內容,緩存它,並確認發送者不要等待太久 - 這樣就需要更少的網絡級讀取)。

0

一次讀取一個字節數組緩衝區。由於多個上下文用戶和內核模式之間切換(取決於實際的libc)讀取單個字符將是狗慢。

如果您讀取緩衝區,您需要準備好緩衝區可能未被完全填滿(注意長度返回),緩衝區中沒有足夠的字節到行結束或緩衝區包含多行。

它是在網絡應用程序的通用模式如何將線或固定大小的塊請求映射到緩衝器的該變量蒸汽(和通常實現錯誤的,例如一個0字節長度的答案是可能的)。高級語言會讓你擺脫這種複雜性。