c#:与设备通信期间数据缓冲区发生奇怪的偏移
c#: strange shift in data buffer during communication with device
在我的应用程序中,我必须从通过 COM 端口连接的设备接收和处理一些数据。我部分地做。在该特定设备中,前两个字节是数据包的长度(减去 2,因为它没有考虑这两个字节;所以它毕竟是数据包其余部分的长度)。然后,由于我知道该设备向我发送数据的速度较慢,因此我在循环中读取剩余的数据包,直到读取完所有数据。但是就在这里我遇到了一个奇怪的问题。假设整个数据包(包括前两个长度字节)如下所示:['a'、'b'、'c'、'd'、'e']。当我读取前两个字节('a' 和 'b')时,我希望数据包的其余部分看起来像这样:['c'、'd'、'e'].但是,它看起来像这样:['b'、'c'、'd'、'e']。为什么响应的第二个字节仍在读取缓冲区中?为什么只有第二个,没有上一个?
下面的代码显示了我如何处理通信过程:
//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
}
嗯,很奇怪,但是当我分别读取前两个字节时:
port.Read(size, 0, 1); //Read first two bytes (packet's length) of the response
port.Read(size, 1, 1); //Second time, lol
无论我从设备收到什么样的数据包,一切正常。
SerialPort 的文档包含以下文本:
Because the SerialPort class buffers data, and the stream contained in
the BaseStream property does not, the two might conflict about how
many bytes are available to read. The BytesToRead property can
indicate that there are bytes to read, but these bytes might not be
accessible to the stream contained in the BaseStream property because
they have been buffered to the SerialPort class.
这可以解释为什么 BytesToRead
会给您带来混淆的值吗?
就个人而言,我总是使用 DataReceived
事件,在我的事件处理程序中,我使用 ReadExisting()
读取所有立即可用的数据并将其添加到我自己的缓冲区中。我不试图在那个级别对数据流强加任何意义,我只是缓冲它;相反,我通常会编写一个小状态机,一次从缓冲区中取出一个字符,并将数据解析为所需的任何格式。
或者,您可以使用 ReactiveExtensions 生成一个可观察的接收字符序列,然后在其上放置观察者。我用这样的几个扩展方法来做到这一点:
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;
}
}
ISerialPort
接口只是我从SerialPort
class中提取出来的头接口,方便我在单元测试的时候mock它。
您犯了一个传统错误,您不能忽略 Read() 的 return 值。它会告诉您 实际 收到了多少字节。至少为 1,不超过 count。无论接收缓冲区中有多少,BytesToRead 都会告诉您。只需继续调用 Read() 直到您满意:
int cnt = 0;
while (cnt < 2) cnt += port.Read(size, cnt, 2 - cnt);
只需在代码的第二部分使用相同的代码,这样您就不会在没有调用 Sleep() 的情况下消耗 100% 的核心。请记住,当您读取大小时,TimeoutException 很可能发生,实际上更有可能发生。 cnt > 0时抛出是致命异常,不能再同步了
在我的应用程序中,我必须从通过 COM 端口连接的设备接收和处理一些数据。我部分地做。在该特定设备中,前两个字节是数据包的长度(减去 2,因为它没有考虑这两个字节;所以它毕竟是数据包其余部分的长度)。然后,由于我知道该设备向我发送数据的速度较慢,因此我在循环中读取剩余的数据包,直到读取完所有数据。但是就在这里我遇到了一个奇怪的问题。假设整个数据包(包括前两个长度字节)如下所示:['a'、'b'、'c'、'd'、'e']。当我读取前两个字节('a' 和 'b')时,我希望数据包的其余部分看起来像这样:['c'、'd'、'e'].但是,它看起来像这样:['b'、'c'、'd'、'e']。为什么响应的第二个字节仍在读取缓冲区中?为什么只有第二个,没有上一个?
下面的代码显示了我如何处理通信过程:
//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
}
嗯,很奇怪,但是当我分别读取前两个字节时:
port.Read(size, 0, 1); //Read first two bytes (packet's length) of the response
port.Read(size, 1, 1); //Second time, lol
无论我从设备收到什么样的数据包,一切正常。
SerialPort 的文档包含以下文本:
Because the SerialPort class buffers data, and the stream contained in the BaseStream property does not, the two might conflict about how many bytes are available to read. The BytesToRead property can indicate that there are bytes to read, but these bytes might not be accessible to the stream contained in the BaseStream property because they have been buffered to the SerialPort class.
这可以解释为什么 BytesToRead
会给您带来混淆的值吗?
就个人而言,我总是使用 DataReceived
事件,在我的事件处理程序中,我使用 ReadExisting()
读取所有立即可用的数据并将其添加到我自己的缓冲区中。我不试图在那个级别对数据流强加任何意义,我只是缓冲它;相反,我通常会编写一个小状态机,一次从缓冲区中取出一个字符,并将数据解析为所需的任何格式。
或者,您可以使用 ReactiveExtensions 生成一个可观察的接收字符序列,然后在其上放置观察者。我用这样的几个扩展方法来做到这一点:
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;
}
}
ISerialPort
接口只是我从SerialPort
class中提取出来的头接口,方便我在单元测试的时候mock它。
您犯了一个传统错误,您不能忽略 Read() 的 return 值。它会告诉您 实际 收到了多少字节。至少为 1,不超过 count。无论接收缓冲区中有多少,BytesToRead 都会告诉您。只需继续调用 Read() 直到您满意:
int cnt = 0;
while (cnt < 2) cnt += port.Read(size, cnt, 2 - cnt);
只需在代码的第二部分使用相同的代码,这样您就不会在没有调用 Sleep() 的情况下消耗 100% 的核心。请记住,当您读取大小时,TimeoutException 很可能发生,实际上更有可能发生。 cnt > 0时抛出是致命异常,不能再同步了