c#:在与设备通信期间,数据缓冲区发生奇怪的移位
在我的应用程序中,我必须从设备接收和处理一些数据,通过COM端口连接。我只做了一部分。在该特定设备中,前两个字节是数据包的长度(减去2,因为它没有考虑这两个字节;所以它毕竟是数据包其余部分的长度)。然后,因为我知道设备发送数据的速度很慢,所以我读取循环中的其余数据包,直到所有数据都被读取。但就在这里,我遇到了一个奇怪的问题。让我们假设整个数据包(包括长度为的前两个字节)如下所示:['a','b','c','d','e']。当我读取前两个字节('a'和'b')时,我希望数据包的其余部分如下所示:['c','d','e']。但是,它看起来是这样的:['b','c','d','e']。为什么响应的第二个字节仍在读取缓冲区中?为什么只有第二个,没有前一个 下面的代码显示了如何处理通信过程:c#:在与设备通信期间,数据缓冲区发生奇怪的移位,c#,serial-port,buffer,C#,Serial Port,Buffer,在我的应用程序中,我必须从设备接收和处理一些数据,通过COM端口连接。我只做了一部分。在该特定设备中,前两个字节是数据包的长度(减去2,因为它没有考虑这两个字节;所以它毕竟是数据包其余部分的长度)。然后,因为我知道设备发送数据的速度很慢,所以我读取循环中的其余数据包,直到所有数据都被读取。但就在这里,我遇到了一个奇怪的问题。让我们假设整个数据包(包括长度为的前两个字节)如下所示:['a','b','c','d','e']。当我读取前两个字节('a'和'b')时,我希望数据包的其余部分如下所示:[
//The data array is some array with output data
//The size array is two-byte array to store frame-length bytes
//The results array is for device's response
//The part array is for part of the response that's currently in read buffer
port.Write(data, 0, data.Length);
//Receiving device's response (if there's any)
try
{
port.Read(size, 0, 2); //Read first two bytes (packet's length) of the response
//We'll store entire response in results array. We get its size from first two bytes of response
//(+2 for these very bytes since they're not counted in the device's data frame)
results = new byte[(size[0] | ((int)size[1] << 8)) + 2];
results[0] = size[0]; results[1] = size[1]; //We'll need packet size for checksum count
//Time to read rest of the response
for(offset = 2; offset < results.Length && port.BytesToRead > 0; offset += part.Length)
{
System.Threading.Thread.Sleep(5); //Device's quite slow, isn't it
try
{
part = new byte[port.BytesToRead];
port.Read(part, 0, part.Length); //Here's where old data is being read
}
catch(System.TimeoutException)
{
//Handle it somehow
}
Buffer.BlockCopy(part, 0, results, offset, part.Length);
}
if(offset < results.Length) //Something went wrong during receiving response
throw new Exception();
}
catch(Exception)
{
//Handle it somehow
}
//数据数组是带有输出数据的数组
//大小数组是两字节数组,用于存储帧长度字节
//结果数组用于设备的响应
//部分数组用于当前在读取缓冲区中的部分响应
端口写入(数据,0,数据长度);
//接收设备的响应(如果有)
尝试
{
port.Read(大小,0,2);//读取响应的前两个字节(数据包长度)
//我们将整个响应存储在结果数组中。我们从响应的前两个字节获取其大小
//(+2表示这些字节,因为它们不计入设备的数据帧)
results=newbyte[(size[0]|((int)size[1]很奇怪,但当我分别读取前两个字节时:
port.Read(size, 0, 1); //Read first two bytes (packet's length) of the response
port.Read(size, 1, 1); //Second time, lol
无论我从设备接收到什么类型的数据包,一切都正常。SerialPort的文档包含以下文本:
因为SerialPort类缓冲数据,而
BaseStream属性没有,这两个属性可能会在如何执行方面发生冲突
可以读取许多字节。BytesToRead属性可以
指示存在要读取的字节,但这些字节可能不存在
可访问BaseStream属性中包含的流,因为
它们已缓冲到SerialPort类
这能解释为什么BytesToRead
会给您带来令人困惑的值吗
就我个人而言,我总是使用DataReceived
事件,在我的事件处理程序中,我使用ReadExisting()
读取所有立即可用的数据并将其添加到我自己的缓冲区。我不试图在该级别对数据流施加任何意义,我只是对其进行缓冲;相反,我通常会编写一个小型状态机,每次从缓冲区中取出一个字符,并将数据解析为所需的任何格式
或者,您可以使用生成一个可观察的接收字符序列,然后在其上分层观察者
public static class SerialObservableExtensions
{
static readonly Logger log = LogManager.GetCurrentClassLogger();
/// <summary>
/// Captures the <see cref="System.IO.Ports.SerialPort.DataReceived" /> event of a serial port and returns an
/// observable sequence of the events.
/// </summary>
/// <param name="port">The serial port that will act as the event source.</param>
/// <returns><see cref="IObservable{Char}" /> - an observable sequence of events.</returns>
public static IObservable<EventPattern<SerialDataReceivedEventArgs>> ObservableDataReceivedEvents(
this ISerialPort port)
{
var portEvents = Observable.FromEventPattern<SerialDataReceivedEventHandler, SerialDataReceivedEventArgs>(
handler =>
{
log.Debug("Event: SerialDataReceived");
return handler.Invoke;
},
handler =>
{
// We must discard stale data when subscribing or it will pollute the first element of the sequence.
port.DiscardInBuffer();
port.DataReceived += handler;
log.Debug("Listening to DataReceived event");
},
handler =>
{
port.DataReceived -= handler;
log.Debug("Stopped listening to DataReceived event");
});
return portEvents;
}
/// <summary>
/// Gets an observable sequence of all the characters received by a serial port.
/// </summary>
/// <param name="port">The port that is to be the data source.</param>
/// <returns><see cref="IObservable{char}" /> - an observable sequence of characters.</returns>
public static IObservable<char> ReceivedCharacters(this ISerialPort port)
{
var observableEvents = port.ObservableDataReceivedEvents();
var observableCharacterSequence = from args in observableEvents
where args.EventArgs.EventType == SerialData.Chars
from character in port.ReadExisting()
select character;
return observableCharacterSequence;
}
}
公共静态类SerialObservableExtensions
{
静态只读记录器log=LogManager.GetCurrentClassLogger();
///
///捕获串行端口的事件并返回
///可观察到的事件顺序。
///
///将用作事件源的串行端口。
///-可观察到的事件序列。
公共静态IObservable可观测的接收设备(
这是ISerialPort(端口)
{
var portEvents=可观察的.FromEventPattern(
处理程序=>
{
调试(“事件:SerialDataReceived”);
返回处理程序.Invoke;
},
处理程序=>
{
//我们必须在订阅时丢弃过时的数据,否则会污染序列的第一个元素。
port.DiscardInBuffer();
port.DataReceived+=处理程序;
调试(“侦听DataReceived事件”);
},
处理程序=>
{
port.DataReceived-=处理程序;
调试(“停止侦听DataReceived事件”);
});
返回口;
}
///
///获取串行端口接收的所有字符的可观察序列。
///
///要作为数据源的端口。
///-可观察到的字符序列。
公共静态IObservable接收字符(此iSeries端口)
{
var observeeevents=port.observedartareceivedevents();
var observableCharacterSequence=来自observableEvents中的参数
其中args.EventArgs.EventType==SerialData.Chars
从port.ReadExisting()中的字符开始
选择字符;
返回可观察字符序列;
}
}
ISerialPort
接口只是我从SerialPort
类中提取的一个头接口,这使我在进行单元测试时更容易模拟它。您犯了一个传统错误,不能忽略Read()的返回值。它告诉您实际接收了多少字节。它将至少为1,不超过计数。无论接收缓冲区中存在多少字节,BytesToRead都会告诉您。只需继续调用Read(),直到您满意为止:
int cnt = 0;
while (cnt < 2) cnt += port.Read(size, cnt, 2 - cnt);
int cnt=0;
而(cnt<2)cnt+=port.Read(大小,cnt,2-cnt);
只需在代码的第二部分中使用相同的代码,这样就不会在没有Sleep()调用的情况下烧掉100%的内核。请记住,当您读取大小时,TimeoutException也很可能发生,实际上更可能发生。如果在cnt>0时抛出,则无法再重新同步,这是一个致命的异常。哦,当我第二次读取时(当我得到那个旧字节时,也就是说),port.BytesToRead还意味着还有一个字节