5

我有一个特殊的问题。

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

  1. -> 我的程序发送 SOH 开始传输
  2. -> 我的程序发送了一个 STX+data+ETX 的数据包(我实际上一次发送了 SOH 和这个数据包)
  3. <- 设备发回一个 ACK​​ 以确认他已收到数据包
  4. -> 现在我的程序发送一个 EOT 来表示传输结束

如果消息是一个期望得到答复的请求,则设备会发回数据。因为这种方式SerialPort.DataReceived有效,所以我必须手动将收到的消息拼凑在一起。它有点像这样:

  1. <- 设备发送 SOH+STX+数据
  2. <- 设备发送额外的字节,以 ETX 结尾
  3. -> 一旦收到完整的包,我的程序就会发送 ACK
  4. <- 设备发送 EOT

这基本上是这两个通信的方式,除了丢失的数据包和 NAK 和那些东西,但我不想让它太复杂。

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

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

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

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

4

3 回答 3

1

我将定义一个“SerialTxn”类,其中包含请求数据、回复数据、对协议进行编码/解码的状态机、超时、异常/错误消息、OnCompletion(SerialTxn thisTxn) 事件/委托以及其他任何所需的成员一个完整的交易归为一类。然后我会得到一个,用请求数据等加载它并将它(BlockingCollection)排队到一个处理串行端口的线程。线程获取实例,调用其方法来发送和接收数据,并在完成后调用 OnCompletion 事件/委托。这允许同步和异步事务 - OnCompletion 事件可以发出一个事件(可能是 AutoResetEvet),表明发起线程正在等待,或者它可以将回复排队返回到发起线程可以在需要时处理的某个队列。

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

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

于 2013-02-06T15:21:24.940 回答
1

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

AutoResetEvent通过使用命名空间中的类,我能够开发出相当好的解决方案System.Threading。基本上,我让我的 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在不同的线程上开始处理接收到的数据。

于 2013-02-06T15:03:47.623 回答
1

我会使用锁:

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

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

在收到的

lock(serialLock)
{
//Glue data
}

在所有发送:

lock(serialLock)
{
// send data
}

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

我还会确保您在释放锁定之前已在胶水数据部分收到完整的消息。也许要清除此问题,您将需要在 DataReceived 事件中更新线程安全数据结构,并且在粘合数据部分中,您需要在此部分中检查数据结构以获取完整消息,然后再释放锁定。

于 2013-02-06T14:12:47.580 回答