【问题标题】:Fastest or best way to read from SSLSocket从 SSLSocket 读取的最快或最佳方式
【发布时间】:2013-11-11 23:15:19
【问题描述】:

我正在运行一个多线程简约 http(s) 服务器(虽然不是 Web 服务器),它接受三个服务器套接字上的连接:本地、互联网和 internet-ssl。

每个套接字的超时时间为 1000 毫秒(将来可能会降低)。

工作线程读取请求如下:

byte[] reqBuffer = new byte[512];
theSocket.getInputStream().read(reqBuffer);

现在的问题是新实现的 ssl 套接字会出现 1/n-1 记录拆分技术的问题。还有一些客户端在使用 ssl(4/n-4 等)时会以其他奇怪的方式拆分,所以我想我可能会像这样执行多次读取:

byte[] reqBuffer = new byte[512];
InputStream is = theSocket.getInputStream();
int read = is.read(reqBuffer, 0, 128); // inital read - with x/n-x this is very small
int pos = 0;
if (read > 0) {
   pos = read;
}
int i = 0;
do {
   read = is.read(reqBuffer, pos, 128);
   if (read > 0) {
      pos += read;
   }
   i++;
} while(read == 128 && i < 3); // max. 3 more reads (4 total = 512 bytes) or until less than 128 bytes are read (request should be completely read)

这适用于 firefox 或 chrome 等浏览器以及使用该技术的其他客户端。

现在我的问题是新方法要慢得多。对本地套接字的请求非常慢,以至于一个 2 秒超时的脚本请求超时(我不知道为什么)。也许我的代码中有一些逻辑问题?

有没有更好的方法来读取 SSL 套接字?因为每秒最多有数百甚至上千个请求,而新的读取方法甚至会减慢 http 请求的速度。

注意:ssl-socket 目前未使用,在我解决此问题之前不会使用。

我还尝试使用缓冲读取器逐行读取,因为我们在这里讨论的是 http,但是服务器在文件描述符耗尽时爆炸(限制为 20 000)。不过,可能是因为我的实现。

感谢有关此问题的所有建议。如果您需要有关代码的更多信息,请告诉我,我会尽快发布。

编辑: 实际上,我对我正在尝试做的事情进行了更多思考,我意识到这归结为阅读 HTTP 标头。因此,最好的解决方案是实际读取请求行的行(或字符的字符)并在 x 行之后停止读取或直到达到空行(标记标题的结尾)。 我目前的方法是在套接字的 InputStream 周围放置一个 BufferedInputStream 并使用 InputStreamReader 读取它,该 InputStreamReader 由 BufferedReader“读取”(问题:当我使用 BufferedReader 时使用 BufferedInputStream 是否有意义?)。 此 BufferedReader 逐个读取请求字符,检测行尾 (\r\n) 并继续读取,直到达到超过 64 个字符的行、最多读取 8 行或达到空行 (标记 HTTP 标头的结尾)。明天我将测试我的实现并相应地编辑此编辑。

编辑: 我差点忘了在这里写下我的结果:它有效。在每个套接字上,甚至比以前的工作方式更快。感谢大家为我指出正确的方向。我最终是这样实现的:

List<String> requestLines = new ArrayList<String>(6);
InputStream is = this.cSocket.getInputStream();
bis = new BufferedInputStream(is, 1024);
InputStreamReader isr = new InputStreamReader(bis, Config.REQUEST_ENCODING);
BufferedReader br = new BufferedReader(isr);

/* read input character for character
* maximum line size is 768 characters
* maximum number of lines is 6
* lines are defined as char sequences ending with \r\n
* read lines are added to a list
* reading stops at the first empty line => HTTP header end
*/
int readChar; // the last read character
int characterCount = 0; // the character count in the line that is currently being read
int lineCount = 0; // the overall line count
char[] charBuffer = new char[768]; // create a character buffer with space for 768 characters (max line size)

// read as long as the stream is not closed / EOF, the character count in the current line is below 768 and the number of lines read is below 6
while((readChar = br.read()) != -1 && characterCount < 768 && lineCount < 6) {
    charBuffer[characterCount] = (char) readChar; // fill the char buffer with the read character
    if (readChar == '\n' && characterCount > 0 && charBuffer[characterCount-1] == '\r') { // if end of line is detected (\r\n)
        if (characterCount == 1) { // if empty line
            break; // stop reading after an empty line (HTTP header ended)
        }
        requestLines.add(new String(charBuffer, 0, characterCount-1)); // add the read line to the readLines list (and leave out the \r)
        // charBuffer = new char[768]; // clear the buffer - not required
        characterCount = 0; // reset character count for next line
        lineCount++; // increase read line count
    } else {
        characterCount++; // if not end of line, increase read character count
    }
}

【问题讨论】:

  • http(s) 服务器(虽然不是 Web 服务器)”是什么意思?
  • @Bruno 好吧,它不是为浏览器或类似的东西提供网站。它只实现了 HTTP 协议的一小部分,并进行了一些肮脏(但可靠)和快速的请求解析。
  • 感谢您的建议。阅读所有这些流和读者让我得出结论,我应该依赖我正在处理的协议。由于我只需要 HTTP 标头,这应该是一项非常简单的任务(在处理 content-length 标头后阅读内容也不会是什么大问题)。我编辑了我的原始帖子以反映这一点。
  • 一般来说,您最初所做的关于读取固定数量的字节以获取 HTTP 请求(即使它只是标头)的假设是错误的。您确实应该检测标题的行尾(并为实体使用内容长度或分块传输编码)。

标签: java sockets ssl https serversocket


【解决方案1】:

这很可能会更慢,因为您正在等待另一端发送更多数据,可能是它永远不会发送的数据。

更好的方法是给它一个更大的缓冲区,比如 32KB(128 很小),并且只读取一次可用的数据。如果需要在某种消息中重新组合这些数据,则不应使用超时或固定数量的循环,因为 read() 只能保证至少返回一个字节。

【讨论】:

  • 是的,这可能是问题所在,也是我在最后一次读取没有读取完整的 128 个字节后停止读取的原因。我也可以在两次读取中读取 128 / 384 字节,但由于记录拆分,我至少需要两个。据我了解,超时限制了读取等待的时间并且不会增加它还是我错了?固定数量的循环用于将读取的总大小限制为 512 字节。我总共只读取了 512 个字节的请求(从我的缓冲区大小可以看出),因为我需要的所有信息都包含在这 512 个字节中(如果不是,则请求无效)。
  • 如果要读取512字节,为什么不从头读取512字节呢?
  • @Kazuo,您应该始终假设无论如何您都需要多次读取。显然,TLS 记录拆分是必要的,但无论如何普通 TCP 也需要这样做。不要对记录将如何拆分(1/n-1、4/n-4、0/n、...)做出任何假设,也不要假设 3 就足够了。只需阅读直到它结束,或使用超时。使用BufferedInputStream 可能会有所帮助,但不要忘记关闭它。
  • @Kazuo DataInputStream as readFully 读取特定字节数。
  • @EJP,你说得对,我只是在问题“[...] 服务器爆炸用完文件描述符 中的相关段落的上下文中表示这个意思”。我猜有些 I/O 流没有以某种方式正确关闭。
【解决方案2】:

您应该当然在 SSLSocket 的输入流周围包裹一个 BufferedInputStream。

您一次读取 128 个字节并推进偏移量的技术完全没有意义。一次尽可能多地阅读并处理它。或者从缓冲流中一次一个字节。

同样,您当然应该将 SSLSocket 的输出流包装在 BufferedOutputStream 中。

【讨论】:

  • 好吧,我不会认为它毫无意义,因为它按预期工作,只是太慢了。我会尝试你的建议。一次读取一个字节似乎很慢。你介意建议一个实现吗?
  • 额外的代码来做你已经在做的事情总是毫无意义的。 BufferedInputStream.read() 并不慢,因为有缓冲。
  • @EJC 是的,它可能会做同样的事情,但我只需要能够处理拆分的请求(它允许我这样做,只是不够快)。但是,您当然说这不是一个好的解决方案是对的。但毕竟,这就是我来这里问这个问题的原因。感谢您的建议,我想我正在取得进展。
猜你喜欢
  • 1970-01-01
  • 2011-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多