第零:你真的需要優化呢?
通常你會發送相對較小的消息。當您查看您忽略了多少以太網,IP和TCP開銷以及吞吐帶寬的RTT時,從512字節消息中刪除60個字節通常很愚蠢。
另一方面,當你的是發送巨大的消息時,通常不需要在同一個連接上發送多個消息。
看看像HTTP,IMAP等常見的互聯網協議。他們大多數使用線分界,人類可讀,易調試的純文本。 HTTP可以以二進制形式發送「剩餘的消息」,但是在發送完成後關閉套接字。
99%的時間,這是夠好的。如果你認爲它不夠好,我會仍然寫你的協議的文本版本,然後添加一個可選的二進制版本,一旦你有一切調試和工作(然後測試,看看是否它真的有所作爲)。
同時,您的代碼有兩個問題。
首先,如您所知,如果您使用":::END"
作爲分隔符,並且您的消息可以在其數據中包含該字符串,則您有不明確之處。解決這個問題的常用方法是某種形式的轉義或引用。對於一個非常簡單的例子:
def sockWrite(conn, data):
data = data.replace(':', r'\:') + ":::END"
conn.write(data)
現在在讀出側,你剛纔拉過的分隔符,然後replace('r\:', ':')
上的消息。 (當然,使用6字節':::END'
分隔符來逃避每個冒號是很浪費的 - 你可能只需要使用一個非轉義的冒號作爲分隔符,或者寫一個更復雜的轉義機制。)
第二,你正確的是「這次單次寫入可能會發生多次讀取」 - 但是對於這次單次讀取也可能發生多次寫入。你可以閱讀這個消息的一半,再加上下一個消息的一半。這意味着你不能只使用endswith
;您必須使用類似partition
或split
的東西,並編寫可處理多條消息的代碼,並編寫可存儲部分消息的代碼,直至下一次通過read
循環。
同時,爲您的具體問題:
有封裝的數據帶寬增加儘可能最小的一個適當的方式?
當然,至少有三種適當的方式:分隔符,前綴或自定義格式。
您已經找到第一個。與它的問題:除非有一些字符串永遠不會出現在你的數據中(例如,用人類可讀的UTF-8文本,'\0'
),你可以選擇的分隔符不需要轉義。
像JSON這樣的自定義格式是最簡單的解決方案。當最後一個打開的支架/支架關閉時,信息結束,下一個時間到了。
或者,您可以爲每個消息添加一個包含長度的標頭。這是許多低級協議(如TCP)所做的。其中最簡單的格式是netstring,其中標題只是以字節爲單位的長度,表示爲普通的10位字符串,後跟冒號。網絡協議也使用逗號作爲分隔符,它添加了一些錯誤檢查。
我曾想過鹹菜或JSON,但擔心會增加帶寬的顯著量,因爲我相信他們會二進制數據轉換爲ASCII
pickle
有二進制文件和文本格式。正如the documentation解釋,如果您使用協議2
,3
或HIGHEST_PROTOCOL
,您將得到一個相當有效的二進制格式。另一方面,JSON只處理字符串,數字,數組和字典。您必須手動將任何二進制數據呈現爲字符串(或字符串或數字或任何其他數組),然後才能對其進行JSON編碼,然後在另一側進行相反的處理。執行此操作的兩種常用方法是base-64和hex,它們分別爲數據大小增加25%和100%,但如果您真的需要,則可以使用更高效的方法。
當然,JSON協議本身會使用比嚴格必要的更多的字符,所有這些引號和逗號等等,以及您給任何字段的任何名稱都會以未壓縮的UTF-8格式發送。如果確實存在問題,您可以始終使用BSON,Protocol Buffers,XDR或其他較不「浪費」的序列化格式替換JSON。
同時,pickle
不是自行劃分的。你必須首先拆分消息,然後才能取消它們。 JSON 是自定義分隔符,但除非先分開消息,否則不能只使用json.loads
;你將不得不寫更復雜的東西。最簡單的工作是在緩衝區上重複調用raw_decode
,直到獲得一個對象。