【问题标题】:c#: strange shift in data buffer during communication with devicec#:与设备通信期间数据缓冲区的奇怪变化
【发布时间】:2015-06-13 18:48:30
【问题描述】:

在我的应用程序中,我必须从通过 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.BytesToRead 也意味着还有一个字节。我的意思是,如果数据包总共有 x 个字节并且我读取了前两个字节,则 port.BytesToRead == x - 1 而不是 x - 2。
  • 如果这么慢,那么在读取前两个字节后执行“port.DiscardInBuffer()”。
  • 它实际上让一切变得更糟。
  • 哎哟!也许尝试这样的事情:codesamplez.com/programming/serial-port-communication-c-sharp
  • 不能忽略 Read() 的返回值。它是 1,而不是 2。

标签: c# serial-port buffer


【解决方案1】:

你犯了一个传统的错误,你不能忽略 Read() 的返回值。它告诉您实际收到了多少字节。它将至少为 1,不超过 count。不管接收缓冲区中存在多少,BytesToRead 告诉你。只需继续调用 Read() 直到您满意为止:

int cnt = 0;
while (cnt < 2) cnt += port.Read(size, cnt, 2 - cnt);

只需在代码的第二部分使用相同的代码,这样您就不会在没有 Sleep() 调用的情况下烧毁 100% 的内核。请记住,当您阅读大小时,TimeoutException 的可能性也一样,实际上更有可能。如果在cnt > 0时抛出它是一个致命的异常,你不能再重新同步了。

【讨论】:

  • 你是对的。非常感谢,它比我的解决方案好得多。
【解决方案2】:

嗯,很奇怪,但是当我分别读取前两个字节时:

port.Read(size, 0, 1); //Read first two bytes (packet's length) of the response
port.Read(size, 1, 1); //Second time, lol

无论我从设备收到什么样的数据包,一切正常。

【讨论】:

    【解决方案3】:

    SerialPort 的文档包含以下文本:

    因为 SerialPort 类缓冲数据,而流包含在 BaseStream 属性没有,两者可能会发生冲突 许多字节可供读取。 BytesToRead 属性可以 表示有字节要读取,但这些字节可能不是 可访问 BaseStream 属性中包含的流,因为 它们已被缓冲到 SerialPort 类。

    这能否解释为什么 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 类中提取的一个头接口,这让我在单元测试时更容易模拟它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-10-18
      • 1970-01-01
      • 2016-03-25
      • 2021-07-17
      • 1970-01-01
      • 1970-01-01
      • 2014-07-21
      相关资源
      最近更新 更多