在来自 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/
如何在ruby中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL
rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送
我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha
我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
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()还是其他方法完成
我很难理解Ruby中sender和receiver的实际含义。它们一般是什么意思?到目前为止,我只是将它们理解为方法调用和获取其返回值的调用。但是,我知道我的理解还远远不够。谁能给我一个Ruby中发送者和接收者的具体解释? 最佳答案 面向对象中的一个核心概念是消息传递和早期概念化,这在很大程度上借鉴了计算的Actor模型。艾伦·凯(AlanKay)创造了面向对象一词并发明了最早的OO语言之一SmallTalk,他拥有voicedregretatusingatermwhichputthefocusonobjectsinsteadofo
假设我们有A、B、C类。Adefself.inherited(sub)#metaprogramminggoeshere#takeclassthathasjustinheritedclassA#andforfooclassesinjectprepare_foo()as#firstlineofmethodthenrunrestofthecodeenddefprepare_foo#=>prepare_foo()neededhere#somecodeendendBprepare_foo()neededhere#somecodeendend如您所见,我正在尝试将foo_prepare()调用注入
如果我必须在一个HTTP请求中发送一堆post参数,所有这些参数都具有相同的名称,我该如何构建要发布的data对象?想象一个带有一些复选框的表单,它们都具有相同的name属性但具有不同的值(如果它们被选中):我想用ruby构建它(但它需要根据在表单上选择的内容动态创建):data={"color"=>"red","color"=>"green","color"=>"blue"}然后将数据发送到某个URL:Net::HTTP.post_form(url,data)我无法控制接收端,所以我必须发送它期望接收的参数。怎么办? 最佳答案
我如何做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