【场景】:用海康的视频接口连续抓图自己处理图像后返回一个大的byte流(大概有12万到16万字节),然后如何发送到前端实时显示图像?
【工具】:使用开源networkcomms V3.(VS2012,.NetFramework 4.5);
【说明】:这只是我用来测试的代码,测试用的单路发送,并未考虑多路发送的情况,其实也只需要修改调用代码即可;
这个本来用于文件传输的,文件也是byte流,抓图也是返回byte流,一样一样的;
【扩展】:这种场景,还有一种实现方式,把处理后的一张张的图片H265成视频然后直播,不过需要比较多的视频处理技术;
【工程引用第三方库】:
protobuf-net.dll
ProtobufSerializer.dll
NetworkCommsDotNet.dll
检出networkcomms的开源代码示列里面有直接拷贝过来即可
【测试代码】:
using MP.Entity;
using MP.GR;
using NetworkCommsDotNet;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplicationForPictureChunkSend
{
public partial class Form1 : Form
{
private long count = 0;
private FileTransClient _client=new FileTransClient();
public Form1()
{
InitializeComponent();
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);//双缓存防闪屏
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
}
private void button1_Click(object sender, EventArgs e)
{
_client.Initial();
HCPictureCapture.Instance.BMPBufferFull+=Instance_BMPBufferFull;//海康抓图事件
HCPictureCapture.Instance.Start(new HCPresetLoginInfo() {IP="192.168.9.63",Port=8000,UserName="admin",Password="admin12345",Channel=1 });
button1.Visible = false;
}
private void Instance_BMPBufferFull(MP.Entity.HCPresetLoginInfo info, byte[] buffer)
{
_client.SendFile(Guid.NewGuid().ToString(), buffer);//发送大的byte流
}
private void Form1_Load(object sender, EventArgs e)
{
FileTransSvr.Instance.Initial();
//byte流接收完成,显示图片
FileTransSvr.Instance.ReceiveCompleted += ((ConnectionInfo connInfo,Byte[] buffer) =>
{
count++;
this.BeginInvoke(new Action(() =>
{
using (MemoryStream ms = new MemoryStream(buffer))
{
if (null != this.BackgroundImage)
{
this.BackgroundImage.Dispose();
}
this.BackgroundImage = Image.FromStream(ms);
this.Text = count.ToString();
}
}));
});
}
}
}
【接收服务】:
using NetworkCommsDotNet;
using NetworkCommsDotNet.Connections;
using NetworkCommsDotNet.Connections.TCP;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace WindowsFormsApplicationForPictureChunkSend
{
public class FileTransSvr
{
private static object syncRoot = new object();
private Dictionary<ConnectionInfo, Dictionary<long, SendInfo>> incomingDataInfoCache = new Dictionary<ConnectionInfo, Dictionary<long, SendInfo>>();
private Dictionary<ConnectionInfo, Dictionary<long, byte[]>> incomingDataCache = new Dictionary<ConnectionInfo, Dictionary<long, byte[]>>();
private Dictionary<ConnectionInfo, Dictionary<string, ReceivedFile>> receivedFilesDict = new Dictionary<ConnectionInfo, Dictionary<string, ReceivedFile>>();
private static FileTransSvr _instance=null;
public event Action<ConnectionInfo, byte[]> ReceiveCompleted;
public FileTransSvr()
{
NetworkComms.AppendGlobalIncomingPacketHandler<byte[]>("PartialFileData",IncomingPartialFileData);
NetworkComms.AppendGlobalIncomingPacketHandler<SendInfo>("PartialFileDataInfo",IncomingPartialFileDataInfo);
}
~FileTransSvr()
{
}
public static FileTransSvr Instance
{
get
{
if (null == _instance)
{
lock (syncRoot)
{
if (null == _instance)
{
_instance = new FileTransSvr();
}
}
}
return _instance;
}
}
/// <summary>
/// 接收到的文件数据
/// </summary>
/// <param name="packetHeader"></param>
/// <param name="connection"></param>
/// <param name="data"></param>
private void IncomingPartialFileData(PacketHeader packetHeader, NetworkCommsDotNet.Connections.Connection connection, byte[] data)
{
try
{
//SendInfo info = null;
//ReceivedFile file = null;
lock (syncRoot)
{
long sequenceNumber = packetHeader.GetOption(PacketHeaderLongItems.PacketSequenceNumber);
if (!incomingDataCache.ContainsKey(connection.ConnectionInfo))
incomingDataCache.Add(connection.ConnectionInfo, new Dictionary<long, byte[]>());
incomingDataCache[connection.ConnectionInfo].Add(sequenceNumber, data);
// if (incomingDataInfoCache.ContainsKey(connection.ConnectionInfo) &&
// incomingDataInfoCache[connection.ConnectionInfo].ContainsKey(sequenceNumber))
// {
// info = incomingDataInfoCache[connection.ConnectionInfo][sequenceNumber];
// incomingDataInfoCache[connection.ConnectionInfo].Remove(sequenceNumber);
// if (!receivedFilesDict.ContainsKey(connection.ConnectionInfo))
// receivedFilesDict.Add(connection.ConnectionInfo, new Dictionary<string, ReceivedFile>());
// //如果当前收到字节数据,还没有对应的ReceivedFile类,则创建一个
// if (!receivedFilesDict[connection.ConnectionInfo].ContainsKey(info.Filename))
// {
// receivedFilesDict[connection.ConnectionInfo].Add(info.Filename, new ReceivedFile(info.Filename, connection.ConnectionInfo, info.TotalBytes));
// }
// file = receivedFilesDict[connection.ConnectionInfo][info.Filename];
// }
// else
// {
// if (!incomingDataCache.ContainsKey(connection.ConnectionInfo))
// incomingDataCache.Add(connection.ConnectionInfo,new Dictionary<long,byte[]>());
// incomingDataCache[connection.ConnectionInfo].Add(sequenceNumber,data);
// }
//if (info != null && file != null && !file.IsCompleted)
//{
// file.AddData(info.BytesStart,0,data.Length,data);
// if (file.IsCompleted)
// {
// if (null != ReceiveCompleted)
// {
// ReceiveCompleted(connection.ConnectionInfo,file.FileBuffer);
// }
// if (incomingDataCache.ContainsKey(connection.ConnectionInfo))
// incomingDataCache[connection.ConnectionInfo].Clear();
// if (incomingDataInfoCache.ContainsKey(connection.ConnectionInfo))
// incomingDataInfoCache[connection.ConnectionInfo].Clear();
// if (receivedFilesDict.ContainsKey(connection.ConnectionInfo))
// receivedFilesDict[connection.ConnectionInfo].Clear();
// }
// file = null;
// data = null;
//}
//else if (info == null ^ file == null)
//{
// throw new Exception("Either both are null or both are set. Info is " + (info == null ? "null." : "set.") + " File is " + (file == null ? "null." : "set.") + " File is " + (file.IsCompleted ? "completed." : "not completed."));
//}
}
}
catch(Exception ex)
{
NetworkComms.Logger.Fatal("IncomingPartialFileDataError",ex);
}
}
/// <summary>
/// 接收到的文件信息
/// </summary>
/// <param name="packetHeader"></param>
/// <param name="connection"></param>
/// <param name="incomingObject"></param>
private void IncomingPartialFileDataInfo(PacketHeader packetHeader, NetworkCommsDotNet.Connections.Connection connection, SendInfo info)
{
try
{
byte[] data = null;
ReceivedFile file = null;
lock (syncRoot)
{
long sequenceNumber = info.PacketSequenceNumber;
if (incomingDataCache.ContainsKey(connection.ConnectionInfo) &&
incomingDataCache[connection.ConnectionInfo].ContainsKey(sequenceNumber))
{
//如果当前文件信息类对应的文件字节部分已经存在
data = incomingDataCache[connection.ConnectionInfo][sequenceNumber];
incomingDataCache[connection.ConnectionInfo].Remove(sequenceNumber);
if (!receivedFilesDict.ContainsKey(connection.ConnectionInfo))
receivedFilesDict.Add(connection.ConnectionInfo, new Dictionary<string, ReceivedFile>());
if (!receivedFilesDict[connection.ConnectionInfo].ContainsKey(info.Filename))
{
receivedFilesDict[connection.ConnectionInfo].Add(info.Filename, new ReceivedFile(info.Filename, connection.ConnectionInfo, info.TotalBytes));
}
file = receivedFilesDict[connection.ConnectionInfo][info.Filename];
}
else
{
if (!incomingDataInfoCache.ContainsKey(connection.ConnectionInfo))
incomingDataInfoCache.Add(connection.ConnectionInfo, new Dictionary<long, SendInfo>());
incomingDataInfoCache[connection.ConnectionInfo].Add(sequenceNumber, info);
}
if (data != null && file != null && !file.IsCompleted)
{
file.AddData(info.BytesStart, 0, data.Length, data);
if (file.IsCompleted)
{
if (null != ReceiveCompleted)
{
ReceiveCompleted(connection.ConnectionInfo, file.FileBuffer);
}
if (incomingDataCache.ContainsKey(connection.ConnectionInfo))
incomingDataCache[connection.ConnectionInfo].Clear();
if (incomingDataInfoCache.ContainsKey(connection.ConnectionInfo))
incomingDataInfoCache[connection.ConnectionInfo].Clear();
if (receivedFilesDict.ContainsKey(connection.ConnectionInfo))
receivedFilesDict[connection.ConnectionInfo].Clear();
}
file = null;
data = null;
}
else if (data == null ^ file == null)
throw new Exception("Either both are null or both are set. Data is " + (data == null ? "null." : "set.") + " File is " + (file == null ? "null." : "set.") + " File is " + (file.IsCompleted ? "completed." : "not completed."));
}
}
catch(Exception ex)
{
NetworkComms.Logger.Fatal("IncomingPartialFileDataInfoError",ex);
}
}
public void Initial()
{
IPEndPoint thePoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9999);
Connection.StartListening(ConnectionType.TCP,thePoint,false);
}
}
}
【发送端】:
分块发送,sendChunkSizeBytes可以自己改
using NetworkCommsDotNet;
using NetworkCommsDotNet.Connections.TCP;
using NetworkCommsDotNet.DPSBase;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WindowsFormsApplicationForPictureChunkSend
{
public class FileTransClient
{
private TCPConnection newTCPConnection = null;
private SendReceiveOptions customOptions = new SendReceiveOptions<ProtobufSerializer>();
public Boolean Initial()
{
try
{
ConnectionInfo connInfo = new ConnectionInfo("127.0.0.1", 9999);
newTCPConnection = TCPConnection.GetConnection(connInfo);
return true;
}
catch
{
return false;
}
}
public void SendFile(string fileName,Byte[] buffer)
{
try
{
MemoryStream stream = new MemoryStream(buffer);
NetworkCommsDotNet.Tools.StreamTools.ThreadSafeStream safeStream = new NetworkCommsDotNet.Tools.StreamTools.ThreadSafeStream(stream);
long sendChunkSizeBytes = 40960;
long totalBytesSent = 0;
do
{
long bytesToSend = (totalBytesSent + sendChunkSizeBytes) < stream.Length ? sendChunkSizeBytes : stream.Length - totalBytesSent;
NetworkCommsDotNet.Tools.StreamTools.StreamSendWrapper streamWrapper = new NetworkCommsDotNet.Tools.StreamTools.StreamSendWrapper(safeStream, totalBytesSent, bytesToSend);
long packetSequenceNumber;
newTCPConnection.SendObject("PartialFileData", streamWrapper, customOptions, out packetSequenceNumber);
newTCPConnection.SendObject("PartialFileDataInfo", new SendInfo(fileName, stream.Length, totalBytesSent, packetSequenceNumber), customOptions);
totalBytesSent += bytesToSend;
Thread.Sleep(0);
}
while (totalBytesSent < stream.Length);
Array.Clear(buffer,0,buffer.Length);
}
catch(CommunicationException)
{ }
}
}
}
【契约类】:
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WindowsFormsApplicationForPictureChunkSend
{
[ProtoContract]
public class SendInfo
{
[ProtoMember(1)]
public string Filename { get; private set; }
[ProtoMember(2)]
public long BytesStart { get; private set; }
[ProtoMember(3)]
public long TotalBytes { get; private set; }
[ProtoMember(4)]
public long PacketSequenceNumber { get; private set; }
private SendInfo() { }
public SendInfo(string filename, long totalBytes, long bytesStart, long packetSequenceNumber)
{
this.Filename = filename;
this.TotalBytes = totalBytes;
this.BytesStart = bytesStart;
this.PacketSequenceNumber = packetSequenceNumber;
}
}
}
【接收文件类】:
using NetworkCommsDotNet;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WindowsFormsApplicationForPictureChunkSend
{
public class ReceivedFile
{
private byte[] buffer;
private long receivedLength = 0;
public string FileName;
public ConnectionInfo connInfo;
public long TotalBytes;
public void AddData(long bytesStart, long dataStart, long dataLength, byte[] data)
{
Array.Copy(data,0,buffer,bytesStart,dataLength);
receivedLength += dataLength;
}
public ReceivedFile(string fileName,ConnectionInfo connInfo,long totalBytes)
{
this.FileName = fileName;
this.connInfo = connInfo;
this.TotalBytes = totalBytes;
this.receivedLength = 0;
buffer=new byte[totalBytes];
}
public Boolean IsCompleted
{
get
{
return receivedLength==TotalBytes;
}
}
public byte[] FileBuffer
{
get
{
return buffer;
}
}
}
}
写在最后,自己在c#网络通讯方面一直喜欢用networkcomms,成熟的通讯库,一直用很久没问题,然后如何稳定高效的发送大的byte流折腾了2天,由服务端发送到前端开始直接sendobject行不通不稳定,这么大的byte流只能分块发送,那如何分块发送就是上面代码主要要实现的功能,把自己2天的成果贴出来希望能帮助到有需要的你们;