【问题标题】:C# serial port sometimes missing dataC#串口有时会丢失数据
【发布时间】:2018-01-06 12:24:30
【问题描述】:

您好,我在 C# 中创建了一个程序来接收 ASCII 字符串,该字符串是 XML 标记。

我从我那里接收数据的计算机无法控制并且不接受响应,它大约每 10 分钟在 COM 端口上发送一次数据

我收集并存储这些数据的控制台应用程序并不总是有效,我会说大约 50% 的时间数据丢失,例如数据包或字节丢失,并且 XML 字符串不会读入 XmlDocument

我已经尝试了大约一个星期来使它更稳定,但这是我第一次使用 C#,并且希望有一些帮助来改进它。

代码

  class SerialPortProgram : IDisposable
  {
    // Create the serial port with basic settings
    private SerialPort port = new SerialPort("COM1",
       115200, Parity.None, 8, StopBits.One);

    string sBuffer = null;
    string filePath1 = @"C:\Data\data1.xml";
    string filePath2 = @"C:\Data\data2.xml";

    [STAThread]
    static void Main(string[] args)
    {
        // Instatiate this class
        new SerialPortProgram();

    }

    private SerialPortProgram() 
    {
        Console.WriteLine("Started Data Monitoring:");

        //Attach a method to be called when there
        //is data waiting in the port's buffer
        port.ReadBufferSize = 20971520;
        port.ReceivedBytesThreshold = 1;
        port.DataReceived += new SerialDataReceivedEventHandler(Port_DataReceived);

        //Begin communications
        port.Open();

        //Enter an application loop to keep this thread alive
        Application.Run();

    }

    private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        //Show all the incoming data in the port's buffer
        SerialPort sp = (SerialPort)sender;
        sBuffer += sp.ReadExisting();

        if (sBuffer.Length > 26000) // check the file size
        {
            if (sBuffer.Substring(sBuffer.Length - 6) == "</xml>") // check for end of file
            {
                Console.WriteLine("Found: Processing...");
                //Thread.Sleep(1000);
                ProcessXML();
                sBuffer = null;
                Console.WriteLine("Done!");
                DateTime now = DateTime.Now;
                Console.WriteLine(now);
                Console.WriteLine("Monitoring...");
            }
            else
            {
                Console.WriteLine("Still Receiving Data: " + sBuffer.Length);
            }
        }
        else
        {
            Console.WriteLine("Receiving Data: " + sBuffer.Length);
        }
    }
 private void ProcessXML()
    {
        XmlDocument xmlDoc = new XmlDocument();
        try
        {
            xmlDoc.LoadXml("<wrapper>" + sBuffer + "</wrapper>");
            int index = 0;
            XmlNodeList xnl = xmlDoc.SelectNodes("wrapper/xml");
            foreach (XmlNode node in xnl)
            {
                // Console.WriteLine(index.ToString());
                if (index == 0)// xml file 1
                {
                    using (XmlReader r = new XmlNodeReader(node))
                    {
                        DataSet ds = new DataSet();
                        ds.ReadXml(r);
                        ds.WriteXml(filePath1);
                        Console.WriteLine("NEW Data1");
                        ds.Dispose();
                        var db = new Database();
                        db.SaveMetersToDatabase(ds);
                    }
                }
                else if (index == 1)// xml file 2
                {
                    using (XmlReader r1 = new XmlNodeReader(node))
                    {
                        DataSet dst = new DataSet();
                        dst.ReadXml(r1);
                        dst.WriteXml(filePath2);
                        Console.WriteLine("NEW Data2");
                        dst.Dispose();
                    }
                }

                index++;
            }
        }
        catch
        {
            Console.WriteLine("Error: in data");
            try
            {
                string now = DateTime.Now.ToString("yyyyMMddHHmmss");
                System.IO.File.WriteAllText(@"C:\Data\log" + now + ".xml", "<wrapper>" + sBuffer + "</wrapper>");
            }
            catch
            {
                Console.WriteLine("Failed to write to log");
            }
        }
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing && port != null)
        {
            port.Dispose();
            port = null;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

  }
}

更新代码:

  private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        //Show all the incoming data in the port's buffer
        SerialPort sp = (SerialPort)sender;
        sBuffer += sp.ReadExisting();

        new Thread(() =>
        {
            Thread.CurrentThread.IsBackground = true;
            if (sBuffer.Length > 25000) // check the file size
            {
                if (sBuffer.Substring(sBuffer.Length - 6) == "</xml>") // check for end of file
                {
                    Console.WriteLine("Found: Processing...");
                    Task.Run(() =>
                    {
                        ProcessXML();
                        sBuffer = null;
                        Console.WriteLine("Done!");
                        DateTime now = DateTime.Now;
                        Console.WriteLine(now);
                        Console.WriteLine("Monitoring...");
                    });
                }
                else
                {
                    Console.WriteLine("Still Receiving Data: " + sBuffer.Length);
                }
            }
            else
            {
                Console.WriteLine("Receiving Data: " + sBuffer.Length);
            }
        }).Start();
    }

更新

仍然有这个问题,可能是发送计算机有时不发送所有数据或存在数据包丢失我已经尝试了所有方法我已经使用 Serial Port BaseStream BeginRead 在下面尝试了这个新代码

  private SerialPortProgram() 
    {
        Console.WriteLine("Started Data Monitoring:");

        //Attach a method to be called when there
        try
        {
            Port.BaudRate = 115200;
            Port.DataBits = 8;
            Port.Parity = Parity.None;
            Port.StopBits = StopBits.One;
            Port.Handshake = Handshake.None;
            Port.DtrEnable = true;
            Port.NewLine = Environment.NewLine;
            Port.ReceivedBytesThreshold = 2048;
            Port.Open();

            byte[] buffer = new byte[35000];
            Action StartRead = null;
            StartRead = () => {
                Port.BaseStream.BeginRead(buffer, 0, buffer.Length, async (IAsyncResult ar) =>
                {
                    try
                    {
                        int actualLength = Port.BaseStream.EndRead(ar);
                        byte[] received = new byte[actualLength];
                        Buffer.BlockCopy(buffer, 0, received, 0, actualLength);
                        await Task.Run(() =>
                        {
                            sBuffer += Encoding.ASCII.GetString(received);
                            CheckBuffer();
                        });
                    }
                    catch (Exception exc)
                    {
                        Console.WriteLine(exc);
                    }
                    StartRead();
                }, null);
            };
            StartRead();

        }
        catch (Exception ex)
        {
            Console.WriteLine("Error accessing port." + ex);
            Port.Dispose();
            Application.Exit();
        }

        //Enter an application loop to keep this thread alive
        Application.Run();
    }

    private void CheckBuffer()
    {
        if (sBuffer != null && sBuffer.Length > 26000) // check the file size
        {
            if (sBuffer.Substring(sBuffer.Length - 6) == "</xml>") // check for end of file
            {
                new Thread(async () =>
               {
                   Console.WriteLine("Found: Processing...");

                   await Task.Run(() => ProcessXML());

                   sBuffer = null;
                   Console.WriteLine("Done!");
                   DateTime now = DateTime.Now;
                   Console.WriteLine(now);
                   Console.WriteLine("Monitoring...");
               }).Start();
            }
            else
            {
                Console.WriteLine("Still Receiving Data: " + sBuffer.Length);
            }
        }
        else if (sBuffer != null && sBuffer.Length > 0)
        {
            Console.WriteLine("Receiving Data: " + sBuffer.Length);
        }

    }

【问题讨论】:

  • 睡觉是为了什么?那只是要求丢失数据
  • 是的,我认为它不会影响它,因为它正在保存文件并重新打开它但不再需要已删除
  • 通过通信,您希望最大限度地减少回调中的时间,否则您将丢失数据。我建议您使用 循环缓冲区 或类似技术,并将您对所述缓冲区的处理移动到另一个 thread
  • 请看我的回答stackoverflow.com/questions/33310396/…,并在使用答案中提到的控件时判断问题是否仍然存在。
  • 哦,您应该使用更小的缓冲区,接收所有数据,将其放入队列或某处,然后分析数据(当数据在您身边时)。考虑一个线程仅用于接收数据,这样您就不会遇到任何滞后(记住 - 单一责任)和另一个用于解析它的线程。我一直把它分成两类——接收器和处理器。如果您愿意,我可以向您展示示例实现

标签: c#


【解决方案1】:

这是我的实现。 你应该明白大体的想法

首先 - 从连接中获取数据

internal class SerialListener : Listener
{
    private SerialPort sp;
    private ConnectionInfo _connection;
    private Timer _listenerTimer;
    private bool should_exit = false;
    private bool busy = false;
    ConcurrentQueue<byte> fifo_peekonly = null;
    BlockingCollection<byte> fifo_queue = null;

    public SerialListener(ConnectionInfo connection)
        : base(connection)
    {
        _connection = connection;
        InitSerialConnection();
    }

    private void InitSerialConnection()
    {
        sp = new SerialPort(_connection.ifname_ip);
        sp.BaudRate = _connection.baudrate_port;
        sp.Parity = _connection.parity;
        sp.DataBits = _connection.charactersize;
        sp.StopBits = _connection.stopbits;
        sp.Handshake = _connection.flowcontrol;
        sp.DtrEnable = true;
        sp.ReadTimeout = 100;
        sp.Open();
        fifo_peekonly = new ConcurrentQueue<byte>();
        fifo_queue = new BlockingCollection<byte>(fifo_peekonly);
        sp.DataReceived += (sender, e) => 
        { 
            byte[] buffer = new byte[sp.BytesToRead];
            if (!sp.IsOpen)
            {
                throw new System.InvalidOperationException("Serial port is closed.");
            }
            sp.Read(buffer,0,sp.BytesToRead);
            foreach (var b in buffer)
                fifo_queue.Add(b);
        };
    }

    public override byte GetByteFromDevice()
    {
        byte b;
        b = fifo_queue.Take();
        return b;
    }

    public override byte PeekByteFromDevice ()
    {
        byte b;
        bool peeked = false;
        do {
            peeked = fifo_peekonly.TryPeek(out b);
            if (!peeked)
                Thread.Sleep(100);
        } while (!peeked);
        return b;
    }

    public override void Close()
    {
        base.Close();
        sp.DiscardInBuffer();
        sp.DiscardOutBuffer();
        Thread.Sleep(3000);
        sp.Close();
    }
}

然后实现另一个将调用GetByteFromDevice 方法的类。不幸的是,这里发布的代码太具体了,因为它只会让您感到困惑。

所以 - 分离逻辑,实时推送所有字节,并在单独的线程(一些循环、计时器等)中获取先前读取的字节,然后分析它们。

在我看来,您用于 xml 检测的方法也不是最佳的。我已经实现了检测流中的开始和结束序列。如果您想查看代码,请告诉我

【讨论】:

  • 感谢您的帮助,但我在当前程序中使用其中一些代码没有成功
  • 我已经接受了这个答案,经过一些更改,我现在已经让这段代码工作了,它已经解决了我的问题。如果你有 xml 检测的代码,会很高兴看到
  • 我没有这样的代码,但我可以告诉你我会怎么做。您拥有队列中的所有字符,您可以查看队列以监视 xml 开始,然后跟踪开始与结束标记。寻找类似的问题stackoverflow.com/questions/891223/better-way-to-detect-xml
  • 感谢您的帮助在过去的一个月里,我尝试了很多不同的串口读取代码,这是我每次都能快速且完美地工作的唯一代码
  • IMO 在你的解决方案中唯一可能不是最优的就是你有很大的缓冲区而不是丢弃你不需要的东西
猜你喜欢
  • 2011-06-01
  • 1970-01-01
  • 2014-10-04
  • 1970-01-01
  • 1970-01-01
  • 2018-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多