【问题标题】:Issues with concurrent reads from two serial ports via RxTx in a java application在 java 应用程序中通过 RxTx 从两个串行端口并发读取的问题
【发布时间】:2014-04-07 16:43:07
【问题描述】:

我的问题的一些背景

我有一个从两个串行端口读取的应用程序。这些串行端口正在利用我工作的公司定制的两件硬件之间的消息。一个串行端口将消息从硬件 A 传输到硬件 B,而另一个则将消息传送到另一个方向。我需要阅读、解析和显示这些消息,以帮助调试我们遇到的明显丢弃和重复消息的问题。 最重要的是,我的应用程序必须在消息发送时显示消息(即,按照发送的顺序发送的次数),并带有正确的时间戳.

我的应用程序是用 java 编写的,并通过 RxTx 与串行端口连接。我一直在 64 位 Windows 7 PC 上运行我的应用程序。

发送的绝大多数消息(双向)是请求ACK(对这些请求的确认)。每个请求消息都包含一个序列号,相应的 ACK 将包含相同的序列号。当系统不忙时,我的程序似乎可以完美运行。但是,当消息开始很快收到时,我的程序就会出现问题。

我遇到的问题是,我的应用程序似乎正在从一个串行端口读取多条消息,然后然后从另一个串行端口读取,而不是同时从两个串行端口读取。

我的应用程序的核心使用 5 个线程。其中两个是读取器,它们只从端口读取一个字节,然后将该字节放入队列中。这些队列中的每一个都与将这些字节解析为消息的另一个线程共享。 (所以有两个处理线程,每个读者一个)。然后,这两个处理线程都将这些解析的消息放入另一个队列中。管理这些消息的显示的第五个线程然后从该队列中获取消息。

(上面没有提到的是我的 GUI,它通过 EDT 运行)。

在我看来,问题在于(或可能之前)读取器线程,并且它似乎与并发性或共享 CPU 时间片之类的事情有关。我只是不知道这种阻塞是发生在操作系统级别(操作系统从一个端口读取而不是另一个端口)、RxTx 级别还是在我的代码中。

我的读者线程的代码在这个问题的底部。

我的推理:

  1. 我将每个字节与包含微秒近似值的时间戳关联起来(通过改编自 Jason Smith's answer to another question 的技术获得)。当我看到我正在从一个端口(端口 A)和 then 从另一个端口(端口 B)读取多条消息时,我注意到时间戳(以及读取时间)从端口 B 读取的第一条消息的第一个字节发生在 从端口 A 读取的任何条消息的第一个字节的时间戳之后。换句话说,它出现尽管在读取每个字节后调用了Thread.yield(),但端口 A 读取器线程从未屈服于端口 B 读取器线程(如本问题底部的读取器线程代码所示)。为了说明,我看到了这样的消息序列:

    [From Hardware A, To Hardware B] [Timestamp: 0 us] Request for 0x01
    [From Hardware A, To Hardware B] [Timestamp: 60 us] ACK for 0x02
    [From Hardware A, To Hardware B] [Timestamp: 120 us] ACK for 0x03
    [From Hardware B, To Hardware A] [Timestamp: 180 us] ACK for 0x01
    [From Hardware B, To Hardware A] [Timestamp: 240 us] Request for 0x02
    [From Hardware B, To Hardware A] [Timestamp: 300 us] Request for 0x03
    

    但我希望这些消息会像这样到达:

    [From Hardware A, To Hardware B] [Timestamp: 0 us] Request for 0x01
    [From Hardware B, To Hardware A] [Timestamp: 60 us] ACK for 0x01
    [From Hardware B, To Hardware A] [Timestamp: 120 us] Request for 0x02
    [From Hardware A, To Hardware B] [Timestamp: 180 us] ACK for 0x02
    [From Hardware B, To Hardware A] [Timestamp: 240 us] Request for 0x03
    [From Hardware A, To Hardware B] [Timestamp: 300 us] ACK for 0x03  
    
  2. 我知道这个问题在于我的代码而不是其他硬件,因为我看到带有时间戳的 ACK 在 ACK 响应的请求之前到达。这在编程上是不可能的,因为如果发送 ACK 之前没有收到请求,发送 ACK 的硬件将不知道要发送的消息的序列号或类型。

  3. 我已将读取器线程将已读取的字节放入队列所需的时间减少到大约 1.5 微秒(基于读取和排队 500,000 个字节后的平均基准)。我见过的最快的消息传入是每 50 到 60 uSec 大约 1 条消息,所以如果两个读取器线程在读取一个字节后相互让步,我相信第一个字节的时间戳从每个串行端口上读取的消息相当准确。

底线

我想到的解决方案包括更改external library I use to communicate with serial ports,以某种方式修复我的代码(如果问题在于它),在基于 linux 的操作系统的 PC 上运行我的应用程序,或者以某种方式至少重写我的应用程序的阅读组件使用比 java 更低级的语言。

这些解决方案中的每一个都可能是我需要的解决方案,或者它们可能是一个死胡同并且会浪费大量时间。希望你们中的某个人以前处理过这个问题,或者看到过一个我没有处理过的问题,这样我就可以减少我浪费在尝试处理这个问题上的时间!

我的读者的逻辑

我的读者是一个私有的内部类。在其包含的类中定义了以下四个常量:

public static final long BYTE_MASK = 0x7F80000000000000L;
public static final int BYTE_SHIFT = 55;
public static final long TIMESTAMP_MASK = 0x007FFFFFFFFFFFFFL;
public static final long TIMESTAMP_SHIFT = 0;

SerialPorts 及其对应的InputStreams 的创建。
COMM_PORT_ID 是我所需 COM 端口的 CommPortIdentifier 对象的数组。
serialPorts 是一个数组我想要的 COM 端口的 SerialPort 对象。
commInput 是我想要的 COM 端口的 InputStream 对象的数组。
所有这些都发生在包含的类中。

for (int i = 0; i < 2; i++)
{
      serialPorts[i] = (SerialPort) COMM_PORT_ID[i].open("SERIAL CONNECTION", 2000);
      serialPorts[i].setSerialPortParams(BAUD_RATE, DATA_BITS, STOP_BITS, PARITY);
      serialPorts[i].setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
      serialPorts[i].enableReceiveTimeout(1000);
      serialPorts[i].setInputBufferSize(3);
      serialPorts[i].disableReceiveThreshold();
      serialPorts[i].notifyOnDataAvailable(false);
      serialPorts[i].notifyOnFramingError(true);
      serialPorts[i].notifyOnOutputEmpty(false);
      serialPorts[i].notifyOnOverrunError(true);
      commInput[i] = serialPorts[i].getInputStream();
}

SerialPortReader 对象的创建和线程的启动(也发生在包含类中):

comOneReader = new SerialPortReader(commInput[0],
                                    comOneInputByteQueue);
comTwoReader = new SerialPortReader(commInput[1],
                                    comTwoInputByteQueue);

Thread comOne = new Thread(comOneReader, "COM1 Reader");
Thread comTwo = new Thread(comTwoReader, "COM2 Reader");

comOne.setPriority(Thread.MAX_PRIORITY);
comTwo.setPriority(Thread.MAX_PRIORITY);

comOne.start();
comTwo.start();

读者本身:

private class SerialPortReader implements Runnable
{
    private final long startSystemTime;
    private final long startNano;
    private final InputStream input;
    private AtomicBoolean reading; // used to stop and start the reading of bytes
    private final LinkedTransferQueue<Long> toProcessorByteQueue;
    private long holder;

    public SerialPortReader(InputStream input,
                            LinkedTransferQueue<Long> toProcessorByteQueue)
    {
        this.input = input;
        this.toProcessorByteQueue = toProcessorByteQueue;
        reading = new AtomicBoolean();
        startSystemTime = System.currentTimeMillis();
        startNano = System.nanoTime();
    }

    @Override
    public void run()
    {
        long in = -1;
        reading.set(true);
        while (reading.get())
        {
            try
            {

                in = -1;

                // Read the next byte from the serial port, blocking until one is available
                if ((in = input.read()) != -1)
                {   
                    // I store the byte and a timestamp (in microseconds) in a long
                    // Skipping bit 63 (the sign bit), I will store the byte from the serial read
                    // in bits 62 - 55.  Then the remainder (bits 54-0) will be the timestamp                  
                    holder = ((in & 0xFF)) << BYTE_SHIFT;
                    holder |= ((startSystemTime * 1000L) + ((System.nanoTime() - startNano) / 1000L)) & TIMESTAMP_MASK;

                    // Add the long to the queue
                    toProcessorByteQueue.add(holder);                      
                }

                Thread.yield();
            }
            catch (IOException ioe)
            {
                // Deal with Exception
            }
        }
    }

    public void stop()
    {
        reading.set(false);
    }

}

抱歉问了这么长的问题,但希望我能够回答您的任何问题!

【问题讨论】:

  • Thread.yield() 只是一个提示,不应依赖。
  • @AlexeiKaigorodov,我知道。我包括Thread.yield() 电话认为它不会受到伤害。问题是我使用此应用程序的目的是发现我在问题中提到的两个硬件之间的消息何时丢失或重复。因此,即使我可能期望消息具有某种顺序,我也需要能够接收乱序消息。这个限制意味着我不能使用任何类型的阻塞并发工具(信号量、锁、屏障等)来指导阅读器线程的运行顺序。

标签: java multithreading serial-port rxtx serial-communication


【解决方案1】:

好久没写串口代码了:-)

commInput[0] 和 [1] 是从哪里来的?除非您对如何创建这些输入流非常特别,否则您的 java 程序和实际硬件之间肯定会有很多缓冲。它可能发生在 java 库中,可能发生在 WinDoze 用户模式库中,并且可能发生在设备驱动程序中。如果您的串口设备是通过 USB 端口连接的,那可能会更糟。

我不记得有哪些选项可以消除某些缓冲,但您可能无法通过 Java 来做到这一点。在最坏的情况下,根据您需要测量时间的精确程度,您可能必须构建自己的设备驱动程序。

坦率地说,如果我接到你的任务,我会认真考虑编写一个微控制器来收集数据并为其添加时间戳,然后将其上传到 PC 进行分析。


FWIW:您可能在线程方面做得过火了。为什么不在读取字节的同一线程中解析协议?让协议解析器线程从队列中读取而不是直接从设备中读取有什么好处?

此外,您可能需要重新考虑优先级。如果内存是有限的,并且如果程序必须运行很长时间,那么您会希望将数据从队列中拉出的线程以 更高 优先级运行,而不是放入数据的线程。那一点如果您有能力先收集所有数据,然后再对其进行分析,则建议不适用,但是如果您有能力这样做,那么为什么不将其拆分为两个单独的程序呢?一个用于将数据读入文件,另一个用于分析文件。


祝你好运!

【讨论】:

  • 啊,对不起!我错过了我没有包括 commInput[0] 和 [1] 的创建。 (现已修复!)我有一种感觉,我最终将需要外部硬件,但目前我正在尝试探索软件解决方案(为了便于使用)。我将处理器与阅读器分开,这样阅读器就会被重置并准备好尽快读取下一个字节。我并不真正关心处理/显示的速度,但尽可能快地完成字节的读取/时间戳是非常重要的。首要任务是努力提高性能。
  • 但正如你所说,我可以将我的应用程序分成两个程序,看看它是如何工作的!
  • 没有解决我的问题,但这是最好的答案(我等了很长时间才允许可能的解决方案出现),所以我将其标记为正确
【解决方案2】:

您需要在serialPorts[i].notifyO.... 上设置其他类型的通知事件 如:serialPort.notifyOnRingIndicator(true)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-25
    • 1970-01-01
    • 1970-01-01
    • 2012-12-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多