對於基於socket的udp協議通訊,丟包問題大家應該都見怪不怪了,但我們仍然希望在通訊方面使用Udp協議通訊,因為它即時,消耗資源少,響應迅速,靈活性強無需向Tcp那樣建立連接消耗很長的時間等等很有優勢的理由讓我們對Udp通訊寄予了厚望。但它也存在一個不好的特點,經常丟包是時常發生的事。可能各位大俠已經有了很好的解決方案,本人在這也只是本著大家共同學習的目的,提供自己的解決方式。
解決思路:模擬tcp三次握手協議,通過使用Timer定時器監視發送請求後接受數據的時間,如果一段時間內沒有接受到數據包則判定丟包,並重新發送本次請求。
下面我們將通過在客戶端向服務器端發送文件以及請求下載文件...將該例子是如何解決Udp丟包的問題。
個人優化:基於上章項目的Tcp協議下通訊,模擬聊天的同時發送文件。我們開辟兩個端口,一個基於Tcp協議下進行聊天通訊,一個基於Udp協議下傳送文件通訊。
項目大致思路如下:
發送文件:1.tcp客戶端(文件發送端)先在本端初始化Udp服務端,並綁定好相應的端口,並將Ip地址以及端口號發送到tcp服務器端(文件接收端)。
2.tcp服務端(文件接收端)接收到發送文件請求後,初始化Udp客戶端,並向指定的Udp服務端發送已准備完畢信息。
3.Udp服務器端接收到Udp客戶端已准備好的信息後,初始化要發送文件對象,獲取文件的基本參數(文件名、文件大小、數據大小、數據包總數)。
4.Udp客戶端收到文件的基本參數後想Udp服務端發送開始發送文件,以及此時接受到文件之後的偏移量,告訴Udp服務端應該從文件的哪個部分發送數據。此時啟動Timer定時器。
5.Udp服務器端收到文件繼續發送的請求後,設置文件的偏移量,然後發送對應的數據包。此時關閉Timer定時器。
6.循環4-5過程直到文件發送完畢後,Udp客戶端向Udp服務端發送文件接受完畢消息,並與3秒後關閉Udp客戶端套接字。
7.Udp接受到文件接受完畢的消息後,關閉該套接字。
8.Udp客戶端一段時間沒接受到消息則出發Timer的定時觸發事件,重新發送本次請求。
接收文件:1.tcp客戶端(文件發送端)先在本端初始化Udp服務端,並綁定好相應的端口,並將Ip地址以及端口號發送到tcp服務器端(文件接收端)。
2.tcp服務端(文件接收端)接收到發送文件請求後,初始化Udp客戶端,並根據文件名初始化要發送文件對象,獲取文件的基本參數(文件名、文件大小、數據大小、數據包總數),並將此信息轉成協議信息發送到Udp服務端。
後面的過程與上類似,我就不再馬字了......
下面的過程主要針對發送文件進行講解:
對於文件信息以及各種其他確認請求通過自定義協議發送後並解析的情況,在上一章都已經展示給大家看了。
Udp服務端以及Udp客戶端繼承UdpCommon公用抽象類,主要存放兩端共同所需對象及方法:
全局成員 Socket worksocket; EndPoint sendEP; EndPoint reciveEP; buffersize = ; RequestFile requestFile; FileSendManager sendmanager; FileReciveManager recivemanager; HandlerMessage handlerMsg = HandlerMessage(); ReciveCallbackDelegate( length, ReciveCallbackDelegate ReciveCallbackEvent; TimeOutHandlerDelegate(MutualMode mode, TimeOutHandlerDelegate TimeOutHandlerEvent; 構造函數 = 抽象方法 ReciveBufferHandler( length, 異步接受/發送buffer [] buffer = , buffer.Length, SocketFlags.None, => (asyncResult.IsCompleted) length = worksocket.EndReceiveFrom(asyncResult, (TimeOutHandlerEvent != AsynSend(=> (asyncResult.IsCompleted) (TimeOutHandlerEvent !=
Udp服務端初始化Socket套接字,並初始化相應的消息返回處理委托事件:
命名空間 IPEndPoint ipep = IPEndPoint(IPAddress.Parse(), .worksocket = .reciveEP = .sendEP = (EndPoint)( IPEndPoint(IPAddress.Any, IPEndPoint localEP = (IPEndPoint)== (requestFile.Mode == RequestMode.send) .ReciveCallbackEvent += .ReciveCallbackEvent += ReadySendBuffer( length, = handlerMsg.HandlerObject(Encoding.UTF8.GetString(buffer, (msgPro.MessageType == MessageType.text && msgPro.MessageInfo.Content == -= += (sendmanager == = = ReciveBufferHandler( length, (requestFile.Mode == RequestMode.send)
因為兩端都存在SendFile以及ReciveFile的方法,我們在UdpCommon實現兩個方法方便兩端共同調用。
准備發送/接收文件 = ReadyReciveBuffer( length, = handlerMsg.HandlerObject(Encoding.UTF8.GetString(buffer, -= += = 發送/接收文件 SendFile( length, protocol = Encoding.UTF8.GetString(buffer, = (msgPro.MessageType == msg =[] strArr = msg.Split(= (Status)Enum.Parse((Status), strArr[ (status === Convert.ToInt32(strArr[]); AsynSend(sendmanager.Read()); Console.WriteLine( ReciveFile( length, 發送請求下一個包 MessageProtocol msgPro = , ( (recivemanager.status == tempsize = recivemanager.fileobject.FileLength - (tempsize <= buffersize == 釋放資源 (worksocket !=
下面我來看看Udp客戶端是怎樣工作的......
命名空間 TimerManager timermanager; .worksocket = .sendEP = .reciveEP = IPEndPoint(IPAddress.Parse(), (_requestFile.Mode == RequestMode.send) .ReciveCallbackEvent += = MessageProtocol( .ReciveCallbackEvent += = = timermanager = TimerManager(+= .TimeOutHandlerEvent += ReciveBufferHandler( length, (requestFile.Mode == RequestMode.send) TimeOutHandler(MutualMode mode, .temp_buffer = (mode == (! (temp_buffer !=
我們來看看運行效果:
發送文件:
接收文件:
由此對於文件使用Udp傳送的過程我們完成了,至於在上面的測試過程中丟包問題,我在同學的電腦上測試過丟包問題,由於不方便截圖,就不放到上面了,上面測試過程中如果出現丟包會重新發送本次請求,並接受數據。
附上源碼:SocketProQuests.zip