阅读和处理流

时间:2014-03-05 13:58:16

标签: java sockets streamreader

我想在一行中读取并处理包含单个OR多个命令的String流。

我目前正在使用InputStream in = socket.getInputStream();作为我的输入流。

同样用于处理输入的典型线程:

public void run() {
    String input = "";
    try {
        int data = 0;
        while (!isInterrupted()) {
            while ((data = in.read()) != -1 && !isInterrupted()) {
                input += Integer.toHexString(data);
                handleInput(input);
            }
            try {
                sleep(500);
            } catch (InterruptedException e) {
                break;
            }
        }
        socket.close();
        return;
    } catch (IOException e) {
        main.log("Connection lost...");
        main.log(e.toString());
        main.stopBTCommunication();
        main.startBTServer();
    }
}

handleInput()旨在处理任何给定的字符串并正确响应。这种实现的问题是,从handleInput()读取的每个字节都会调用in.read()。我知道,我可以使用BufferedReader.readLine(),但这需要每个命令命令都附加"\n",但事实并非如此,也无法更改。

我知道

        while (!isInterrupted()) {
            while ((data = in.read()) != -1 && !isInterrupted()) { 

是一种令人讨厌的东西,但基本上它希望线程读取,直到没有读取新内容,然后处理该输入然后再次读取...

编辑:

基本上,我需要的是非阻塞read()

EDIT2:

传入命令和命令链如何显示:

  • 选择命令:“s”
  • ReadLine命令:“rlXX”,其中X是十六进制数字
  • WriteLine命令:“wlXXSSSSSSSS”,其中X和S是十六进制数字

所以命令链可能看起来像下列之一:

  • "s"
  • "srlff" = "s" + "rlff"
  • "rlffwlbb2e2e2e2erlbb" = "s" + "rlff" + "wlbb2e2e2e2e" + "rlbb"

4 个答案:

答案 0 :(得分:2)

我认为你真的不需要非阻塞读取。您需要一种逐字节读取流的方法,并将其转换为命令。

类似的东西:

 public void processStream(InputStream in) {
     List<Command> commands = new ArrayList<Command>();
     while((int c = in.getChar()) != -1 ) {
          switch((char)c) {
              case 's':
                  commands.add(new SelectCommand());
                  break;
              case 'r':
                  commands.add(ReadCommand.buildFromStream(in));
                  break;
              case 'w':
                  commands.add(WriteCommand.buildFromStream(in));
                  break;
              case ';':
                  commandEngine.execute(commands);
                  break;
              default:
                  throw new StreamParseError("unexpected character: " + c);
          }
     }
 }

这假定SelectCommandReadCommandWriteCommandCommand类型兼容。

...例如ReadCommand.buildFromStream为:

   public static ReadCommand buildFromStream(InputStream in) {
        if((char)in.read() != 'n') {
            throw new StreamParseError("Expect 'l' after 'r'");
        }

        // bad error checking here - be less lazy in real life.
        String hexNum = in.read() + in.read(); 
        int num = Integer.parseInt(hexNum,16);
        return new ReadCommand(num);
   }

这是非常原始的解析,但它显示了原理。有很好的技术可以进行更高级的解析,如果你愿意,可以阅读。

您还可以使用Scanner。最常见的是,Scanner与分隔符一起使用,但它也可以查找正则表达式模式。

    Scanner scanner = new Scanner(stream);
    String cmd = "";
    while(cmd != "e") { // I made up an "end" command :)
        cmd = scanner.findWithinHorizon("(s|rl..|wl.{8}|e)",12);
        if(cmd == null) {
            // end of input, or badly formed input
            break;
        }
        handleCmd(cmd);
    }

答案 1 :(得分:1)

编辑 - 使用这个特定的输入/输出,由于你没有分隔符,扫描器可能不是这里的方法,但如果你在命令之间有一个分隔符,那将是一个很好的选择,所以我会在这里保留答案,希望它可能在将来帮助某人。

由于你的例子没有分隔符,我必须得到一些hack-y来证明扫描程序的真棒,但它仍然适用于你列出的确切命令。如果您期望命令词汇表发生变化,那将不是一个好的选择。

如果可能的话,我真的建议使用分隔符。它让生活更轻松。


如果我是你,我会看Scanner课。

Scanner可以包装您的输入流,然后根据正则表达式或分隔符进行扫描以获取输入块。然后,您的handleInput()方法可以对块(整个命令)进行操作,而不是单个字节。

以下是一个简短的独立示例:

package com.stackoverflow.q22199860;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.regex.Pattern;

public class ReadStream
{
    public static void main(String[] args)
    {
        Pattern commandPattern = Pattern.compile("s|rl|wl");
        String commands = "srlffwlbb2e2e2e2erlbb";
        Charset utf8 = StandardCharsets.UTF_8;

        try (
            InputStream inputStream = new ByteArrayInputStream(commands.getBytes(utf8));
            Scanner scanner = new Scanner(inputStream, utf8.name());
        ) {
            scanner.useDelimiter(commandPattern);
            while(scanner.hasNext()) {
                String command = scanner.next();
                if (command.isEmpty()){
                    //s
                    System.out.println("s" + command);
                } else if (command.length() == 2) {
                    //rl
                    System.out.println("rl" + command);
                } else if (command.length() == 10) {
                    //wl
                    System.out.println("wl" + command);
                }
            }
        }
        catch (IOException e)
        {
            System.err.println("Error Reading Stream");
        }
    }
}

这个输出是:

s
rlff
wlbb2e2e2e2e
rlbb

答案 2 :(得分:1)

你可以读取像这样的字节数组

int bytesRead = 0;
byte[] buffer = new byte[1024]; // reads up to 1024 byte chunks
while((bytesRead = in.read(buffer)) != -1) {
    for ( int i = 0; i < bytesRead; i++ ) {
        input += Integer.toHexString(buffer[i]);
    }

    handleInput(input);
}

上面的代码调用与旧代码相同,“输入”不断增长并反复使用调用handleInput()。不确定这是否是你的意图,但看起来很可疑。

答案 3 :(得分:1)

请注意,您正在从中读取数据。这意味着您必须自己实现命令结构的恢复,即您必须至少在自己的代码中检测命令的开头和结尾。

这再次导致另一个问题:您无法保证传输层如何将流的数据拆分为“块”。您可以在一次read(buffer)调用中收到一个命令加半个命令,然后在下一个read(buffer)中接收命令的后半部分以及更多数据。

因此,我建议您只有在检测到一条消息/命令/结束时才继续读取数据,然后在读取更多传入数据并重复之前仅对此单条消息执行处理。其他一切(即处理部分收到的消息)很容易变得混乱。