转载:https://www.cnblogs.com/showlie/articles/2367154.html
一、现象
不管如何设置ReceivedBytesThreshold的值,DataReceived接收到的数据都是比较混乱,不是一个完整的应答数据。
二、原因
1、上位机下发的命令比较密集,以200ms周期发送实时状态轮询命令。
2、在状态实时轮询命令中间有操作命令插入。
2、不同的命令,接收的应答格式也不同。
三、分析
不同的命令有不同的应答数据,但是不同的应答数据中都具有唯一的结束符,可以根据结束符来作为多个应答数据的分割标志。因此可以把应答数据进行缓存,然后另起一个线程对缓存的应答数据进行分析处理。
因此系统具有:
1、命令队列用来插入操作命令,空闲时处理状态实时轮询命令。
2、命令发送线程,以200ms周期性的发送队列中的命令。
3、应答集合,用来缓存DataReceived接收数据。
4、应答处理线程,对应答集合中的数据进行集中处理。
四、代码片段
1.1、定义
/// <summary>
/// 请求命令队列
/// </summary>
private Queue<Request> requests;
/// <summary>
/// 应答数据集合
/// </summary>
private List<byte> responses;
/// <summary>
/// 发送线程同步信号
/// </summary>
private ManualResetEvent sendWaiter;
/// <summary>
/// 应答数据处理线程同步信号
/// </summary>
private ManualResetEvent receiveWaiter;
this.requests = new Queue<Request>();
this.responses = new List<byte>();
this.sendWaiter = new ManualResetEvent(false);
this.receiveWaiter = new ManualResetEvent(false);
//命令发送线程
ThreadPool.QueueUserWorkItem(new WaitCallback(Send));
//应答处理线程
ThreadPool.QueueUserWorkItem(new WaitCallback(Received));
2.开始、停止线程
/// <summary>
/// 启动服务
/// </summary>
public void Start()
{
try
{
if (!this.serialPort1.IsOpen)
{
this.serialPort1.Open();
}
this.requests.Clear();
//插入初始化命令
this.Push(new Request());
this.sendWaiter.Set();
this.receiveWaiter.Set();
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 停止服务
/// </summary>
public void Stop()
{
this.sendWaiter.Reset();
this.receiveWaiter.Reset();
if (this.serialPort1.IsOpen)
{
this.serialPort1.Close();
}
this.requests.Clear();
this.responses.Clear();
}
3.发送线程
/// <summary>
/// 插入操作命令
/// </summary>
/// <param name="request"></param>
public void Push(Request request)
{
Monitor.Enter(this.requests);
this.requests.Enqueue(request);
Monitor.Exit(this.requests);
}
/// <summary>
/// 发送
/// </summary>
/// <param name="obj"></param>
private void Send(object obj)
{
while (true)
{
try
{
this.sendWaiter.WaitOne();
Monitor.Enter(this.requests);
Request request = null;
if (this.requests.Count > 0)
{
request = this.requests.Dequeue();
}
else if (this.Polling)
{
this.send++;
request = new Request(this.config.Zone);
}
if (request != null)
{
byte[] buffer = request.ToBytes();
this.serialPort1.DiscardInBuffer();
this.serialPort1.Write(buffer, 0, buffer.Length);
}
Monitor.Exit(this.requests);
Thread.Sleep(200);
}
catch (Exception ex)
{
throw ex;
}
}
}
4.接收和处理
/// <summary>
/// 串口接收
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SerialPortDataReceived(object sender, EventArgs e)
{
try
{
if (this.serialPort1.BytesToRead > 0)
{
byte[] buffer = new byte[this.serialPort1.BytesToRead];
int readCount = this.serialPort1.Read(buffer, 0, buffer.Length);
Monitor.Enter(this.responses);
this.responses.AddRange(buffer);
Monitor.Exit(this.responses);
}
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 缓存处理
/// </summary>
/// <param name="obj"></param>
private void Received(object obj)
{
while (true)
{
this.receiveWaiter.WaitOne();
Monitor.Enter(this.responses);
if (this.responses.Count > 0)
{
int endIndex = this.responses.IndexOf(Request.SYMBOL_END);
if (endIndex >= 0)
{
byte[] buffer = this.responses.GetRange(0, endIndex + 1).ToArray();
this.responses.RemoveRange(0, endIndex + 1);
if (buffer.Length > 3)
{
int cmd = buffer[1];
switch (cmd)
{
case Request.CMD_QUERY_STATE_SYSTEM:
case Request.CMD_QUERY_UNKNOWN_ZONE:
{
this.config.Update(buffer, 0, buffer.Length);
this.Polling = true;
if (this.ShelvesInitialized != null)
{
this.ShelvesInitialized(this, new ShelvesInitializedEventArgs(this.config));
}
break;
}
case Request.CMD_QUERY_STATE_LINE:
{
this.received++;
this.realtime = Realtime.Parse(buffer, 0, buffer.Length);
if (this.ShelvesDataReceived != null && this.realtime != null)
{
this.ShelvesDataReceived(this, new ShelvesDataReceivedEventArgs(this.realtime));
}
break;
}
}
}
}
}
Monitor.Exit(this.responses);
Thread.Sleep(200);
}
5.以事件的形式在主界面实时显示处理后的应答数据
总结:这种方法挺好,我在项目中采用他的 接收数据辅助线程 private void SerialPortDataReceived(object sender, EventArgs e) 和处理数据线程分开。
接收线程只管接收,处理线程进行数据处理。两线程之间通过list 列表作为队列缓冲区,接收线程往list集合尾部添加数据,数据处理线程从list集合头部取出数据。采用锁来实现两线程的访问list同步。
好处:
1.这样处理速度更快。
2.接收数据辅助线程来一个字节触发一次,现在这种分开后,处理线程就不会频繁的处理断截的数据(因为list缓冲区缓存了,满足一帧数据才处理)