㈠ android 支持netty嗎
這個是支持的。
什麼是netty,有興趣的朋友可以google一下netty,可以知道這東東做什麼用,當然你也可以選擇mina,一樣的都是java nio框架.
選擇netty是支持protobuf(google內部使用頻率比較高的,之前的android源碼中內含有這一部分的代碼)協議,這樣可以解決網路編程中粘包(也可以理解成傳輸過程中長度的問題,有興趣可以看相關文章).
㈡ 網路編程(五)TCP詳解
考慮最簡單的情況:兩台主機之間的通信。這個時候只需要一條網線把兩者連起來,規定好彼此的硬體介面,如都用 USB、電壓 10v、頻率 2.4GHz 等, 這一層就是物理層,這些規定就是物理層協議 。
我們當然不滿足於只有兩台電腦連接,因此我們可以使用交換機把多個電腦連接起來,如下圖:
這樣連接起來的網路,稱為區域網,也可以稱為乙太網(乙太網是區域網的一種)。在這個網路中,我們需要標識每個機器,這樣才可以指定要和哪個機器通信。這個標識就是硬體地址 MAC。
硬體地址隨機器的生產就被確定,永久性唯一。在區域網中,我們需要和另外的機器通信時,只需要知道他的硬體地址,交換機就會把我們的消息發送到對應的機器。
這里我們可以不管底層的網線介面如何發送,把物理層抽離,在他之上創建一個新的層次,這就是 數據鏈路層 。
我們依然不滿足於區域網的規模,需要把所有的區域網聯系起來,這個時候就需要用到路由器來連接兩個區域網:
但是如果我們還是使用硬體地址來作為通信對象的唯一標識,那麼當網路規模越來越大,需要記住所有機器的硬體地址是不現實的;
同時,一個網路對象可能會頻繁更換設備,這個時候硬體地址表維護起來更加復雜。這里使用了一個新的地址來標記一個網路對象: IP 地址 。
通過一個簡單的寄信例子來理解 IP 地址。
我住在北京市,我朋友 A 住在上海市,我要給朋友 A 寫信:
因此,這里 IP 地址就是一個網路接入地址(朋友 A 的住址),我只需要知道目標 IP 地址,路由器就可以把消息給我帶到。 在區域網中,就可以動態維護一個 MAC 地址與 IP 地址的映射關系,根據目的 IP 地址就可以尋找到機器的 MAC 地址進行發送 。
這樣我們不需管理底層如何去選擇機器,我們只需要知道 IP 地址,就可以和我們的目標進行通信。這一層就是 網路層 。網路層的核心作用就是 提供主機之間的邏輯通信 。
這樣,在網路中的所有主機,在邏輯上都連接起來了,上層只需要提供目標 IP 地址和數據,網路層就可以把消息發送到對應的主機。
一個主機有多個進程,進程之間進行不同的網路通信,如邊和朋友開黑邊和女朋友聊微信。我的手機同時和兩個不同機器進行通信。
那麼當我的手機收到數據時,如何區分是微信的數據,還是王者的數據?那麼就必須在網路層之上再添加一層: 運輸層 :
運輸層通過 socket(套接字),將網路信息進行進一步的拆分,不同的應用進程可以獨立進行網路請求,互不幹擾。
這就是運輸層的最本質特點: 提供進程之間的邏輯通信 。這里的進程可以是主機之間,也可以是同個主機,所以在 android 中,socket 通信也是進程通信的一種方式。
現在不同的機器上的應用進程之間可以獨立通信了,那麼我們就可以在計算機網路上開發出形形式式的應用:如 web 網頁的 http,文件傳輸 ftp 等等。這一層稱為 應用層 。
應用層還可以進一步拆分出表示層、會話層,但他們的本質特點都沒有改變: 完成具體的業務需求 。和下面的四層相比,他們並不是必須的,可以歸屬到應用層中。
最後對計網分層進行小結:
這里需要注意的是,分層並不是在物理上的分層,而是邏輯上的分層。通過對底層邏輯的封裝,使得上層的開發可以直接依賴底層的功能而無需理會具體的實現,簡便了開發。
這種分層的思路,也就是責任鏈設計模式,通過層層封裝,把不同的職責獨立起來,更加方便開發、維護等等。
TCP 並不是把應用層傳輸過來的數據直接加上首部然後發送給目標,而是把數據看成一個位元組 流,給他們標上序號之後分部分發送。這就是 TCP 的 面向位元組流 特性:
面向位元組流的好處是無需一次存儲過大的數據佔用太多內存,壞處是無法知道這些位元組代表的意義,例如應用層發送一個音頻文件和一個文本文件,對於 TCP 來說就是一串位元組流,沒有意義可言,這會導致粘包以及拆包問題,後面講。
前面講到,TCP 是可靠傳輸協議,也就是,一個數據交給他,他肯定可以完整無誤地發送到目標地址,除非網路炸了。他實現的網路模型如下:
對於應用層來說,他就是一個可靠傳輸的底層支持服務;而運輸層底層採用了網路層的不可靠傳輸。雖然在網路層甚至數據鏈路層就可以使用協議來保證數據傳輸的可靠性,但這樣網路的設計會更加復雜、效率會隨之降低。把數據傳輸的可靠性保證放在運輸層,會更加合適。
可靠傳輸原理的重點總結一下有: 滑動窗口、超時重傳、累積確認、選擇確認、連續 ARQ 。
停止等待協議
要實現可靠傳輸,最簡便的方法就是:我發送一個數據包給你,然後你跟我回復收到,我繼續發送下一個數據包。傳輸模型如下:
這種「一來一去」的方法來保證傳輸可靠就是 停止等待協議 (stop-and-wait)。不知道還記不記得前面 TCP 首部有一個 ack 欄位,當他設置為 1 的時候,表示這個報文是一個確認收到報文。
然後再來考慮另一種情況:丟包。網路環境不可靠,導致每一次發送的數據包可能會丟失,如果機器 A 發送了數據包丟失了,那麼機器 B 永遠接收不到數據,機器 A 永遠在等待。
解決這個問題的方法是: 超時重傳 。當機器 A 發出一個數據包時便開始計時,時間到還沒收到確認回復,就可以認為是發生了丟包,便再次發送,也就是重傳。
但重傳會導致另一種問題:如果原先的數據包並沒有丟失,只是在網路中待的時間比較久,這個時候機器 B 會受到兩個數據包,那麼機器 B 是如何辨別這兩個數據包是屬於同一份數據還是不同的數據?
這就需要前面講過的方法: 給數據位元組進行編號 。這樣接收方就可以根據數據的位元組編號,得出這些數據是接下來的數據,還是重傳的數據。
在 TCP 首部有兩個欄位:序號和確認號,他們表示發送方數據第一個位元組的編號,和接收方期待的下一份數據的第一個位元組的編號。
停止等待協議的優點是簡單,但缺點是 信道利用率 太低。
假定AB之間有一條直通的信道來傳送分組
這里的TD是A發送分組所需要的時間(顯然TD = 分組長度 / 數據速率)再假定TA是B發送確認分組所需要的時間(A和B處理分組的時間都忽略不計)那麼A在經過TD+RTT+TA時間後才能發送下一個分組,這里的RTT是往返時間,因為只有TD是採用來傳輸有用的數據(這個數據包括了分組首部,如果可以知道傳輸更精確的數據的時間,可以計算的更精確),所有信道利用率為
為了提高傳輸效率,發送方可以不使用低效率的停止等待協議,而是採用 流水線傳輸 :就是發送方可以 連續的發送多個分組 ,不必每發完一個分組就停下來等待對方的確認。這樣可使信道上一直有數據不間斷地在傳送。顯然這種傳輸方式可以獲得很高的信道利用率
停止等待協議已經可以滿足可靠傳輸了,但有一個致命缺點: 效率太低 。發送方發送一個數據包之後便進入等待,這個期間並沒有干任何事,浪費了資源。解決的方法是: 連續發送數據包 。
也就是下面介紹的 連續ARQ協議 和 滑動窗口協議
連續 ARQ 協議
模型如下:
和停止等待最大的不同就是,他會源源不斷地發送,接收方源源不斷收到數據之後,逐一進行確認回復。這樣便極大地提高了效率。但同樣,帶來了一些額外的問題:
發送是否可以無限發送直到把緩沖區所有數據發送完?不可以。因為需要考慮接收方緩沖區以及讀取數據的能力。如果發送太快導致接收方無法接受,那麼只是會頻繁進行重傳,浪費了網路資源。所以發送方發送數據的范圍,需要考慮到接收方緩沖區的情況。這就是 TCP 的 流量控制 。
解決方法是: 滑動窗口 。基本模型如下:
在 TCP 的首部有一個窗口大小欄位,他表示接收方的剩餘緩沖區大小,讓發送方可以調整自己的發送窗口大小。通過滑動窗口,就可以實現 TCP 的流量控制,不至於發送太快,導致太多的數據丟失。
連續 ARQ 帶來的第二個問題是:網路中充斥著和發送數據包一樣數據量的確認回復報文,因為每一個發送數據包,必須得有一個確認回復。提高網路效率的方法是: 累積確認 。
接收方不需要逐個進行回復,而是累積到一定量的數據包之後,告訴發送方,在此數據包之前的數據全都收到。例如,收到 1234,接收方只需要告訴發送方我收到 4 了,那麼發送方就知道 1234 都收到了。
第三個問題是:如何處理丟包情況。在停止等待協議中很簡單,直接一個超時重傳就解決了。但,連續 ARQ 中不太一樣。
例如:接收方收到了 123 567,六個位元組,編號為 4 的位元組丟失了。按照累積確認的思路,只能發送 3 的確認回復,567 都必須丟掉,因為發送方會進行重傳。這就是 GBN(go-back-n) 思路。
但是我們會發現,只需要重傳 4 即可,這樣不是很浪費資源,所以就有了: 選擇確認 SACK 。在 TCP 報文的選項欄位,可以設置已經收到的報文段,每一個報文段需要兩個邊界來進行確定。這樣發送方,就可以根據這個選項欄位只重傳丟失的數據了。
第四個問題是:擁塞控制的問題
也是通過窗口的大小來控制的,但是檢測網路滿不滿是個挺難的事情,所以 TCP 發送包經常被比喻成往誰管理灌水,所以擁塞控制就是在不堵塞,不丟包的情況下盡可能的發揮帶寬。
水管有粗細,網路有帶寬,即每秒鍾能發送多少數據;水管有長度,端到端有時延。理想狀態下,水管裡面的水 = 水管粗細 * 水管長度。對於網路上,通道的容量 = 帶寬 * 往返時延。
如果我們設置發送窗口,使得發送但未確認的包為通道的容量,就能撐滿整個管道。
如圖所示,假設往返時間為 8 秒,去 4 秒,回 4 秒,每秒發送一個包,已經過去了 8 秒,則 8 個包都發出去了,其中前四個已經到達接收端,但是 ACK 還沒返回,不能算發送成功,5-8 後四個包還在路上,還沒被接收,這個時候,管道正好撐滿,在發送端,已發送未確認的 8 個包,正好等於帶寬,也即每秒發送一個包,也即每秒發送一個包,乘以來回時間 8 秒。
如果在這個基礎上調大窗口,使得單位時間可以發送更多的包,那麼會出現接收端處理不過來,多出來的包會被丟棄,這個時候,我們可以增加一個緩存,但是緩存裡面的包 4 秒內肯定達不到接收端課,它的缺點會增加時延,如果時延達到一定程度就會超時重傳
TCP 擁塞控制主要來避免兩種現象,包丟失和超時重傳,一旦出現了這些現象說明發送的太快了,要慢一點。
具體的方法就是發送端慢啟動,比如倒水,剛開始倒的很慢,漸漸變快。然後設置一個閾值,當超過這個值的時候就要慢下來
慢下來還是在增長,這時候就可能水滿則溢,出現擁塞,需要降低倒水的速度,等水慢慢滲下去。
擁塞的一種表現是丟包,需要超時重傳,這個時候,採用快速重傳演算法,將當前速度變為一半。所以速度還是在比較高的值,也沒有一夜回到解放前。
到這里關於 TCP 的可靠傳輸原理就已經介紹得差不多。最後進行一個小結:
當然,這只是可靠傳輸的冰山一角,感興趣可以再深入去研究
㈢ socket 通信粘包怎麼處理
一、socket 通信粘包的處理方法:
1、對於發送方引起的粘包現象,用戶可通過編程設置來避免,TCP提供了強制數據立即傳送的操作指令push,TCP軟體收到該操作指令後,就立即將本段數據發送出去,而不必等待發送緩沖區滿;
2、對於接收方引起的粘包,則可通過優化程序設計、精簡接收進程工作量、提高接收進程優先順序等措施,使其及時接收數據,從而盡量避免出現粘包現象;
3、由接收方控制,將一包數據按結構欄位,人為控制分多次接收,然後合並,通過這種手段來避免粘包。
二、實現代碼:
三、方法注意事項:
1、第一種編程設置方法雖然可以避免發送方引起的粘包,但它關閉了優化演算法,降低了網路發送效率,影響應用程序的性能,一般不建議使用。
2、第二種方法只能減少出現粘包的可能性,但並不能完全避免粘包,當發送頻率較高時,或由於網路突發可能使某個時間段數據包到達接收方較快,接收方還是有可能來不及接收,從而導致粘包;
3、第三種方法雖然避免了粘包,但應用程序的效率較低,對實時應用的場合不適合。
四、實驗環境
1、硬體環境:伺服器:pentium 350 微機 、客戶機:pentium 166微機、網路平台:由10兆共享式hub連接而成的區域網;
2、軟體環境:操作系統:windows 98 、編程語言:visual c++ 5.0
㈣ 寫了一個發送端與接收端的ucp程序,為什麼運行有錯誤。怎麼解決
不太清楚你的代碼是怎樣寫的,所以只能從可能出現的問題上進行分析。
1、UDP丟包。使用UDP的時候經常會出現的問題,這個只能要求重發。
2、TCP粘包。如果你是新手這個問題是很容易出現的。你要知道,Socket.send並不是把數據發出去,而是把信息寫入到了底層winsock的緩沖區,在有空的時候給他發出去。如果你數個SEND是連續執行的,就會出現粘包的問題。比如你要發送12345,67890這兩條,接受方就可能收到1234567890這樣的數據。如果你注意receive函數的返回值,就會發現你收到的大小是10而不是預先的5.
解決辦法有三種
1、固定信息的長度,每次從接受緩沖區中讀出一定的長度。發送時不足長度的補齊。當然,這會加大開銷
2、要求接收方回執,每次send後進入receive等待回執,避免連續Send。當然,這也會增加開銷。通常這種做法只在接收方要進行信息的後續處理時採用
3、用NetworkStream+分隔符的方法發送和接收。分隔符可以使用信息中不包含的字元來做。譬如如果信息中永遠不包含換行符,那麼可以在每條發送信息的結尾附加一個換行符,然後NetworkStream讀取時以換行符作為分隔。每遇到換行符就作為一個數據包讀出。
㈤ Golang網路編程TCP粘包
result:
㈥ protobuf怎麼處理粘包,分包,斷包的問題
http://bbs.csdn.net/topics/391894970
Apache Mina Server 是一個網路通信應用框架,也就是說,它主要是對基於TCP/IP、UDP/IP協議棧的通信框架(當然,也可以提供Java 對象的序列化服務、虛擬機管道通信服務等),Mina 可以幫助我們快速開發高性能、高擴展性的網路通信應用,Mina 提供了事件驅動、非同步(Mina 的非同步IO 默認使用的是Java NIO 作為底層支持)操作的編程模型。
在mina中,一般的應用場景用TextLine的Decode和Encode就夠用了(TextLine的默認分割符雖然是\r\n,但其實分隔符是可以自己指定的,如:newTextLineDecoder(charset, decodingDelimiter);)
但默認解碼器每次讀取緩沖的數據是有限制的,即ReadBufferSize的大小,默認是2048個位元組,當數據包比較大時將被分成多次讀取,造成斷包。雖然可以通過acceptor.getSessionConfig().setReadBufferSize(newsize)這種方式來增加默認容量,但畢竟不是王道(太大了浪費空間,肯定會降低數據的處理效率)。
所以,當我們接收的數據的大小不是很固定,且容易偏大的時候,默認的TextLine就不適合了。這時我們在解析之前就需要判斷數據包是否完整,這樣處理起來就會非常麻煩。那麼Mina 中幸好提供了CumulativeProtocolDecoder
類,從名字上可以看出累積性的協議解碼器,也就是說只要有數據發送過來,這個類就會去讀取數據,然後累積到內部的IoBuffer 緩沖區,但是具體的拆包(把累積到緩沖區的數據解碼為JAVA 對象)交由子類的doDecode()方法完成,實際上CumulativeProtocolDecoder就是在decode()反復的調用暴漏給子類實現的doDecode()方法。
具體執行過程如下所示:
A. 你的doDecode()方法返回true 時,CumulativeProtocolDecoder 的decode()方法會首先判斷你是否在doDecode()方法中從內部的IoBuffer 緩沖區讀取了數據,如果沒有,則會拋出非法的狀態異常,也就是你的doDecode()方法返回true 就表示你已經消費了本次數據(相當於聊天室中一個完整的消息已經讀取完畢),進一步說,也就是此時你必須已經消費過內部的IoBuffer 緩沖區的數據(哪怕是消費了一個位元組的數據)。如果驗證通過,那麼CumulativeProtocolDecoder會檢查緩沖區內是否還有數據未讀取,如果有就繼續調用doDecode()方法,沒有就停止對doDecode()方法的調用,直到有新的數據被緩沖。
B. 當你的doDecode()方法返回false 時,CumulativeProtocolDecoder 會停止對doDecode()方法的調用,但此時如果本次數據還有未讀取完的,就將含有剩餘數據的IoBuffer 緩沖區保存到IoSession 中,以便下一次數據到來時可以從IoSession 中提取合並。如果發現本次數據全都讀取完畢,則清空IoBuffer 緩沖區。
簡而言之,當你認為讀取到的數據已經夠解碼了,那麼就返回true,否則就返回false。這個CumulativeProtocolDecoder其實最重要的工作就是幫你完成了數據的累積,因為這個工作是很煩瑣的。
一、 實現解碼器
CumulativeProtocolDecoder是一個抽象類,必須繼承並實現其doDecode方法,用戶自定義協議的拆分就應該寫在doDecode方法中,下面的MyDecoder類是一個其子類的實現:
public class MyDecoder extends CumulativeProtocolDecoder {
public static Logger log = Logger.getLogger(MyDecoder.class);
/**
* 包解碼器組件
*/
private PacketComponent packetComponent;
/**
* 這個方法的返回值是重點:
* 1、當內容剛好時,返回false,告知父類接收下一批內容
* 2、內容不夠時需要下一批發過來的內容,此時返回false,這樣父類 CumulativeProtocolDecoder
* 會將內容放進IoSession中,等下次來數據後就自動拼裝再交給本類的doDecode
* 3、當內容多時,返回true,因為需要再將本批數據進行讀取,父類會將剩餘的數據再次推送本
* 類的doDecode
*/
public boolean doDecode(IoSession session,IoBuffer in,
ProtocolDecoderOutput out) throws Exception {
log.info("in.remaining : "+in.remaining());
if(in.remaining() > 0){//有數據時,讀取前8位元組判斷消息長度
byte [] sizeBytes = new byte[8];
in.mark();//標記當前位置,以便reset
//因為我的前數據包的長度是保存在第4-8位元組中,
in.get(sizeBytes,0,8);//讀取4位元組
//DataTypeChangeHelper是自己寫的一個byte[]轉int的一個工具類
int size = (int) DataTypeUtil.bytesToInt(sizeBytes,4);
log.info("size : "+size);
in.reset();
if(size > in.remaining()){//如果消息內容不夠,則重置,相當於不讀取size
return false;//父類接收新數據,以拼湊成完整數據
} else{
byte[] bytes = new byte[size];
in.get(bytes, 0, size);
//把位元組轉換為Java對象的工具類
PackageData pack = packetComponent.getDataFromBuffer(IoBuffer.wrap(bytes));
out.write(pack);
if(in.remaining() > 0){//如果讀取內容後還粘了包,就讓父類再重讀 一次,進行下一次解析
return true;
}
}
}
return false;//處理成功,讓父類進行接收下個包
}
getter();
Setter();
}
二、 實現編解碼工廠和解碼器
我們還需要一個編解碼工廠,用來為編解碼過濾器提供編碼器和解碼器,解碼器此處我們用不到,但是也必須提供,所以可以提供一個空的實現。
/**
*
* 編解碼工廠
*
*/
public class MyCodecFcatory implements ProtocolCodecFactory {
private ProtocolEncoder encoder = null;
private ProtocolDecoder decoder = null;
public MyCodecFcatory(ProtocolEncoder encoder, ProtocolDecoderdecoder) {
this.encoder = encoder;
this.decoder = decoder;
}
@Override
public ProtocolEncoder getEncoder(IoSession session) throws Exception {
return this.encoder;
}
@Override
public ProtocolDecoder getDecoder(IoSession session) throws Exception {
return this.decoder;
}
}
/**
*
* 編碼器:不做任何操作,數據已是約定好的格式,按原格式編碼
*
*/
public class MyEncoder extends ProtocolEncoderAdapter {
@Override
public void encode(IoSession session, Object message,
ProtocolEncoderOutput out) throws Exception {
// TODO Do nothing
}
}
三、 配置編解碼過濾器
下面就可以配置編解碼過濾器了:
<!-- 累加數據包解碼器:解斷丟包、粘包問題 -->
<bean id="codec" class="org.apache.mina.filter.codec.ProtocolCodecFilter">
<constructor-arg>
<bean class="com.mina.codec.MyCodecFcatory">
<constructor-arg index="0">
<bean class="com.mina.codec.MyEncoder"></bean>
</constructor-arg>
<constructor-arg index="1">
<bean class="com.mina.codec.MyDecoder">
<property name="packetComponent">
<bean class="com. mina.component.RootComponent">
</bean>
</property>
</bean>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
<bean id="filterChainBuilder" class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder">
<property name="filters">
<map>
<entry key="codec" value-ref="codec"/>
<entry key="logger" value-ref="loggerFilter"/>
<entry key="executors" value-ref="executors"/>
</map>
</property>
</bean>
需要注意的是:在doDecode中通過out.write(pack) 把數據輸出後,官方的說明文檔中說接下來會繼續執行後面的過濾器,然後是IoHandle。如果你是只用了一個編解碼過濾器的話,這可能完全沒問題,但是如果使用了兩個編解碼過濾器(可能很少有人會這樣做,但本人由於前期使用了另外一個自定義的編解碼過濾器,後來想加上這個可累加的解碼器,為了圖省事就在原過濾器的前面新增加了一個編解碼過濾器,後來數據流就不走我原來的編解碼過濾器了,out.write()之後直接到了IoHandle裡面,搞了我好久,無奈最後把兩個編解碼過濾器合二為一啦,其中原因我還沒時間去搞個清楚,為防止大家和我犯同一個錯誤,特此提醒!)