【发布时间】:2014-04-07 16:43:07
【问题描述】:
我的问题的一些背景
我有一个从两个串行端口读取的应用程序。这些串行端口正在利用我工作的公司定制的两件硬件之间的消息。一个串行端口将消息从硬件 A 传输到硬件 B,而另一个则将消息传送到另一个方向。我需要阅读、解析和显示这些消息,以帮助调试我们遇到的明显丢弃和重复消息的问题。 最重要的是,我的应用程序必须在消息发送时显示消息(即,按照发送的顺序和发送的次数),并带有正确的时间戳.
我的应用程序是用 java 编写的,并通过 RxTx 与串行端口连接。我一直在 64 位 Windows 7 PC 上运行我的应用程序。
发送的绝大多数消息(双向)是请求和ACK(对这些请求的确认)。每个请求消息都包含一个序列号,相应的 ACK 将包含相同的序列号。当系统不忙时,我的程序似乎可以完美运行。但是,当消息开始很快收到时,我的程序就会出现问题。
我遇到的问题是,我的应用程序似乎正在从一个串行端口读取多条消息,然后然后从另一个串行端口读取,而不是同时从两个串行端口读取。
我的应用程序的核心使用 5 个线程。其中两个是读取器,它们只从端口读取一个字节,然后将该字节放入队列中。这些队列中的每一个都与将这些字节解析为消息的另一个线程共享。 (所以有两个处理线程,每个读者一个)。然后,这两个处理线程都将这些解析的消息放入另一个队列中。管理这些消息的显示的第五个线程然后从该队列中获取消息。
(上面没有提到的是我的 GUI,它通过 EDT 运行)。
在我看来,问题在于(或可能之前)读取器线程,并且它似乎与并发性或共享 CPU 时间片之类的事情有关。我只是不知道这种阻塞是发生在操作系统级别(操作系统从一个端口读取而不是另一个端口)、RxTx 级别还是在我的代码中。
我的读者线程的代码在这个问题的底部。
我的推理:
-
我将每个字节与包含微秒近似值的时间戳关联起来(通过改编自 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 我知道这个问题在于我的代码而不是其他硬件,因为我看到带有时间戳的 ACK 在 ACK 响应的请求之前到达。这在编程上是不可能的,因为如果发送 ACK 之前没有收到请求,发送 ACK 的硬件将不知道要发送的消息的序列号或类型。
我已将读取器线程将已读取的字节放入队列所需的时间减少到大约 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