none
为什么设置了串口的ReceivedBytesThreshold却有小于ReceivedBytesThreshold的事件发生? RRS feed

  • 问题

  • 程序是vs2005开发的,我自己包装了SerialPort,设置了ReceivedBytesThreshold = 18,并进行了接收到数据量的检测,但在日志中却发现了 长度 = 8 或 长度 = 9的记录,与PLC的通讯是造成这个现象的原因。但我不明白的是PLC端发送了什么样的数据会导致串口跳过了我的检测并执行了代码。

     

    打开串口的代码:

      /// <summary>
      /// 打开电子称串口
      /// </summary>
      private void OpenScaleSerialPort()
      {
       try
       {
        if (scaleSerialPort == null)
        {
         scaleSerialPort = new SerialPortWrapper();
         // 要求18个字节才触发OnDataReceived
         scaleSerialPort.ReceivedBytesThreshold = 18;
        }
    
        // 为了防止重新打开串口时已有数据处理事件,先注销已有事件再添加
        scaleSerialPort.OnDataReceived -= serialDataReceived;
        scaleSerialPort.OnDataReceived += serialDataReceived;
    
        ConfigureInfo config = new ConfigureInfo();
    
        string portName;
        int baudRate, dataBits;
        config.GetSerialPortSettings(out portName, out baudRate,
         out dataBits, out portName, out baudRate, out dataBits);
    
        scaleSerialPort.PortName = portName;
        scaleSerialPort.BaudRate = baudRate;
        scaleSerialPort.DataBits = dataBits;
    
        scaleSerialPort.Open();
       }
       catch (Exception e)
       {
        MsgBox.Warn(e.Message);
       }
      }
    
    处理接收事件的代码:

      /// <summary>
      /// 串口接收到数据
      /// </summary>
      /// <param name="sneder">消息发送者</param>
      /// <param name="e">串口接收数据事件</param>
      private void OnSerialDataReceived(object sender, SerialDataReceivedEventArgs e)
      {
       if (scaleSerialPort.BytesToRead >= scaleSerialPort.ReceivedBytesThreshold)
       {
        byte[] buffer = new byte[scaleSerialPort.BytesToRead];
        scaleSerialPort.Read(buffer, 0, buffer.Length);
    
        logWriter.SaveLog(scaleSerialPort.PortName + "接收到了" + buffer.Length 
         + "个字节");
    
        // 这里省略了处理代码
       }
      }
    
    logWritter类是简单的文件日志记录类,只是向文件中保存字符串信息而已。省略的处理代码中都是使用buffer来进行数据处理,不会再涉及到scaleSerialPort。打开串口的操作在Form的Load中调用,其他地方不会再次调用。

     

    下面是我的串口包装类:

    /* 创建人:刘文飞
     * 创建时间:2010-04-27
     * 创建原因:为了在使用中不受到SerialPort的关闭困扰(有数据在传输并处理时关闭串口导致严重到死机的现象)
     * 实现:增加正在接收数据标志和试图关闭串口标志,
     *  增加对串口DataReceived事件的包装。
     * 1.在执行DataReceived委托前先判断是否要关闭串口,如果是则不再进行下面的一系列处理
     * 2.把正在接收数据标志设置为true
     * 3.执行委托
     * 4.把正在接收数据标志设置为false
     * 
     * 原理:在DataReceived事件中如果产生了对窗体或其上控件的调用的话,必须使用Invoke来操作。
     *  这实际上隐式的产生了线程,线程间的操作并发性导致在平时关闭正在处理DataReceived
     *  的串口和窗体间的线程产生了死锁。解决办法就是增加状态控制,主动释放线程资源。
     */
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    using System.IO.Ports;
    using System.Windows.Forms;
    
    namespace Nodes.Kappa.Utility
    {
     /// <summary>
     /// 串口简单包装类
     /// </summary>
     public class SerialPortWrapper : IDisposable
     {
      /// <summary>
      /// 串口对象
      /// </summary>
      private SerialPort serialPort;
    
      /// <summary>
      /// 是否已释放
      /// </summary>
      private bool disposed;
    
      /// <summary>
      /// 是否正在接收数据
      /// </summary>
      private bool isReceivingData;
    
      /// <summary>
      /// 是否正在试图关闭串口
      /// </summary>
      private bool isTryToClose;
    
      /// <summary>
      /// 接收到数据事件
      /// </summary>
      public event SerialDataReceivedEventHandler OnDataReceived;
    
    
      /// <summary>
      /// 获取或设置串口的端口
      /// </summary>
      public string PortName
      {
       get { return serialPort.PortName; }
       set
       {
        if (!serialPort.IsOpen)
        {
         serialPort.PortName = value;
        }
       }
      }
    
      /// <summary>
      /// 获取或设置串口的波特率
      /// </summary>
      public int BaudRate
      {
       get { return serialPort.BaudRate; }
       set { serialPort.BaudRate = value; }
      }
    
      /// <summary>
      /// 获取或设置串口的数据位
      /// </summary>
      public int DataBits
      {
       get { return serialPort.DataBits; }
       set { serialPort.DataBits = value; }
      }
    
      /// <summary>
      /// 串口是否已经打开
      /// </summary>
      public bool IsOpen
      {
       get { return serialPort.IsOpen; }
      }
    
      /// <summary>
      /// 设置串口通讯中结束符的值
      /// </summary>
      public string NewLine
      {
       get { return serialPort.NewLine; }
       set { serialPort.NewLine = value; }
      }
    
      /// <summary>
      /// 获取接收到的字节的长度
      /// </summary>
      public int BytesToRead
      {
       get { return serialPort.BytesToRead; }
      }
    
      /// <summary>
      /// 获取或设置触发Read事件前要求的缓存中的可用字节数
      /// </summary>
      public int ReceivedBytesThreshold
      {
       get { return serialPort.ReceivedBytesThreshold; }
       set { serialPort.ReceivedBytesThreshold = value; }
      }
    
    
      /// <summary>
      /// 构造串口简单包装类
      /// </summary>
      public SerialPortWrapper()
       : this("COM1", 9600, 8)
      {
      }
    
      /// <summary>
      /// 构造串口简单包装类
      /// </summary>
      /// <param name="portName">端口(如COM1)</param>
      public SerialPortWrapper(string portName)
       : this(portName, 9600, 8)
      {
      }
    
      /// <summary>
      /// 构造串口简单包装类
      /// </summary>
      /// <param name="portName">端口(如COM1)</param>
      /// <param name="baudRate">波特率</param>
      /// <param name="dataBits">数据位</param>
      public SerialPortWrapper(string portName, int baudRate, int dataBits)
      {
       serialPort = new SerialPort(portName, baudRate, Parity.None, dataBits);
       serialPort.DataReceived += delegate(object sender, SerialDataReceivedEventArgs e)
       {
        if (isTryToClose) return;
    
        isReceivingData = true;
    
        if (OnDataReceived != null)
        {
         OnDataReceived(sender, e);
        }
    
        isReceivingData = false;
       };
      }
    
      ~SerialPortWrapper()
      {
       Dispose(false);
      }
    
    
      /// <summary>
      /// 打开串口
      /// </summary>
      public void Open()
      {
       if (disposed)
       {
        throw new Exception("此串口对象的实例已释放,无法调用!");
       }
    
       isReceivingData = false;
    
       if (!serialPort.IsOpen)
       {
        isTryToClose = false; // 将关闭标志重置
        serialPort.Open();
       }
      }
    
      /// <summary>
      /// 关闭串口
      /// </summary>
      public void Close()
      {
       if (disposed)
       {
        throw new Exception("此串口对象的实例已释放,无法调用!");
       }
    
       // 打开关闭标志,这样在Invoke前检测到关闭标志就不会进行Invoke调用
       isTryToClose = true;
    
       // 在数据处理中时,等待Invoke的调用结束
       while (isReceivingData)
       {
        Application.DoEvents();
       }
       serialPort.Close();
      }
    
      /// <summary>
      /// 读取串口接收缓冲区中的字节数据
      /// </summary>
      /// <returns>读取到的字节的长度</returns>
      public int Read(byte[] buffer, int offset, int count)
      {
       if (disposed)
       {
        throw new Exception("此串口对象的实例已释放,无法调用!");
       }
    
       int length = 0;
       if (serialPort.IsOpen && !isTryToClose)
       {
        length = serialPort.Read(buffer, offset, count);
       }
       return length;
      }
    
      /// <summary>
      /// 读取串口接收缓冲区中的一行数据
      /// </summary>
      /// <returns>以SerialPort的NewLine定义为结束符的一行字符</returns>
      public string ReadLine()
      {
       if (disposed)
       {
        throw new Exception("此串口对象的实例已释放,无法调用!");
       }
    
       string line = null;
       if (serialPort.IsOpen && !isTryToClose)
       {
        line = serialPort.ReadLine();
       }
       return line;
      }
    
      /// <summary>
      /// 读取接收缓冲区中所有可用的字符
      /// </summary>
      /// <returns>缓冲区中的字符串</returns>
      public string ReadExisting()
      {
       if (disposed)
       {
        throw new Exception("此串口对象的实例已释放,无法调用!");
       }
    
       string existing = null;
       if (serialPort.IsOpen && !isTryToClose)
       {
        existing = serialPort.ReadExisting();
       }
       return existing;
      }
    
      /// <summary>
      /// 将指定的文本写入串行端口
      /// </summary>
      /// <param name="text">文本</param>
      public void Write(string text)
      {
       if (disposed)
       {
        throw new Exception("此串口对象的实例已释放,无法调用!");
       }
    
       if (serialPort.IsOpen && !isTryToClose)
       {
        serialPort.Write(text);
       }
      }
    
      /// <summary>
      /// 将制定的字节数组写入串行端口
      /// </summary>
      /// <param name="buffer">字节数组</param>
      /// <param name="offset">起始索引</param>
      /// <param name="count">写入长度</param>
      public void Write(byte[] buffer, int offset, int count)
      {
       if (disposed)
       {
        throw new Exception("此串口对象的实例已释放,无法调用!");
       }
    
       if (serialPort.IsOpen && !isTryToClose)
       {
        serialPort.Write(buffer, offset, count);
       }
      }
    
      /// <summary>
      /// 将制定的字符数组写入串行端口
      /// </summary>
      /// <param name="buffer">字符数组</param>
      /// <param name="offset">起始索引</param>
      /// <param name="count">写入长度</param>
      public void Write(char[] buffer, int offset, int count)
      {
       if (disposed)
       {
        throw new Exception("此串口对象的实例已释放,无法调用!");
       }
    
       if (serialPort.IsOpen && !isTryToClose)
       {
        serialPort.Write(buffer, offset, count);
       }
      }
    
      /// <summary>
      /// 将指定的文本和默认的换行符写入串行端口
      /// </summary>
      /// <param name="text">文本</param>
      public void WriteLine(string text)
      {
       if (disposed)
       {
        throw new Exception("此串口对象的实例已释放,无法调用!");
       }
    
       if (serialPort.IsOpen && !isTryToClose)
       {
        serialPort.WriteLine(text);
       }
      }
    
      /// <summary>
      /// 丢弃串口缓冲区的数据
      /// </summary>
      public void DiscardInBuffer()
      {
       if (disposed)
       {
        throw new Exception("此串口对象的实例已释放,无法调用!");
       }
    
       if (serialPort.IsOpen)
       {
        serialPort.DiscardInBuffer();
       }
      }
    
    
      #region IDisposable 成员
    
      /// <summary>
      /// 释放资源
      /// </summary>
      public void Dispose()
      {
       this.Dispose(true);
       GC.SuppressFinalize(this);
      }
    
      /// <summary>
      /// 释放资源
      /// </summary>
      /// <param name="disposing">显示释放(手动指定释放资源)</param>
      private void Dispose(bool disposing)
      {
       if (!disposed)
       {
        if (disposing)
        {
         this.Close();
         serialPort = null;
        }
        disposed = true;
       }
      }
    
      #endregion
     }
    }
    
    这是个很完整的串口包装类,如果对您有帮助可以拿去使用,请保留我的注释,这样您在使用时也会看的明白点。

    • 已编辑 46577471 2010年10月10日 4:28 修改错误文字
    2010年10月10日 4:24

答案

  •  

    你好 虽然我不怎么懂串口,但是我刚才研究了下串口类的源码,请注意下下面两段:

    public int BytesToRead
    {
        get
        {
            if (!this.IsOpen)
            {
                throw new InvalidOperationException(SR.GetString("Port_not_open"));
            }
            return (this.internalSerialStream.BytesToRead + this.CachedBytesToRead);
        }
    }

     

    其中internalSerialStream就是BaseStream
     
    private int CachedBytesToRead
    {
        get
        {
            return (this.readLen - this.readPos);
        }
    }
     
     
    以及Read方法:

      int num = 0;
        if (this.CachedBytesToRead >= 1)
        {
            num = Math.Min(this.CachedBytesToRead, count);
            Buffer.BlockCopy(this.inBuffer, this.readPos, buffer, offset, num);
            this.readPos += num;
            if (num == count)
            {
                if (this.readPos == this.readLen)
                {
                    this.readPos = this.readLen = 0;
                }
                return count;
            }
            if (this.BytesToRead == 0)
            {
                return num;
            }
        }
        this.readLen = this.readPos = 0;
        int num2 = count - num;
        num += this.internalSerialStream.Read(buffer, offset + num, num2);
        this.decoder.Reset();
        return num;


    以及MSDN上的一段话

    由于 SerialPort 类会缓冲数据,而 BaseStream 属性内包含的流则不缓冲数据,因此二者在可读字节数量上可能会不一致。 BytesToRead 属性可以指示有要读取的字节,但 BaseStream 属性中包含的流可能无法访问这些字节,原因是它们已缓冲到 SerialPort 类中。

     

    由此我们举个例子:

    比如 this.internalSerialStream.BytesToRead=10

    this.CachedBytesToRead=13

    scaleSerialPort.BytesToRead=10+13=23

    scaleSerialPort.BytesToRead >= scaleSerialPort.ReceivedBytesThreshold 成立

    于是执行

     byte[] buffer = new byte[23];
        scaleSerialPort.Read(buffer, 0, 18);

    于是我们进入到串口类的内部Read方法,请参看我的注释

    if (this.CachedBytesToRead >= 1)  //13>=1
        {
            num = Math.Min(this.CachedBytesToRead, count); //min(13,18)=13
            Buffer.BlockCopy(this.inBuffer, this.readPos, buffer, offset, num);// 先读取SerialPort缓存的13个字节
            this.readPos += num; //13
            if (num == count) //很明显这里不成立 13!=18
            {
                if (this.readPos == this.readLen)
                {
                    this.readPos = this.readLen = 0;
                }
                return count;
            }
            if (this.BytesToRead == 0) //23-13=10!=0
            {
                return num;
            }
        }

    this.readLen = this.readPos = 0;
        int num2 = count - num; //18-13=5
        num += this.internalSerialStream.Read(buffer, offset + num, num2); //直接从baseStream中读取5个 num=13+5=18
        this.decoder.Reset();
        return num; //返回18

    对于上面这种情况返回的是18 是正常的,但是我们再看另外一种情况:

    如果当 this.readLen = this.readPos = 0;
        int num2 = count - num; //18-13=5
        num += this.internalSerialStream.Read(buffer, offset + num, num2);

    执行到    num += this.internalSerialStream.Read(buffer, offset + num, num2); 这个时候比如再发起了你说的PLC终端发起了数据,导致baseStream将数据又缓冲到SerialPort中,而且清空了,那么这里读取到的值就是0,或者小于num2=5  那么最终返回的值就小于18了

    所以问题的关键在于SerialStream会在SerialPort内部的inBuffer位数组中缓存数据

    由于我还没有详细的了解整个类 所以这里仅是我自己的猜测而已,希望对你有所帮助

     


    I see you~,.NET交流群71840452 微软中文论坛同城社区成都QQ群:74268428 http://hi.baidu.com/1987raymondMy Blog~~~
    • 已标记为答案 46577471 2010年10月22日 7:28
    2010年10月15日 2:44
    版主

全部回复

  • 真好,看来懂中文的没人懂SerialPort。。。
    2010年10月11日 2:30
  •  

    你好 虽然我不怎么懂串口,但是我刚才研究了下串口类的源码,请注意下下面两段:

    public int BytesToRead
    {
        get
        {
            if (!this.IsOpen)
            {
                throw new InvalidOperationException(SR.GetString("Port_not_open"));
            }
            return (this.internalSerialStream.BytesToRead + this.CachedBytesToRead);
        }
    }

     

    其中internalSerialStream就是BaseStream
     
    private int CachedBytesToRead
    {
        get
        {
            return (this.readLen - this.readPos);
        }
    }
     
     
    以及Read方法:

      int num = 0;
        if (this.CachedBytesToRead >= 1)
        {
            num = Math.Min(this.CachedBytesToRead, count);
            Buffer.BlockCopy(this.inBuffer, this.readPos, buffer, offset, num);
            this.readPos += num;
            if (num == count)
            {
                if (this.readPos == this.readLen)
                {
                    this.readPos = this.readLen = 0;
                }
                return count;
            }
            if (this.BytesToRead == 0)
            {
                return num;
            }
        }
        this.readLen = this.readPos = 0;
        int num2 = count - num;
        num += this.internalSerialStream.Read(buffer, offset + num, num2);
        this.decoder.Reset();
        return num;


    以及MSDN上的一段话

    由于 SerialPort 类会缓冲数据,而 BaseStream 属性内包含的流则不缓冲数据,因此二者在可读字节数量上可能会不一致。 BytesToRead 属性可以指示有要读取的字节,但 BaseStream 属性中包含的流可能无法访问这些字节,原因是它们已缓冲到 SerialPort 类中。

     

    由此我们举个例子:

    比如 this.internalSerialStream.BytesToRead=10

    this.CachedBytesToRead=13

    scaleSerialPort.BytesToRead=10+13=23

    scaleSerialPort.BytesToRead >= scaleSerialPort.ReceivedBytesThreshold 成立

    于是执行

     byte[] buffer = new byte[23];
        scaleSerialPort.Read(buffer, 0, 18);

    于是我们进入到串口类的内部Read方法,请参看我的注释

    if (this.CachedBytesToRead >= 1)  //13>=1
        {
            num = Math.Min(this.CachedBytesToRead, count); //min(13,18)=13
            Buffer.BlockCopy(this.inBuffer, this.readPos, buffer, offset, num);// 先读取SerialPort缓存的13个字节
            this.readPos += num; //13
            if (num == count) //很明显这里不成立 13!=18
            {
                if (this.readPos == this.readLen)
                {
                    this.readPos = this.readLen = 0;
                }
                return count;
            }
            if (this.BytesToRead == 0) //23-13=10!=0
            {
                return num;
            }
        }

    this.readLen = this.readPos = 0;
        int num2 = count - num; //18-13=5
        num += this.internalSerialStream.Read(buffer, offset + num, num2); //直接从baseStream中读取5个 num=13+5=18
        this.decoder.Reset();
        return num; //返回18

    对于上面这种情况返回的是18 是正常的,但是我们再看另外一种情况:

    如果当 this.readLen = this.readPos = 0;
        int num2 = count - num; //18-13=5
        num += this.internalSerialStream.Read(buffer, offset + num, num2);

    执行到    num += this.internalSerialStream.Read(buffer, offset + num, num2); 这个时候比如再发起了你说的PLC终端发起了数据,导致baseStream将数据又缓冲到SerialPort中,而且清空了,那么这里读取到的值就是0,或者小于num2=5  那么最终返回的值就小于18了

    所以问题的关键在于SerialStream会在SerialPort内部的inBuffer位数组中缓存数据

    由于我还没有详细的了解整个类 所以这里仅是我自己的猜测而已,希望对你有所帮助

     


    I see you~,.NET交流群71840452 微软中文论坛同城社区成都QQ群:74268428 http://hi.baidu.com/1987raymondMy Blog~~~
    • 已标记为答案 46577471 2010年10月22日 7:28
    2010年10月15日 2:44
    版主
  • 感谢你的回复,最近很忙,忘了来看帖子了。

    你说的情况是因为SerialPort缓冲了数据才导致发生的吧,至少我的理解是这样的,看我的日志(发在英文区的,不翻译了,凑合看吧)

     

    2010-09-29 16:54:21:COM1 Received 18 Bytes:01030400000000FE2A01030400000000FE2A
    2010-09-29 16:54:21:From 01030400000000FE2A analysis weight :0
    2010-09-29 16:54:21:Not allow to show
    2010-09-29 16:54:21:Discard

    2010-09-29 16:54:36:COM1 Received 18 Bytes:01030400005744FE2A01030400005744FE2A
    2010-09-29 16:54:36:From 01030400005744FE2A analysis weight :0.86
    2010-09-29 16:54:37:Data used in program
    2010-09-29 16:54:37:Discard

    2010-09-29 16:55:06:COM1 Received 8 Bytes:01030400881A46FE
    2010-09-29 16:55:06:Analysis faild
    2010-09-29 16:55:06:Discard

    2010-09-29 16:55:06:COM1 Received 9 Bytes:2A01030400881A46FE
    2010-09-29 16:55:06:Analysis faild
    2010-09-29 16:55:06:Discard

    2010-09-29 16:55:06:COM1 Received 9 Bytes:2A01030400881A46FE
    2010-09-29 16:55:06:Analysis faild
    2010-09-29 16:55:06:Discard

    2010-09-29 16:55:06:COM1 Received 9 Bytes:2A01030400881A46FE
    2010-09-29 16:55:06:Analysis faild
    2010-09-29 16:55:06:Discard

    2010-09-29 16:55:06:COM1 Received 9 Bytes:2A01030400881A46FE
    2010-09-29 16:55:06:Analysis faild
    2010-09-29 16:55:06:Discard

    2010-09-29 16:55:06:COM1 Received 9 Bytes:2A01030400881A46FE
    2010-09-29 16:55:06:Analysis faild
    2010-09-29 16:55:06:Discard

    2010-09-29 16:55:07:COM1 Received 9 Bytes:2A01030400881A46FE
    2010-09-29 16:55:07:Analysis faild
    2010-09-29 16:55:07:Discard

    难道原因是因为SerialPort缓冲的原因?还是因为我每次都清除缓存数据的原因??如果你有时间再看帖子,帮我看下,谢谢了!!
    • 已编辑 46577471 2010年10月22日 7:40 格式错误
    2010年10月22日 7:38