我正在为Java应用程序编写插件。外部软件与此应用程序建立TCP连接,并将作为UTF-8编码的JSON对象的消息发送到我的插件。每条消息由分隔符分隔。我目前正在使用"\u00A1"
(¡
)作为分隔符。
{ "message": "value" }¡{ "message": "value" }¡{ "message": "value" }...
由于TCP不能保证一次有多少数据到达,因此插件必须接收此数据流并提取每个{ "message": "value" }
令牌。听起来好像java.util.Scanner
..
问题是,应用程序没有提供我的插件直接访问TCP套接字。该插件在重复调用receiveData(byte[] bytes)
方法时接收数据。我需要Scanner
可以读取的某种输入流或通道,但我也可以将字节存入(来自receiveData
)。这样的事情存在吗?如果没有,任何实施建议?或者我离开了,是否有更好的方法来解决这个问题?
注意:我最初尝试手动实现此逻辑。我将获取每个接收字节块,解码为字符串,搜索分隔符,并附加到StringBuilder
。然后我意识到这种方法无效,因为传入的byte[]
可能不会以偶数UTF-8字符边界结束并且无法正确解码。我觉得Scanner
正是我想要的,我只是无法弄清楚如何提供输入。
编辑:在应用程序运行时,数据会连续流式传输以更新显示。不可能等到没有更多数据开始解析。
答案 0 :(得分:0)
我最终创建了一个循环缓冲区,其中包含一个实现ReadableByteChannel
的条件变量。似乎工作得很好。这是一个完整的例子:
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.Scanner;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Buffer implements ReadableByteChannel {
private final Lock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private int readIndex = 0;
private int writeIndex = 0;
private int size = 0;
private final int capacity;
private final byte[] buff;
public Buffer(int capacity) {
this.capacity = capacity;
this.buff = new byte[capacity];
}
/**
* Deposit bytes to the buffer. Will only write until
* buffer is full.
* @param bytes the bytes to add
* @return the number of bytes actually added
*/
public int addBytes(byte[] bytes) {
lock.lock();
int writeCount = 0;
try {
int available = capacity - size;
writeCount = available <= bytes.length ? available : bytes.length;
for (int i = 0; i < writeCount; ++i) {
buff[writeIndex] = bytes[i];
incrementWrite();
++size;
}
// notify callers waiting on read()
notEmpty.signal();
} finally {
lock.unlock();
}
return writeCount;
}
public int addBytes(byte[] bytes, int offset, int length) {
lock.lock();
int writeCount = 0;
try {
int available = capacity - size;
writeCount = available <= length ? available : length;
for (int i = 0; i <writeCount; ++i) {
buff[writeIndex] = bytes[offset + i];
incrementWrite();
++size;
}
notEmpty.signal();
} finally {
lock.unlock();
}
return writeCount;
}
@Override
public int read(ByteBuffer byteBuffer) throws IOException {
lock.lock();
try {
// if the current size is 0, wait until data is added
while (size == 0) {
notEmpty.wait();
}
int attempt = byteBuffer.remaining();
int readCount = attempt <= size ? attempt : size;
for (int i = 0; i < readCount; ++i) {
byteBuffer.put(buff[readIndex]);
incrementRead();
--size;
}
return readCount;
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return 0;
} finally {
lock.unlock();
}
}
@Override
public boolean isOpen() {
return true;
}
@Override
public void close() throws IOException {
// do nothing
}
private final void incrementRead() {
// increment and wrap around if necessary
if (++readIndex == capacity) {
readIndex = 0;
}
}
private final void incrementWrite() {
// increment and wrap around if necessary
if (++writeIndex == capacity) {
writeIndex = 0;
}
}
public static void main(String[] args) {
final Buffer buff = new Buffer(1024);
final Scanner scanner = new Scanner(buff).useDelimiter("!");
Thread readThread = new Thread(new Runnable() {
@Override
public void run() {
while (scanner.hasNext()) {
String message = scanner.next();
System.out.println(message);
if (message.equals("goodbye")) {
return;
}
}
}
});
readThread.start();
buff.addBytes("hello,".getBytes());
buff.addBytes(" world!".getBytes());
buff.addBytes("good".getBytes());
buff.addBytes("bye!".getBytes());
}
}