通过RS232发送和接收消息的原子发送

时间:2013-02-06 14:03:37

标签: c# multithreading serial-port

我有一个特殊的问题。

我需要用来与串行设备通信的自定义协议如下工作,我正在使用标准的ASCII缩写。

  1. - >我的程序发送SOH开始传输
  2. - >我的程序发送一个STX +数据+ ETX包(我实际上一次发送SOH和这个数据包)
  3. < - 设备发回ACK以确认他已收到数据包
  4. - >现在我的程序发送一个EOT来指示传输结束
  5. 如果消息是期望答案的请求,则设备发回数据。因为SerialPort.DataReceived的工作方式,我必须手动将收到的消息拼凑回来。 它有点像这样:

    1. < - 设备发送SOH + STX +数据
    2. < - 设备发送附加字节,以ETX结尾
    3. - >我的程序在完整收到包裹后发送ACK
    4. < - 设备发送EOT
    5. 这基本上就是这两个人沟通的方式,除了丢失的数据包和NAK以及那些东西,但我不想让它变得太复杂。

      我的问题是:如何将完整消息的发送或接收完整的消息包装到原子操作中?我不想要的是我的程序发送新消息,而我正在将收到的消息粘在一起。

      我尝试过使用Monitor.Enter()Monitor.Exit()但是收到的事件是在另一个线程上调用的,所以没有骰子。

      我还尝试使用只有1个资源的Semaphore,在发送或接收开始时调用semaphore.WaitOne(),并在发送EOT后调用semaphore.Release()从设备收到EOT。这也行不通。

      有没有办法更好地做到这一点?

3 个答案:

答案 0 :(得分:1)

我会使用锁:

http://msdn.microsoft.com/en-gb/library/c5kehkcz(v=vs.71).aspx

您可以设置一个全局对象说serialLock并使用它来确保当您将各个部分粘合在一起时,任何发送线程都必须等待:

收到

lock(serialLock)
{
//Glue data
}

在所有发送中:

lock(serialLock)
{
// send data
}

这将对不同线程等的任何问题进行排序。

在释放锁定之前,我还要确保您已在胶水数据部分收到完整的消息。也许要清除它将需要更新DataReceived事件中的线程安全数据结构,并且在粘贴数据部分中,您需要在释放锁之前在此部分中检查完整消息的数据结构。 / p>

答案 1 :(得分:1)

当我编写代码以通过串口与GSM调制解调器通信时遇到类似的问题。

通过在System.Threading命名空间中使用AutoResetEvent类,我能够开发出一个相当不错的解决方案。基本上,我使用Send方法等待ACK信号。

这是一个骨架代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO.Ports;

namespace Sample
{
    public class SocketWrapper
    {
        #region Static Variables

        private static AutoResetEvent _SendWaitHandle = new AutoResetEvent(false);

        #endregion

        #region Member Variables

        private object _SendLockToken = new object();

        #endregion

        #region Public Methods

        public void Write(byte[] data)
        {
            Monitor.Enter(_SendLockToken);
            try
            {
                // Reset Handle
                _SendWaitHandle.Reset();

                // Send Data
                // Your Logic

                // Wait for ACK
                if (_SendWaitHandle.WaitOne(1000)) // Will wait for 1000 miliseconds
                {
                    // ACK Received
                    // Send EOT
                }
                else
                {
                    // Timeout Occurred
                    // Your Logic To Handle Timeout
                }
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                Monitor.Exit(_SendLockToken);
            }
        }
        #endregion

        #region Private Methods

        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            // When ACK is received call SET
            _SendWaitHandle.Set();

        }
        #endregion
    }
}

注意: 在DataReceived方法中,请确保不要直接/间接调用Write方法,因为这可能会导致死锁。始终使用BackgroundWorker或使用TPL在不同的线程上开始处理接收的数据。

答案 2 :(得分:1)

我将定义一个'SerialTxn'类,其中包含请求数据的成员,回复数据,编码/解码协议的状态机,超时,异常/ errorMessage,OnCompletion(SerialTxn thisTxn)事件/委托,以及将完整交易转换为一个类所需的任何其他内容。然后我会得到一个,用请求数据等加载它并将它(BlockingCollection)排队到一个处理串口的线程。线程获取实例,将其方法调用到tx和rx数据,并在完成后调用OnCompletion事件/委托。这允许同步和异步事务 - OnCompletion事件可以发信号通知事件(可能是AutoResetEvet),原始线程正在等待,或者它可以将回复排队回原始线程可以在其希望时处理的某个队列。 / p>

由于只有一个线程处理该端口,因此tx / rx操作重叠的可能性为零。 txn要么完全成功,要么在适当的异常或错误消息中加载到SerialTxn中。如果发生错误,或者需要将数据发送到记录器,GUI或其他任何内容,则所有相关数据都在一个对象中的一个位置,准备排队,BeginInvoked或其他任何内容。

我不喜欢锁定漫长的操作...