服務端把客戶端幾次發的數據一起接受了,是怎麼回事?socket,Tcp協議
客戶端是android,服務端是c#,手機監聽手指一動就把手指所在的那個點的位置發給服務端,服務端死循環接收,android可以保證一次是給服務端發一條數據,但是服務端把幾次發的數據一起接受了
TCP is a byte stream protocol.
TCP協議是流式傳輸協議,本來就沒有什麼次的概念,TCP包是底層實現,上層看到的就是數據流,流的特徵就是連續不斷。
你要區分每一條消息,可以參考建立在TCP連接之上的HTTP協議是怎麼做的。
當然了,最簡單的方法還是發送終結符/分段符原理大家都說了,我補充一點,對於你這種簡單應用,有個簡單的解決方法是把通訊做成對話形式。得到了對方的回應才發下一條信息,就不用手動分包或者加起始結束符了。
TCP本來就是流式傳輸協議,數據是一個流,而不是一個一個的數據包。拆包,黏包都是有可能發生的。這和TCP的設計想法有關(一個建立的連接使用同一組滑動窗口的序號,也需要逐個的確認,這樣就只能被當做位元組的流,而不是單個的包)。
要處理這種問題,自己加上分隔符,自行處理包的合併與拆分。
原因已經很清楚了,就是因為TCP是流式數據,沒有次的概念。看題主的數據,結構本身比較簡單,可以試試利用換行符截斷數據。
客戶端在發送完數據後再發一個「」,服務端接收的時候以readLine 的方式進行接收,這樣的缺點是數據中不能包含換行,解決辦法是,將換行轉義,或者乾脆就把數據編碼以後再發送,完美解決問題。
有無數人以為tcp也是包協議,並發明出粘包,截包之類的偽概念。其實這是一種病,解藥就是《TCP/IP詳解——卷一》
關鍵詞Nagle"s Algorithm
我沒做過Android開發,如果是socket API的話可以用setsockopt NODELAY來避免。不過我覺得還是自己分割處理比較好。建議題主要不換rpc框架吧,這樣雙端通信就簡化成方法調用了。
領導講話,學生聽,中間不許提問。領導吹得天花亂墜學生又不懂,怎麼知道領導講完了?得先摸清領導的習慣。
有的領導喜歡說: 我下面講 3 點,第 1 點是…… 等第 3 點講完,同學們趕緊鼓掌。
有的領導會說: 我的話講完了。同學們也要趕緊鼓掌。
題主遇到的問題是: 領導上來就說一大堆,學生聽了半天也不知道講沒講完,索性聽多少是多少鼓個掌吧!
我的話講完了。關鍵字拆包,黏包
tcp流式發送接收,沒辦法直接區分出第幾條信息
回答中有人說粘包拆包是偽概念,這是不對的,tcp粘的和拆的是你應用層的包。跟tcp本身不是包協議無關。
題主可以做一下分包處理,也就是樓上有人說的終結符/分隔符。一般tcp都是要處理分包粘包問題。
tcp是流,數據是連續的。你發送時是分段發送,但協議不保證是分段接收。實際底層是不停地堆到buffer里,你readall就會讀到題目里那樣的東西。
但流式傳輸,本就不應該用readall讀,就應該用流式讀取,也就是流操作符。那樣代碼會非常非常漂亮,寫起來反而更簡單。
當然,重載操作符時,就需要考慮如何界定兩個對象。正如其他回答所說,可以用c的方法,就是自帶包長來分割。也可以用類似http的方法,加界定符。
但其實這兩者都不需要,我們只需要定義好類結構,就可以了,完全不需要界定符和長度標識。
比如你這個事件,你就定一個事件類,重載&<&< &>&>兩個操作符。
然後客戶端那邊就可以:
MyMoveEvent e(-1, 1);
socket &<&< e;
服務端就:
while(socket.bytesValid() &>= sizeof(int))
{
int type;
socket &>&> type;
switch(type)
{
case MyEvent::move:
MyMoveEvent e;
while(socket.bytesValid() &< e::size()) {} // 若數據不夠填滿event,則繼續等待
socket &>&> e;
processMoveEvent(e);
break;
case MyEvent::Input:
MyInputEvent e;;
while(socket.bytesValid() &< e::size()) {} // 若數據不夠填滿event,則繼續等待
socket &>&> e;
processMoveEvent(e);
break;
case ...: // and so on
}
}
在這裡,MyEvent是個抽象基類,提供MyEvent::Type這個enum,並存儲到成員變數type中。每個枚舉值對應一個子類,然後那個子類有自己的成員結構。
然後就有了流操作符:
socket operator&<&<(socket s, MyEvent event)
{
switch(event.type)
{
case MyEvent::Move:
MyMoveEvent* e = dynamic_cast&
s &<&< *e; // MyMoveEvent: s &<&< int(type) &<&< pos.x &<&< pos.y;
break;
case MyEvent::Input:
MyInputEvent* e = dynamic_cast&
s &<&< *e; // MyInputEvent: s &<&< int(type) &<&< text;
break;
case ....; // and so on
}
return s;
}
然後各子類的&>&>操作符,參照注釋那樣寫就ok了。
TCP是流式傳輸協議,底層傳輸的都是二進位流。調用發送函數只是把數據拷貝到了發送緩衝區,然後再由驅動從緩衝區拷貝數據拼包發送到網路,因此而且不保證業務層每調用一次就發一個tcp包,接收端也是先把數據拷貝到接收緩衝區,再通知上層處理,上層需要自己重新整理數據,形成原始的發送格式
發數據的時候可以定個協議,收的時候按協議讀
多謝各位大神的回答,各位大神說做標記符,讓我明白問題真正出在哪裡,就像老師說話,學生聽,中間不許插嘴,雖然做標記符,達不到即時的效果,但讓我對tcp稍微理解了多一點,我現在是老師說一句,等學生做出回應,再說下一句。這樣就解決問題了,謝謝各位了!
這就是常遇到的粘包分包情況 這種情況是由於底層的數據流根本不知道你的應用層數據包哪裡到哪裡是一段 他們只負責去發送這些數據
解決這種問題的辦法通常是去給應用層數據包前後都加上標記 或者頭部加上標記然後緊跟數據包長度 類似於傳輸層以下的標準協議設計 收到數據包後發現沒有收到包頭 或者長度不夠就不移動讀指針 而是把這一段數據緩存下來 等待數據包完整後再來做處理
查一下TCP的粘包問題,就懂了
IP協議有包的概念,在此基礎上實現的UDP有包,但是TCP沒有。TCP抽象了數據流的概念。可能Client發送10個,Server一次性就收完了。也可能Client發1次,但是Server收了10次才收全
上面的答主都說過了. TCP是流式協議, 是不區分數據包的. 只有在上層接收的時候進行拆分. 如果不是很重要的數據, 可以考慮使用UDP, 這樣就不用考慮拆包的問題了.
推薦閱讀:
※TCP中已有SO_KEEPALIVE選項,為什麼還要在應用層加入心跳包機制??
※TCP面向位元組流和報文段的關係是什麼?
※怎樣實時判斷socket鏈接狀態?
※leader/follower, 半同步半非同步 和 事件驅動的關係是什麼?
※你用socket寫過什麼有趣的程序?