0

我有一个程序将通过 System.in 从外部源接收信息。有两种输入模式:行模式和原始模式。在行模式下,输入只是一系列 UTF-8 字符串,每个字符串都以换行符结束。在线路模式下的某个时刻,我会收到通知说我将要接收 N 字节的原始数据。那时输入切换到原始模式,我收到了 N 个字节的原始二进制数据,它们不是有效的 UTF-8 字符。在此之后,它返回到线路模式。

有没有办法在读取字符串和读取原始数据之间轻松切换?我唯一的想法是逐字节读取 InputStream 并在进行时转换为字符。有没有办法用多种类型的输入流包装 System.in?我觉得从两个不同的包装器中读取会导致问题。

(固定)更新:

我尝试了 parsifal 的建议,但遇到了问题。为了模拟切换输入模式,我修改了我的测试工具。(我意识到我的另一个进程最终也需要以这种方式输出。)我不知道问题是由发送端还是接收端引起的。当我在输出模式之间切换时,它似乎没有正确读取字节。此外,它总是出现相同的字节值。以下是一些代码摘录:

FIX: 问题是显然你不能太快地从 OutputStreamWriter 切换到 OutputStream。我在发送原始字节之前添加了一个 1ms 的睡眠命令,问题就解决了!

测试线束:

Process p = processList.get(pubName); //Stored list of started Processes
OutputStream o = p.getOutputStream(); //Returns OutputStream which feeds into stdin
out = new OutputStreamWriter(runPublisher.getOutputStream());

byte[] payload = new byte[25];
out.write("\nPAYLOAD\nRAW\n"); // "RAW\n" signals raw mode
out.write(String.valueOf(payload.length) + "\n");
out.flush();
Thread.sleep(1); //This fixed the problem I was having.
System.out.println(Arrays.toString(payload));
o.write(payload);
o.flush();

客户:

InputStreamReader inReader = new InputStreamReader(System.in);

while(true){
    try{
        if((chIn = inReader.read())!= -1){
            if(chIn == (int)'\n'){
                if(rawMode){
                    if(strIn.equals("ENDRAW"))
                        rawMode = false;
                    else{
                        System.out.println(strIn);
                        //Exception on next line
                        int rawSize = Integer.parseInt(strIn);
                        payload = new byte[rawSize];
                        int t = System.in.read(payload);
                        System.out.println("Read " + t + " bytes");
                        System.out.print(Arrays.toString(payload));
                    }
                }else if(strIn.startsWith("RAW")){
                    rawMode = true;
                }else {
                    // Do other things
                }
                strIn = "";
            }else
                strIn += (char)chIn;
        }else
            break;
    }catch(IOException e){break;}
}

输出(在添加 Sleep 语句之前)如下所示:

测试线束:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

客户端:
25
读取 9 个字节
[83, 72, 85, 84, 68, 79, 87, 78, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 , 0, 0, 0]

Exception in thread "main" java.lang.NumberFormatException: For input string: "
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:470)
    at java.lang.Integer.parseInt(Integer.java:514)
    at myClass.handleCommand(myClass.java:249)
4

2 回答 2

3

System.in您可以使用指定“utf-8”编码的包装InputStreamReader,然后逐字符读取。将字符累积到 a 中StringBuilder并在适当的时候分派(通常在您看到 时'\n',但可能基于构建器的测试)。

当您要读取二进制数据时,只需从底层InputStream( System.in) 读取。根据InputStreamReader需要执行翻译,并且不缓冲数据。

您不想堆栈中使用任何类型的缓冲流或读取器。这将消除任何使用readLine()方法的机会,至少如果您将自己限制在 JDK 类中。


根据您的最新更新进行编辑:

我认为您在生熟模式之间的切换有点可疑。如果我要实现这个,我会创建两个原始操作,String readLine()并且byte[] readData(length). 第一个将字符累积到换行符,第二个读取固定缓冲区。然后你的主循环看起来像这样:

InputStream in = // ...
Reader rd = new InputStreamReader(in, "USASCII");  // or whatever encoding you use

while (true) {
    String command = readLine(rd );
    if (command .equals("RAW")) {
        int length = Integer.parseInt(readLine(rd ));
        byte[] data = readData(in , length);
        if (! readLine(rd ).equals("ENDRAW")) {
            throw // an exception that indicates protocol violation
        }
    }
    else // process other commands
}

我还将把整个事情包装在一个围绕流构建的对象中,并且可能使用回调来分派数据包。

于 2013-01-07T17:47:48.883 回答
1

最好的选择可能是逐字节(使用System.in.read())读取缓冲区,直到您点击 UTF-8 换行字节 0x0A,然后将该字节缓冲区转换为字符串(使用new String(byte[] bytes, "UTF-8"))。

请注意,read()在 InputStream 上调用将返回一个值从 0 到 255 的 int,您需要将其转换为一个字节。您可以在某种 Collection 中累积字节,然后使用标准 Collection 框架工具将其转换为数组以供 String 构造函数使用。

当您看到将要切换的指示器(可能是某种流内信号,某些特定字节)时,然后切换到您的原始字节读取代码。

于 2013-01-07T17:40:49.190 回答