之前困恼了很久的问题,终于解决了,项目要求使用Upd传输,128000的采样率 ,每次一个通道2000个数据,一个数据用3个字节传输,共8通道,最后两字节验证码,最后计算转成double值,用曲线显示并保存文件。
则:
128000/2000=62(次)
1/62≈0.016(s)=16(ms)
200038+2=48002(字节)
相当于循环16ms传输48002个字节。
在之前的项目中,没有解决,让c++的同事写的插件,空闲时还是要自己研究下哈,但是资料真的好难找,最后自己摸索,实现了,下面把帮助类贴上,供大家一起讨论学习。
public class BigDataUDPServerHelper
{
private Socket _server;
private IPAddress Ip;//本机设备ip
public int Port;//本机设备端口号
private EndPoint _remote;//对方设备Ip及端口
public int PagLength = 1472;//从服务读取的数据量,因为BeginReceiveFrom每次都是提前设置下一次接受,要注意如何传递
int buffIndex = 0;//使用的缓存数组索引
public byte[][] recvBuff0;//缓存数组
readonly int MAX_BUFFLEN = 32;//最大缓存数组
private int _readLen = 6002; //从服务读取的数据量,因为BeginReceiveFrom每次都是提前设置下一次接受,要注意如何传递
public BigDataUDPServerHelper(string clientIpString,int clientPort,int size)
{
if (!IPAddress.TryParse(GetLocalIP(), out Ip))
{
MessageBox.Show("获取本机信息错误!");
return ;
}
if (!int.TryParse(GetLocalValidPort(), out Port))
{
MessageBox.Show("本机没有可用的端口!");
return ;
}
var clientIp= IPAddress.Parse(clientIpString);
_remote = new IPEndPoint(clientIp, clientPort);
PagLength = size;
Init();
}
/// <summary>
/// 缓存清零,初始化
/// </summary>
public void BuffInit()
{
recvBuff0 = new byte[MAX_BUFFLEN][];
for (int i = 0; i < MAX_BUFFLEN; i++)
{
recvBuff0[i] = new byte[50000];
}
}
/// <summary>
/// 初始化服务
/// </summary>
public void Init()
{
BuffInit();
_server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
_server.Bind(new IPEndPoint(Ip, Port));//绑定端口号和IP
//接受回调 ReceiveMsg是回调函数
_server.BeginReceiveFrom(recvBuff0[buffIndex], 0, PagLength, 0, ref _remote, ReceiveMsg, null);
}
#region 接收
/// <summary>
/// 设置接收长度
/// </summary>
/// <param name="size"></param>
public void SetBufferSize(int size)
{
PagLength = size;
}
public int realBytesRecv = 0;//实际已接收字节数
public event Action<byte[]> ReceiveEvent;//将数据转到界面
public void ReceiveMsg(IAsyncResult ar)
{
//能进入这个事件,表明缓存中已存好本次的数据,不是通过BeginReceiveFrom来读取,BeginReceiveFrom是准备下次的
int nRead = _server.EndReceive(ar);//获取传递过来的字节数。
if (nRead <= 0|| realBytesRecv >= PagLength)
{
realBytesRecv = 0;
var Inforstr = "Socket rec33recv Error code:" + "\r\n";//出错
return;
}
realBytesRecv += nRead;
if (realBytesRecv >= PagLength)//接受完整,大数据字节包过来受网络限制,最大传递1472个字节,所以需要拼包
{
realBytesRecv = 0;
buffIndex++;
if (buffIndex > (MAX_BUFFLEN - 1))
buffIndex = 0;
Thread t1 = new Thread(ThreadReceiveEvent);
t1.Start();
}
//必须加,为下次接受回调做准备
_server.BeginReceiveFrom(recvBuff0[buffIndex], realBytesRecv, _readLen, 0, ref _remote, ReceiveMsg, null);
}
/// <summary>
/// 获取网络字节包
/// </summary>
public void ThreadReceiveEvent()
{
var buffer1 = new byte[PagLength];
var indx = buffIndex - 1;
if (indx == -1)
indx = MAX_BUFFLEN-1;
Array.Copy(recvBuff0[indx], buffer1, PagLength);//复制接收数据,减去可能多余的数组
ReceiveEvent.Invoke(buffer1);
}
#endregion
#region 发送
public bool SendMsg(byte[] value)
{
var res = _server.SendTo(value, _remote);//会触发BeginReceiveFrom事件,好像是因为EndReceive
if (res < 0)
{
return false;
}
return true;
}
#endregion
/// <summary>
/// // 获得本机局域网IP地址
/// </summary>
/// <returns></returns>
private string GetLocalIP()
{
IPAddress[] AddressList = Dns.GetHostByName(Dns.GetHostName()).AddressList;
if (AddressList.Length < 1)
{
return "";
}
return AddressList[0].ToString();
}
/// <summary>
/// 获取本地有效的端口号
/// </summary>
/// <returns></returns>
private string GetLocalValidPort()
{
IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties();
//返回本地计算机上的所有Tcp监听程序
var ipsTcp = properties.GetActiveTcpListeners();
//返回本地计算机上的所有UDP监听程序
var ipsUdp = properties.GetActiveUdpListeners();
for (int i = 1024; i < 5000; i++)
{
foreach (var ip in ipsTcp)
{
if (ip.Port == i) continue;
foreach (var ip2 in ipsUdp)
{
if (ip2.Port != i)
{
return i.ToString();
}
}
}
if (i == 4999)
{
return "";
}
}
return "";
}
}
使用方法:
连接:
BigDataUDPServerHelper uDPServerHelper = new BigDataUDPServerHelper("ip地址",int.Parse("端口号"), int.Parse("接收字节长度"));
uDPServerHelper.ReceiveEvent += Ss_ReceiveEvent;//接收字节事件
richTextBox1.AppendText("连接成功\n");
修改接收字节长度:
uDPServerHelper.SetBufferSize(int.Parse(textBox1.Text));
发送字节:
uDPServerHelper.SetBufferSize(6002);
byte[] code = new byte[] {
0x55, 0xaa,0x01,0x80,0x2b,0xf9,0x2f,0xd3,0xa1,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf3 };
var result = uDPServerHelper.SendMsg(code);
var str = result ? "发送成功\n" : "发送失败\n";
richTextBox1.AppendText(str);
接收字节:
在连接的时候,我们绑定了接收字节事件,此事件会在接收完整字节后触发,或者可找到帮助类中对应的位置修改传递方式。
考虑到高速大数据量,我在该接收函数中又添加了三维数组用于缓存,截图如下,因个人解析不同,故只用于参考:
ps:注意此处不能占用过多时间,会导致接收不完整。
写在最后:
和之前的upd通信相比,本次采用了异步通信的方式,但是这样的方式并不能说就实现了大数据量接收,因为数据到了,保存、解析、计算、使用都会占用时间,可能影响下次的接收保存。
所以我采用了二维数组缓存(二维数组不是一个整体的内存空间,是每个一维数组一个地址,所以不会造成读写使用一个对象 ),这样有32个一维数组可使用,将数据按条件存满一个第一个一维数组后,我使用下个一维数组进行保存,同时在线程中将第一个一维数组去进行解析、计算、使用,互不干涉。
同时在界面方面,也采用了多维数组进行缓存。
只有在每个环节都减少对象占用的等待时间,数据接收才能完整呈现。
最后,本有尝试添加一个接口,用于封装解析,但是发觉数据采集一段时间后出错,所以在不了解性能的情况下,尽量少一点封装较好。
转载:https://blog.csdn.net/Yyuanyuxin/article/details/116193252