首先解釋一下這個題目, "報文"指的是業(yè)務(wù)層自定義的報文, TCP是流式協(xié)議, 不像UDP那樣是報文協(xié)議.
某兩個系統(tǒng)間進行網(wǎng)絡(luò)交互, 請求報文的格式為:
<json串>&sig=xxx
SYS-ATM作為請求方, 用PHP的stream_socket_sendto()進行發(fā)送. SYS-SIM開發(fā)服務(wù)器端,
使用Python的twisted框架. 上線后, 出現(xiàn)問題, 服務(wù)器端接收到的報文不完整. 例如, json串只讀了一半,
或者缺少"&sig=xxx", 缺少的數(shù)據(jù)是隨機的, 但只缺少尾部, 已經(jīng)接收到數(shù)據(jù)沒有差錯.
發(fā)送方代碼:
$str = sprintf('json=%s&sig=%s', $json, $sig);
stream_socket_sendto($fp, $str);
接收方代碼:
class XServer(Protocol):
def dataReceived(self, data):
#處理一個報文
factory = Factory()
factory.protocol = XServer
reactor.listenTCP(19009, factory)
reactor.run()
我們通過PHP和Python的API文檔, 以及我們對網(wǎng)絡(luò)協(xié)議和套接口的理解來分析.
我們首先想到的原因會不會是, 發(fā)送方的發(fā)送緩沖比一個報文小, 所以"分包"了? 不過仔細一想, 這個問題出發(fā)點本身就是錯誤的. 因為TCP協(xié)議是流式協(xié)議, 不存在"分包"的問題, 因為TCP只能看到字節(jié)流, 不認識報文(包). 所以, 發(fā)送方的發(fā)送緩沖大小不影響接收.
懷疑完發(fā)送緩沖, 現(xiàn)在來懷疑一下接收緩沖. 因為使用的是TCP協(xié)議, 所以, 就算是只讀到一個字節(jié)的數(shù)據(jù), dataReceived()也可能被調(diào)用一次, 所以, 在dataReceived()中把接收到的數(shù)據(jù)當作一個報文, 就犯了邏輯錯誤, 與接收緩沖的大小無關(guān). 而且, dataReceived()的API文檔提到: Please keep in mind that you will probably need to buffer some data.
問題1和問題2都有相同的錯誤, 那就是錯誤地把TCP當作是基于報文的協(xié)議. 使用TCP, 如果應(yīng)用層協(xié)議不是基于報文的, 你可以在每一次讀數(shù)據(jù)之后對數(shù)據(jù)進行處理, 比如, echo程序. 如果應(yīng)用層協(xié)議是基于報文的, 那么, 你必須自己組裝報文; 或者, 一次TCP會話只發(fā)送一個報文, 通過連接關(guān)閉來顯示聲明報文發(fā)送完畢. 而一個報文可能需要多次讀操作才能組裝完畢, 也可能一次讀包含了多于一個報文(如果不是停止等待應(yīng)答的話).
基于TCP socket的程序, 有幾種方式可用來實現(xiàn)報文協(xié)議:
1. 報文中聲明報文數(shù)據(jù)的長度.
2. 使用分隔符.
3. 發(fā)送方發(fā)送完一個報文后關(guān)閉連接.
業(yè)務(wù)層本意上是想使用一種基于報文的協(xié)議, 但所定義的報文格式并沒有提供報文分隔符或者長度字段, 這就要求程序進行語義分析, 增加了實現(xiàn)難度. 現(xiàn)在改成使用換行符作為分隔符.
class XServer(LineServer):
def lineReceived(self, data):
#處理一個報文
前面提到, 我們必須自己組裝報文, 但這不代表我們要自己寫組裝代碼, 行IO庫可以替我們做這項工作. LineServer實現(xiàn)了基于行的協(xié)議, 只有讀到完整的一行, lineReceived()才會調(diào)用.
姊妹篇: 編寫基于TCP的應(yīng)用程序 http://www.ideawu.net/blog/?p=429
你現(xiàn)在看的文章是: TCP讀取報文不完整的問題分析如對本文有疑問,請?zhí)峤坏浇涣髡搲瑥V大熱心網(wǎng)友會為你解答??! 點擊進入論壇