jjzjj

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

coder 2023-09-19 原文

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

在 127.0.0.1 上,接收跟上发送,但是当我在远程机器上尝试时,在我看来接收跟不上,我可能发送了 6 张图像,但接收者到目前为止只收到了一张。 .. 随着时间的推移,差异越来越大,直到你在另一个屏幕上看到整整一分钟前的自己。值得注意的是,我希望它能在另一个国家/地区的大约 64kbps-100kbps 的连接上正常工作,那里的 ping 时间可能非常长,如 250 毫秒或更长。

我有哪些同步选项?

我的兄弟告诉我一个简单的解决方案,即实现 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;
    }
}

最佳答案

好吧,这不是 TCP 特定的问题。你生产的速度快于你消费的速度。因此,您需要限制生产者。

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

在生产者上,您会保留一个状态标志,它允许您跟踪帧是否已发送是否未被确认。当此标志为真时,您会在新图像出现时丢弃它们。当它为 false 时,您发送图像并将其设置为 true。当收到确认时,您将标志设置为 false。

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

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

关于c# - TCP 发送简单的洪水控制和同步 - P2P 网络摄像头,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8848434/

有关c# - TCP 发送简单的洪水控制和同步 - P2P 网络摄像头的更多相关文章

  1. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  2. jquery - 我的 jquery AJAX POST 请求无需发送 Authenticity Token (Rails) - 2

    rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送

  3. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  4. ruby - 使用 Ruby 通过 Outlook 发送消息的最简单方法是什么? - 2

    我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=

  5. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  6. ruby - 是否可以在不实际发送或读取数据的情况下查明 ruby​​ 套接字是否处于 ESTABLISHED 或 CLOSE_WAIT 状态? - 2

    s=Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)s.connect(Socket.pack_sockaddr_in('port','hostname'))ssl=OpenSSL::SSL::SSLSocket.new(s,sslcert)ssl.connect从这里开始,如果ssl连接和底层套接字仍然是ESTABLISHED,或者它是否在默认值7200之后进入CLOSE_WAIT,我想检查一个线程几秒钟甚至更糟的是在实际上不需要.write()或.read()的情况下关闭。是用select()、IO.select()还是其他方法完成

  7. ruby - 如何理解 Ruby 中的发送者和接收者? - 2

    我很难理解Ruby中sender和receiver的实际含义。它们一般是什么意思?到目前为止,我只是将它们理解为方法调用和获取其返回值的调用。但是,我知道我的理解还远远不够。谁能给我一个Ruby中发送者和接收者的具体解释? 最佳答案 面向对象中的一个核心概念是消息传递和早期概念化,这在很大程度上借鉴了计算的Actor模型。艾伦·凯(AlanKay)创造了面向对象一词并发明了最早的OO语言之一SmallTalk,他拥有voicedregretatusingatermwhichputthefocusonobjectsinsteadofo

  8. ruby - 动态扩展现有方法或覆盖 ruby​​ 中的发送方法 - 2

    假设我们有A、B、C类。Adefself.inherited(sub)#metaprogramminggoeshere#takeclassthathasjustinheritedclassA#andforfooclassesinjectprepare_foo()as#firstlineofmethodthenrunrestofthecodeenddefprepare_foo#=>prepare_foo()neededhere#somecodeendendBprepare_foo()neededhere#somecodeendend如您所见,我正在尝试将foo_prepare()调用注入

  9. ruby-on-rails - 如何通过 POST 发送多个相同的键/参数? - 2

    如果我必须在一个HTTP请求中发送一堆post参数,所有这些参数都具有相同的名称,我该如何构建要发布的data对象?想象一个带有一些复选框的表单,它们都具有相同的name属性但具有不同的值(如果它们被选中):我想用ruby​​构建它(但它需要根据在表单上选择的内容动态创建):data={"color"=>"red","color"=>"green","color"=>"blue"}然后将数据发送到某个URL:Net::HTTP.post_form(url,data)我无法控制接收端,所以我必须发送它期望接收的参数。怎么办? 最佳答案

  10. c# - C# 中的 Flatten Ruby 方法 - 2

    我如何做Ruby方法"Flatten"RubyMethod在C#中。此方法将锯齿状数组展平为一维数组。例如:s=[1,2,3]#=>[1,2,3]t=[4,5,6,[7,8]]#=>[4,5,6,[7,8]]a=[s,t,9,10]#=>[[1,2,3],[4,5,6,[7,8]],9,10]a.flatten#=>[1,2,3,4,5,6,7,8,9,10 最佳答案 递归解决方案:IEnumerableFlatten(IEnumerablearray){foreach(variteminarray){if(itemisIEnume

随机推荐