TCP发送简单的洪水控制和同步-P2P网络摄像头
在一些好人的帮助下,我慢慢建立了一个小型的P2P应用程序,它发送和接收大约4kb的图像流。TCP发送简单的洪水控制和同步-P2P网络摄像头
在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;
}
}
嗯,这是不是一个TCP特定问题。你正在消耗更快的生产。因此你需要控制你的制作人。
我会改变生产者等待来自消费者的确认,然后再发送下一张图片。在此期间我会放弃所有新图像。
在生产者你会保持一个状态标志,它允许你跟踪一个帧是否已被发送和它还没有被确认。虽然这个标志是真的,你丢弃新的图像,因为它们出现。如果为false,则发送图像并将其设置为true。当确认到来时,您将标志设置为false。
编辑:我会将确认作为“bool”(网络上的一个字节)来执行,因为这会比发送图像作为响应要快得多。我将定义两个“消息”:MessageType.Image和MessageType.Acknowledgement。接收器可以看到哪个MessageType到达并更新屏幕或开始发送下一个图像。
编辑2:你不需要丢弃图像。你可以有一个变量Image latestUnsentImage
。当凸轮产生图像时,您无条件覆盖此变量。当您需要发送图像时,您只需访问此变量。这将始终发送最新的可用和未发送的图像。
这里发生的事情是,通过生成数据来发送数据比通过网络发送要快得多,可以填满发送机器上的缓冲区。我的做法是
- 在类级别创建两个布尔标志newImageAvailable和readyToSend,都起始于假
- 在NewFrameReceived创建一个新的形象,但还没有把它。原子字节[]存储类vairable代替,并设置newImageAvailable为true,则调用新的功能TrySendImage()(参见下面的信息)
- 在TCP连接的设置readyToSend为true,并调用TrySendImage()
- 在TrySendImage ()
- ....检查是否newImageAvailable和readyToSend是真的,如果没有的话 没有
- ....设置newImageAvailable和readyToSend假
- ....发送图像(大小+数据)异步(BeginSend())
- 在用于BeginSend()设定readyToSend完成通知为true
虽然这是有点复杂,它可以确保,始终发送最新的图像,并且只有在之前的图像“在电线上”时才发送。
我认为这优于“1:1发送/接收”解决方案,因为您经常遇到两种方向带宽不同的情况 - 在这种情况下,1:1解决方案会降低良好方向的性能去表现糟糕的方向。
为了动态调整JPEG编码的质量参数,我在类似项目中使用的一种优化方法是:如果帧速率低于阈值,则将质量降低一个因子,直到达到最小值,如果帧速率高于另一阈值将质量提高一个因子,直到达到最大值。
感谢您的回复,您的解决方案看起来比较优越,但比我在这个阶段所熟悉的要复杂一点,一旦我找到了一个简单的解决方案,我就会尝试。我喜欢jpeg可变压缩的想法。 Btw会改变bitdepth影响的大小,它现在在24bit,16bit更适合这个? – sprocket12 2012-01-13 12:13:18
至于“usr”关于节流的说法,而不是丢弃图像,您可能希望将这些捕获的图像添加到队列中。从te队列中,你应该能够一个接一个地通过另一个线程接通它们并通过网络传输它们。您甚至可以将序列号添加到除布尔确认之外的正在传输的图像,以确保传输图像。任何没有运输的东西都应该再次入场。排序应该允许您排序正确的图像顺序并产生较少的数据丢失。
发件人
- 创建序列号
- 分配布尔标志
- 排入在传输队列
- 出列和运输(独立的线程)
- 重新队列如果没有确认
接收器(可能你可以做什么)
- 从网络接收
- 发送确认
- 检查序列号和排序(可存储测序 编号入住时需提供)
谢谢,这是类似于我已经提出了什么。这个“确认”会是更好的小布尔值还是更好地等待来自相反方向的图像作为确认? – sprocket12 2012-01-13 09:47:06
编辑......... – usr 2012-01-13 09:55:24
感谢您的想法,他们很简单,在我的范围内。当与上面的Eugen Rieck建议的变量jpeg压缩相结合时,希望也能有效。 – sprocket12 2012-01-13 12:10:54