c#:在与设备通信期间,数据缓冲区发生奇怪的移位

c#:在与设备通信期间,数据缓冲区发生奇怪的移位,c#,serial-port,buffer,C#,Serial Port,Buffer,在我的应用程序中,我必须从设备接收和处理一些数据,通过COM端口连接。我只做了一部分。在该特定设备中,前两个字节是数据包的长度(减去2,因为它没有考虑这两个字节;所以它毕竟是数据包其余部分的长度)。然后,因为我知道设备发送数据的速度很慢,所以我读取循环中的其余数据包,直到所有数据都被读取。但就在这里,我遇到了一个奇怪的问题。让我们假设整个数据包(包括长度为的前两个字节)如下所示:['a','b','c','d','e']。当我读取前两个字节('a'和'b')时,我希望数据包的其余部分如下所示:[

在我的应用程序中,我必须从设备接收和处理一些数据,通过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
}
//数据数组是带有输出数据的数组
//大小数组是两字节数组,用于存储帧长度字节
//结果数组用于设备的响应
//部分数组用于当前在读取缓冲区中的部分响应
端口写入(数据,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还意味着还有一个字节