TCP发送简单的洪水控制和同步 - P2P网络摄像头

时间:2012-01-13 09:35:31

标签: c# sockets networking tcp webcam

在SO的一些好人的帮助下,我慢慢建立了一个小型P2P应用程序,每个应用程序发送和接收大约4kb的图像流。

在127.0.0.1上接收与发送保持同步,但是当我在远程计算机上尝试时,在我看来接收无法跟上,我可能已经发送了6张图像,但接收器到目前为止只收到了一张。 ..随着时间的推移,差异变得更大,直到你在一分钟前看到自己在另一个屏幕上。值得注意的是,我希望这能在其他国家的64kbps-100kbps连接上运行良好,而ping时间可能非常大,只需250ms或更长。

我有哪些同步选项?

我的兄弟告诉我一个简单的解决方案是实现1:1的发送/接收。所以我只收到一张图片。

由于我是网络编程的初学者,所以欢迎任何其他提示,这是我的完整代码:

namespace MyPrivateChat
{
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using AForge.Video;
    using AForge.Video.DirectShow;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Net;
    using System.Text.RegularExpressions;
    using System.Net.Sockets;
    using System.Diagnostics;
    using AForge.Imaging.Filters;

    public partial class fChat : Form
    {
        public fChat()
        {
            InitializeComponent();
        }
        private void fChat_Load(object sender, EventArgs e)
        {
            // get ip
            _ownExternalIp = GetPublicIP();
            Text = "My Private Chat - IP: " + _ownExternalIp;

            // get video cam
            var _videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
            if (_videoDevices.Count != 0)
            {
                _videoDevice = new VideoCaptureDevice(_videoDevices[0].MonikerString);
                btnStart.Enabled = true;
            }

            // fire up listener
            listeningThread.RunWorkerAsync();
        }
        public string GetPublicIP()
        {
            string ip = "";
            using (WebClient wc = new WebClient())
            {
                Match m = Regex.Match(wc.DownloadString("http://checkip.dyndns.org/"), @"(?<IP>\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})");
                if (m.Success)
                {
                    ip = m.Groups["IP"].Value;
                }
            }
            return ip;
        }
        private void mnuPasteOwnIP_Click(object sender, EventArgs e)
        {
            txtPartnerIP.Text = _ownExternalIp;
        }
        private void btnStart_Click(object sender, EventArgs e)
        {
            if (_tcpOut == null)
            {
                // tcp server setup
                _tcpOut = new TcpClient();
                _tcpOut.Connect(txtPartnerIP.Text, 54321);
                tmrLive.Enabled = true;
            }
            else
            {
                tmrLive.Enabled = false;
                _tcpOut.Client.Disconnect(true);
                _tcpOut.Close();
                _tcpOut = null;
            }

            if (!_videoDevice.IsRunning)
            {
                _videoDevice.NewFrame += new NewFrameEventHandler(NewFrameReceived);
                _videoDevice.DesiredFrameSize = new Size(640, 480);
                _videoDevice.DesiredFrameRate = 100;
                _videoDevice.Start();
                btnStart.Text = "Stop";
            }
            else
            {
                _videoDevice.SignalToStop();
                btnStart.Text = "Start";
            }
        }
        private void NewFrameReceived(object sender, NewFrameEventArgs e)
        {
            Bitmap img = (Bitmap)e.Frame.Clone();
            byte[] imgBytes = EncodeToJpeg(img, 25).ToArray();

            if (_tcpOut.Connected)
            {
                NetworkStream ns = _tcpOut.GetStream();
                if (ns.CanWrite)
                {
                    ns.Write(BitConverter.GetBytes(imgBytes.Length), 0, 4);
                    ns.Write(imgBytes, 0, imgBytes.Length);
                    _totalFramesSent++;
                }
            }
        }
        private void listeningThread_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            _tcpIn = new TcpListener(IPAddress.Any, 54321);
            _tcpIn.Start();

            TcpClient _inClient = _tcpIn.AcceptTcpClient();
            lblStatus.Text = "Connected - Receiving Broadcast";
            tmrLive.Enabled = true;

            NetworkStream ns = _inClient.GetStream();
            while (true)
            {
                // read image size. 
                Byte[] imgSizeBytes = new Byte[4];
                int totalBytesRead = 0;
                do
                {
                    int bytesRead = ns.Read(imgSizeBytes, totalBytesRead, 4 - totalBytesRead);
                    if (bytesRead == 0)
                    {
                        break; // problem
                    }
                    totalBytesRead += bytesRead;
                } while (totalBytesRead < 4);

                // read image
                int imgSize = BitConverter.ToInt32(imgSizeBytes, 0);
                Byte[] imgBytes = new Byte[imgSize];
                totalBytesRead = 0;
                do
                {
                    int bytesRead = ns.Read(imgBytes, totalBytesRead, imgSize - totalBytesRead);
                    if (bytesRead == 0)
                    {
                        break; // problem
                    }
                    totalBytesRead += bytesRead;
                } while (totalBytesRead < imgSize);

                picVideo.Image = Image.FromStream(new MemoryStream(imgBytes));
                _totalFramesReceived++;
            }
        }
        private void CloseVideoDevice()
        {
            if (_videoDevice != null)
            {
                if (_videoDevice.IsRunning)
                {
                    _videoDevice.SignalToStop();
                }
                _videoDevice = null;
            }
        }
        private void fChat_FormClosing(object sender, FormClosingEventArgs e)
        {
            CloseVideoDevice();
        }
        private void btnClose_Click(object sender, EventArgs e)
        {
            Close();
        }
        private void tmrLive_Tick(object sender, EventArgs e)
        {
            _totalSecondsLive++;
            lblStats.Text = "S:"+_totalFramesSent + " R:" + _totalFramesReceived + " T:"+ _totalSecondsLive;
            if (_totalSecondsLive == 60)
            {
                MessageBox.Show("Total Frames : " + _totalFramesSent);
            }
        }

        #region ENCODING JPEG

        private MemoryStream EncodeToJpeg(Bitmap img, long quality)
        {
            using (EncoderParameters myEncoderParameters = new EncoderParameters(1))
            {
                MemoryStream ms = new MemoryStream();
                myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality);
                img.Save(ms, GetEncoder(ImageFormat.Jpeg), myEncoderParameters);
                return ms;
            }
        }
        private ImageCodecInfo GetEncoder(ImageFormat format)
        {
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
            foreach (ImageCodecInfo codec in codecs)
            {
                if (codec.FormatID == format.Guid)
                {
                    return codec;
                }
            }
            return null;
        }

        #endregion

        VideoCaptureDevice _videoDevice;
        TcpClient _tcpOut;
        TcpListener _tcpIn;
        string _ownExternalIp;
        int _totalFramesSent;
        int _totalFramesReceived;
        int _totalSecondsLive;
    }
}

3 个答案:

答案 0 :(得分:1)

这不是特定于TCP的问题。你正在消耗更快的生产。因此,你需要限制你的制作人。

在发送下一张图片之前,我会更改制作人等待来自消费者的确认。在此期间我会丢弃所有新图像。

在制作人上,你会保留一个状态标志,可以跟踪一个帧是否已被发送它尚未被确认。当此标志为true时,您会丢弃出现的新图像。如果为false,则发送图像并将其设置为true。确认到来时,您将标志设置为false。

编辑:我将确认实现为“bool”(网络上的一个字节),因为这将比发送图像作为响应更快地确认。我将定义两个“消息”:MessageType.Image和MessageType.Acknowledgement。接收器可以看到哪个消息类型到达并更新屏幕或开始发送下一个图像。

编辑2:您不需要丢弃图像。您可以拥有变量Image latestUnsentImage。当凸轮产生图像时,您无条件地覆盖此变量。当您需要发送图像时,您只需访问此变量。这将始终发送最新的可用和未发送的图像。

答案 1 :(得分:1)

这里发生的事情是,通过生成数据发送速度比通过网络发送的速度快,可以填满发送计算机上的缓冲区。我的方法是

  • 在类级别创建两个布尔标志newImageAvailable和readyToSend,均以false开头
  • 在NewFrameReceived中创建新图像,但尚未发送。原子地将byte []存储在可变类中,并将newImageAvailable设置为true,然后调用new函数TrySendImage()(参见下面的信息)
  • 在Tcp上将readyToSend设置为true并调用TrySendImage()
  • 在TrySendImage()
  • ....检查BOTH newImageAvailable和readyToSend是否为true,如果不是 没什么
  • ....将newImageAvailable和readyToSend设置为false
  • ....异步发送图像(大小+数据)(BeginSend())
  • 在BeginSend()的完成通知中,将readyToSend设置为true

虽然这有点复杂,但它确保了最新图像的发送,并且只有前面的图像是“在线上”。

我认为这优于“1:1发送/接收”解决方案,因为您经常遇到两个方向带宽不同的情况 - 在这种情况下,1:1解决方案会降低良好方向的性能表现不好的方向。

我在类似项目中使用的优化是,动态调整JPEG编码的质量参数:如果帧速率低于阈值,则降低质量一个因子,直到达到最小值,如果帧速率超过另一个阈值增加质量乘以一个因子,再次达到最大值。

答案 2 :(得分:0)

关于“usr”所说的关于限制的内容,而不是丢弃您的图像,您可能希望将这些捕获的图像添加到队列中。从te队列,您应该能够逐个接收它们并通过另一个线程并通过网络传输它们。您甚至可以将序列号添加到与布尔确认分开的图像中,以确保图像的传输。任何未运输的东西都应该再次入队。排序应该允许您对图像的正确顺序进行排序,并减少数据丢失。

发件人

  • 创建序列号
  • 指定bool flag
  • 在传输队列中排队
  • 出队和运输(单独的线程)
  • 如果没有确认则重新排队

接收者(可能你能做的)

  • 从网络接收
  • 发送确认
  • 检查序列号和排序(可能需要存储以进行排序 编号检查)