Socket编程 (异步通讯,解决Udp丢包) - Part4

原文http://www.cnblogs.com/zengqinglei/archive/2013/05/15/3079007.html

Socket编程 (异步通讯,解决Udp丢包)

  对于基于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公用抽象类,主要存放两端共同所需对象及方法:  

   /// <summary>
    /// Udp异步通讯公有类
    /// 实现server与client相同方法
    /// </summary>
    public abstract class UdpCommon : IDisposable
    {
        #region 全局成员
        protected Socket worksocket;//当前套接字
        protected EndPoint sendEP;//发送端remote
        protected EndPoint reciveEP;//接受端remote
        protected int buffersize = 1024;//数据缓冲区大小

        protected RequestFile requestFile;//文件请求

        protected FileSendManager sendmanager;//文件发送管理
        protected FileReciveManager recivemanager;//文件接收管理

        protected HandlerMessage handlerMsg = new HandlerMessage();//消息协议处理类

        protected delegate void ReciveCallbackDelegate(int length, byte[] buffer);
        protected event ReciveCallbackDelegate ReciveCallbackEvent;//消息接收处理事件

        protected delegate void TimeOutHandlerDelegate(MutualMode mode, byte[] buffer);
        protected event TimeOutHandlerDelegate TimeOutHandlerEvent;//超时处理
        #endregion

        #region 构造函数
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="_requestFile">请求信息</param>
        public UdpCommon(RequestFile _requestFile)
        {
            requestFile = _requestFile;
        }
        #endregion

        #region 抽象方法
        /// <summary>
        /// 接受buffer处理
        /// </summary>
        /// <param name="length"></param>
        /// <param name="buffer"></param>
        public abstract void ReciveBufferHandler(int length, byte[] buffer);
        #endregion

        #region 异步接受/发送buffer
        /// <summary>
        /// 异步接受buffer
        /// </summary>
        protected void AsynRecive()
        {
            byte[] buffer = new byte[buffersize];
            worksocket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref reciveEP,
                asyncResult =>
                {
                    if (asyncResult.IsCompleted)//信息接收完成
                    {
                        int length = worksocket.EndReceiveFrom(asyncResult, ref sendEP);
                        if (TimeOutHandlerEvent != null)
                        {
                            TimeOutHandlerEvent.Invoke(MutualMode.recive, null);
                        }
                        ReciveCallbackEvent.Invoke(length, buffer);
                    }
                }, null);
        }
        /// <summary>
        /// 异步发送buffer
        /// </summary>
        /// <param name="buffer"></param>
        protected void AsynSend(byte[] buffer)
        {
            worksocket.BeginSendTo(buffer, 0, buffer.Length, SocketFlags.None, sendEP,
                asyncResult =>
                {
                    if (asyncResult.IsCompleted)//消息发送完成
                    {
                        worksocket.EndSendTo(asyncResult);
                        if (TimeOutHandlerEvent != null)
                        {
                            TimeOutHandlerEvent.Invoke(MutualMode.send, buffer);
                        }
                    }
                }, null);
        }
        #endregion
}

Udp服务端初始化Socket套接字,并初始化相应的消息返回处理委托事件:

using System;
using System.Collections.Generic;
using System.Text;
#region 命名空间
using System.Net;
using System.Net.Sockets;
using System.IO;
using SocketCommon;
#endregion

namespace ClientConsole
{
    public class UdpServer : UdpCommon
    {
        public UdpServer(RequestFile _requestFile)
            : base(_requestFile)
        {
            //初始化套接字
            IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("192.168.1.108"), 0);
            base.worksocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            base.worksocket.Bind(ipep);
            base.reciveEP = base.sendEP = (EndPoint)(new IPEndPoint(IPAddress.Any, 0));

            //返回Ip地址及端口信息
            IPEndPoint localEP = (IPEndPoint)base.worksocket.LocalEndPoint;
            requestFile.Address = localEP.Address.ToString();
            requestFile.Port = localEP.Port;

            //根据不同的请求初始化相应的事件
            if (requestFile.Mode == RequestMode.send)//发送准备
            {
                base.ReciveCallbackEvent += new ReciveCallbackDelegate(ReadySendBuffer);
            }
            else //接受准备
            {
                base.ReciveCallbackEvent += new ReciveCallbackDelegate(ReadyReciveBuffer);
            }
            base.AsynRecive();
        }
        /// <summary>
        /// 准备发送文件信息
        /// </summary>
        /// <param name="length"></param>
        /// <param name="buffer"></param>
        public void ReadySendBuffer(int length, byte[] buffer)
        {
            MessageProtocol msgPro = handlerMsg.HandlerObject(Encoding.UTF8.GetString(buffer, 0, length));
            if (msgPro.MessageType == MessageType.text && msgPro.MessageInfo.Content == "ready")
            {
                ReciveCallbackEvent -= new ReciveCallbackDelegate(ReadySendBuffer);
                ReciveCallbackEvent += new ReciveCallbackDelegate(ReciveBufferHandler);
                if (sendmanager == null)
                {
                    sendmanager = new FileSendManager(requestFile.FileObject);
                    Console.WriteLine("发送文件:{0}", sendmanager.fileobject.FileName);
                }
                msgPro = new MessageProtocol(RequestMode.send, sendmanager.fileobject);
                AsynSend(msgPro.ToBytes());
                AsynRecive();
            }
        }
        /// <summary>
        /// 接受下一个包请求或者发送下一个数据包
        /// </summary>
        /// <param name="length"></param>
        /// <param name="buffer"></param>
        public override void ReciveBufferHandler(int length, byte[] buffer)
        {
            if (requestFile.Mode == RequestMode.send) //发送文件
            {
                SendFile(length, buffer);
            }
            else//写入文件
            {
                ReciveFile(length, buffer);
            }
        }
    }
}

  因为两端都存在SendFile以及ReciveFile的方法,我们在UdpCommon实现两个方法方便两端共同调用。

#region 准备发送/接收文件
        /// <summary>
        /// 准备接收
        /// </summary>
        protected void ReciveReady()
        {
            recivemanager = new FileReciveManager(requestFile.FileObject);
            Console.WriteLine("接受文件:{0}", recivemanager.fileobject.FileName);
            RequestAction();
        }
        /// <summary>
        /// 准备接收文件Buffer
        /// </summary>
        /// <param name="length"></param>
        /// <param name="buffer"></param>
        protected void ReadyReciveBuffer(int length, byte[] buffer)
        {
            MessageProtocol msgPro = handlerMsg.HandlerObject(Encoding.UTF8.GetString(buffer, 0, length));
            ReciveCallbackEvent -= new ReciveCallbackDelegate(ReadyReciveBuffer);
            ReciveCallbackEvent += new ReciveCallbackDelegate(ReciveBufferHandler);
            requestFile = msgPro.RequestFile;
            ReciveReady();
        }
        #endregion

        #region 发送/接收文件
        /// <summary>
        /// 发送文件
        /// </summary>
        /// <param name="length"></param>
        /// <param name="buffer"></param>
        protected void SendFile(int length, byte[] buffer)
        {
            string protocol = Encoding.UTF8.GetString(buffer, 0, length);
            MessageProtocol msgPro = handlerMsg.HandlerObject(protocol);
            if (msgPro.MessageType == MessageType.text)
            {
                string msg = msgPro.MessageInfo.Content;
                string[] strArr = msg.Split('|');
                Status status = (Status)Enum.Parse(typeof(Status), strArr[0]);
                if (status == Status.keepon)
                {
                    sendmanager.offset = Convert.ToInt32(strArr[1]);//文件偏移量
                    AsynSend(sendmanager.Read());//发送下一个包
                    Console.WriteLine("已发送:{0}%", sendmanager.GetPercent());
                    AsynRecive();
                }
                else Dispose();
            }
        }
        /// <summary>
        /// 接受文件
        ///     1.未接收完之前将文件保存为临时文件
        ///     2.完毕后通过moveTo重命名
        /// </summary>
        /// <param name="length"></param>
        /// <param name="buffer"></param>
        protected void ReciveFile(int length, byte[] buffer)
        {
            recivemanager.Write(buffer);
            Console.WriteLine("已接受:{0}%", recivemanager.GetPercent());
            RequestAction();
        }
        #endregion

        #region 发送请求下一个包
        /// <summary>
        /// 发送请求下一个包
        /// </summary>
        public void RequestAction()
        {
            //根据状态处理
            MessageProtocol msgPro = new MessageProtocol(
                    String.Format("{0}|{1}", (int)recivemanager.status, recivemanager.offset));
            if (recivemanager.status == Status.keepon)
            {
                AsynSend(msgPro.ToBytes());
                long tempsize = recivemanager.fileobject.FileLength - recivemanager.offset;
                if (tempsize < recivemanager.fileobject.PacketSize)
                {
                    buffersize = Convert.ToInt32(tempsize);
                }
                else buffersize = recivemanager.fileobject.PacketSize;
                AsynRecive();
            }
            else
            {
                TimeOutHandlerEvent = null;
                AsynSend(msgPro.ToBytes());
            }
        }
        #endregion

        #region 释放资源
        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            if (worksocket != null)
            {
                Thread.Sleep(3);
                worksocket.Close(3);
            }
        }
        #endregion

下面我来看看Udp客户端是怎样工作的......

using System;
using System.Collections.Generic;
using System.Text;
#region 命名空间
using System.Net;
using System.Net.Sockets;
using System.IO;
using SocketCommon;
using System.Timers;
#endregion

namespace ServerConsole
{
    public class UdpClient : UdpCommon
    {
        /// <summary>
        /// 定时器辅助类
        /// </summary>
        TimerManager timermanager;//定时(用于超时重发)
        //当前发送请求数据缓存
        byte[] temp_buffer;
        public UdpClient(RequestFile _requestFile)
            : base(_requestFile)
        {
            //初始化套接字
            base.worksocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            base.sendEP = new IPEndPoint(IPAddress.Parse(_requestFile.Address), requestFile.Port);
            base.reciveEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0);

            //根据不同的请求返回对应的消息
            MessageProtocol msgPro;
            if (_requestFile.Mode == RequestMode.send)//接受准备
            {
                base.ReciveCallbackEvent += new ReciveCallbackDelegate(ReadyReciveBuffer);
                msgPro = new MessageProtocol("ready");
            }
            else //发送准备
            {
                base.ReciveCallbackEvent += new ReciveCallbackDelegate(ReciveBufferHandler);
                sendmanager = new FileSendManager(requestFile.FileObject);
                msgPro = new MessageProtocol(RequestMode.recive, sendmanager.fileobject);
                Console.WriteLine("发送文件:{0}", sendmanager.fileobject.FileName);
            }
            //初始化定时器
            timermanager = new TimerManager(3000);
            timermanager.ElapsedEvent += new TimerManager.ElapsedDelegate(ElapsedEvent);
            base.TimeOutHandlerEvent += new TimeOutHandlerDelegate(TimeOutHandler);
            base.AsynSend(msgPro.ToBytes());
            base.AsynRecive();
        }
        /// <summary>
        /// 请求下一个数据包或者发送下一个数据包
        /// </summary>
        /// <param name="length"></param>
        /// <param name="buffer"></param>
        public override void ReciveBufferHandler(int length, byte[] buffer)
        {
            if (requestFile.Mode == RequestMode.send)//接受来自客户端的文件
            {
                ReciveFile(length, buffer);
            }
            else //向客户端发送文件
            {
                SendFile(length, buffer);
            }
        }

        private void TimeOutHandler(MutualMode mode, byte[] buffer)
        {
            this.temp_buffer = buffer;
            if (mode == MutualMode.send)
            {
                if (!timermanager.IsRuning)
                {
                    timermanager.Start();
                }
            }
            else
            {
                if (timermanager.IsRuning)
                {
                    timermanager.Stop();
                }
            }
        }
        /// <summary>
        /// 超时后重发当前请求
        /// </summary>
        private void ElapsedEvent()
        {
            if (temp_buffer != null)
            {
                Console.WriteLine("发生丢包,重新请求...");
                base.AsynSend(temp_buffer);
            }
        }
    }
}

我们来看看运行效果:

  发送文件:

  接收文件:

由此对于文件使用Udp传送的过程我们完成了,至于在上面的测试过程中丢包问题,我在同学的电脑上测试过丢包问题,由于不方便截图,就不放到上面了,上面测试过程中如果出现丢包会重新发送本次请求,并接受数据。

附上源码:SocketProQuests.zip

作者:曾庆雷
出处:http://www.cnblogs.com/zengqinglei
本页版权归作者和博客园所有,欢迎转载,但未经作者同意必须保留此段声明, 且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利
时间: 2024-07-28 12:49:39

Socket编程 (异步通讯,解决Udp丢包) - Part4的相关文章

Socket编程 (异步通讯,解决Tcp粘包) - Part3

原文 http://www.cnblogs.com/zengqinglei/archive/2013/05/14/3078842.html Socket编程 (异步通讯,解决Tcp粘包) 从上一章的通讯中,我们发现如果使用Tcp连续发送消息会出现消息一起发送过来的情况,这样给我们编程造成一定的问题,给我们的信息解析造成一定的问题.那么这篇文章就将针对以上问题给出解决方案......   问题一般会出现的情况如下,假设我们连续发送两条两天记录("我是liger_zql"): 模拟发送示例

udp丢包 又是udp丢包

什么会导致udp丢包呢,我这里列举了如下几点原因: 1.调用recv方法接收端收到数据后,处理数据花了一些时间,处理完后再次调用recv方法,在这二次调用间隔里,发过来的包可能丢失.对于这种情况可以修改接收端,将包接收后存入一个缓冲区,然后迅速返回继续recv. 2.发送的包巨大丢包.虽然send方法会帮你做大包切割成小包发送的事情,但包太大也不行.例如超过30K的一个udp包,不切割直接通过send方法发送也会导致这个包丢失.这种情况需要切割成小包再逐个send. 3.发送的包较大,超过mtu

UDP丢包原因

一.主要丢包原因 1.接收端处理时间过长导致丢包:调用recv方法接收端收到数据后,处理数据花了一些时间,处理完后再次调用recv方法,在这二次调用间隔里,发过来的包可能丢失.对于这种情况可以修改接收端,将包接收后存入一个缓冲区,然后迅速返回继续recv. 2.发送的包巨大丢包:虽然send方法会帮你做大包切割成小包发送的事情,但包太大也不行.例如超过50K的一个udp包,不切割直接通过send方法发送也会导致这个包丢失.这种情况需要切割成小包再逐个send. 3.发送的包较大,超过接受者缓存导

小弟目前要做一个SOCKET编程客户端通讯软件,与多个服务器进行通讯

问题描述 这个项目主要是上位机与下位机通讯方面的东西,因为之前从来没有写过这方面的软件,所以对这个是一无所知.这个软件我想实现的功能就是编写一个客户端上位机软件与多台服务器通讯,当然这些服务器都是一些网卡设备说具体一点就是一些带网卡的单片机或者ARM,在此之前我试了很多次方法(比如说异步多线程什么的)都没有成功但是几天前用了异步方法只是连接成功了但是异步接受还是不行,我知道解决这个方面的问题就是你连接多少个服务器IP就必须必须写多少个接收数据的方法,但是这个系统没有限制呀<有可能是100个设备,

解决网络丢包问题及故障判断方法

  我们首先来认识一下什么是丢包,以及什么样的现象被成为是网络丢包: 数据在INTERNET上是以数据包为单位传输的,每包nK,不多也不少.这就是说,不管网络线路有多好.网络设备有多强悍,你的数据都不会是以线性(就象打电话一样)传输的,中间总是有空洞的.数据包的传输,不可能百分之百的能够完成,因为种种原因,总会有一定的损失.碰到这种情况,INTERNET会自动的让双方的电脑根据协议来补包和重传该包.如果网络线路好.速度快,包的损失会非常小,补包和重传的工作也相对较易完成,因此可以近似的将所传输的

如何解决网络丢包问题

  数据在INTERNET上是以数据包为单位传输的,每包nK,不多也不少.这就是说,不管网络线路有多好.网络设备有多强悍,你的数据都不会是以线性(就象打电话一样)传输的,中间总是有空洞的.数据包的传输,不可能百分之百的能够完成,因为种种原因,总会有一定的损失.碰到这种情况,INTERNET会自动的让双方的电脑根据协议来补包和重传该包.如果网络线路好.速度快,包的损失会非常小,补包和重传的工作也相对较易完成,因此可以近似的将所传输的数据看做是无损的.但是,如果网络线路较差,数据的损失量就会非常大,

UDP 丢包 急。。谢谢

问题描述 我的udpclient端发送的太快,udpserver来不及接收?据说可以使用缓冲区来处理..麻烦哪位大牛帮忙说说怎么做?非常感谢. 解决方案 解决方案二:UDP协议丢包很正常.解决方案三:UDP就是丢包的没办法,如果你要自己实现UDP的差错控制的话,那还不如直接使用TCP解决方案四:我知道丢包很严重,可是这个对实时性要求很强,选择UDP是不错的..主要是我在接收的时候来不及接..我做处理了,.处理就是写入io流.如果不处理的话就有足够的时间来接了..麻烦哪位能说下,如何能把两者分开,

SOCKET 编程TCP/IP、UDP

TCP/IP 资源:http://download.csdn.net/detail/mao0514/9061265 server: #include<stdio.h> #include<winsock2.h> void main() { SOCKET servsock,clisock; struct sockaddr_in sa; struct sockaddr_in cliaddr; int servport=6666; char buff[256]; WSADATA ws; i

java udp丢包严重

问题描述 我在客户端一次性发了100000万条消息,抓包工具也显示所有包都收到了,可是程序里就是有部分包收不到,这是为什么? 解决方案 解决方案二:10亿?求程序处理方案,或者代码解决方案三:被底层操作系统给丢掉了?解决方案四:可是抓包工具全部抓到了呀解决方案五:你做压力测试么?搞这么大数据量解决方案六:这么说一定是程序原因了?同求代码和处理方法