繁体   English   中英

Java NIO:如何知道SocketChannel read()何时完成非阻塞I / O.

[英]Java NIO: How to know when SocketChannel read() is complete with non-blocking I/O

我目前正在使用非阻塞SocketChannel(Java 1.6)充当Redis服务器的客户端。 Redis直接在套接字上接受纯文本命令,由CRLF终止并以类似方式响应,这是一个简单的例子:

发送:'PING \\ r \\ n'

RECV:'+ PONG \\ r \\ n'

Redis还可以返回大量回复(取决于您要求的内容),其中许多\\ r \\ n终止数据部分都作为单个响应的一部分。

我正在使用标准while(socket.read()> 0){// append bytes}循环从套接字读取字节并将它们重新组装到客户端进行回复。

注意:我没有使用Selector,只是连接到服务器的多个客户端SocketChannel,等待服务发送/接收命令。

我感到困惑的是SocketChannel.read()方法在非阻塞模式下的合同 ,具体来说,如何知道服务器何时完成发送,我有整个消息。

我有一些方法可以防止返回太快并让服务器有机会回复,但我坚持的一件事是:

  1. 是否有可能read()返回字节,然后在后续调用返回没有字节,但在另一个后续调用再次返回一些字节?

基本上,如果我收到至少1个字节并且最终read()返回0然后我知道我已经完成了,或者可能服务器只是忙碌而且可能会丢弃一些如果我等待并继续尝试更多的字节?

如果它仍然可以继续发送字节,即使read()返回0字节(之前成功读取后),那么我不知道如何告诉服务器何时完成与我交谈,事实上我很困惑如何java.io. *样式通信甚至可以知道服务器何时“完成”。

你们知道read永远不会返回-1,除非连接已经死了,这些都是标准的长期数据库连接,所以我不会在每次请求时关闭并打开它们。

我知道一个受欢迎的反应(至少对于这些NIO问题)一直在看Grizzly,MINA或Netty - 如果可能的话,我真的想在采用第三方依赖之前了解这一切是如何工作的。

谢谢。

奖金问题:

我原本以为阻塞SocketChannel就是这样的方式,因为我不想让调用者做任何事情,直到我处理他们的命令然后给他们回复。

如果这最终成为一种更好的方法,只要没有足够的字节来填充给定的缓冲区,我就会看到SocketChannel.read()阻塞,我有点困惑...没有逐字节读取所有内容我无法弄清楚这个默认行为是如何实际使用的......我从来不知道从服务器返回的回复的确切大小,所以我对SocketChannel.read()的调用总是阻塞直到超时(此时我终于看到内容位于缓冲区中了。

我不清楚使用阻塞方法的正确方法,因为它总是挂起来读取。

请查看您的Redis规范以获得此答案。

它不违反调用.read()的规则,在一次调用时返回0个字节,在后续调用中返回1个或多个字节。 这是完全合法的。 如果由于网络滞后或Redis服务器的缓慢导致交付延迟,可能会发生这种情况。

您寻求的答案与问题的答案相同:“如果我手动连接到Redis服务器并发送命令,我怎么知道何时完成向我发送响应以便我可以发送另一个命令?”

答案必须在Redis规范中找到。 如果服务器在执行完命令时没有发送全局令牌,则可以逐个命令地实现。 如果Redis规范不允许这样做,则这是Redis规范中的错误。 他们应该告诉你如何判断他们何时发送了所有数据。 这就是shell有命令提示的原因。 Redis应该有一个等价物。

如果Redis在他们的规范中没有这个,那么我会建议使用某种计时器功能。 对处理套接字的线程进行编码,以便在指定的时间段(如5秒)内未收到任何数据后发出命令已完成的信号。 选择比服务器上执行的最长命令长得多的时间段。

如果它仍然可以继续发送字节,即使read()返回0字节(之前成功读取后),那么我不知道如何告诉服务器何时完成与我交谈,事实上我很困惑如何java.io. *样式通信甚至可以知道服务器何时“完成”。

阅读并遵循协议:

http://redis.io/topics/protocol

该规范描述了可能的回复类型以及如何识别它们。 有些是行终止,而多行响应包括前缀计数。

回复

Redis将回复具有不同类型回复的命令。 可以检查服务器发送的第一个字节的回复类型:

  • 使用单行回复,回复的第一个字节将为“+”
  • 如果出现错误消息,回复的第一个字节将为“ - ”
  • 使用整数,回复的第一个字节将是“:”
  • 使用批量回复时,回复的第一个字节将为“$”
  • 使用多批量回复时,回复的第一个字节将为“*”

单行回复

单行回复是单行字符串形式, 以“+”开头,以“\\ r \\ n”结尾 ...

...

多批量回复

像LRANGE这样的命令需要返回多个值(列表的每个元素都是一个值,LRANGE需要返回多个元素)。 这是使用多个批量写入完成的, 前缀为初始行,表示将跟随多少批量写入


是否有可能read()返回字节,然后在后续调用返回没有字节,但在另一个后续调用再次返回一些字节? 基本上,如果我收到至少1个字节并且最终read()返回0然后我知道我已经完成了,或者可能服务器只是忙碌而且可能会丢弃一些如果我等待并继续尝试更多的字节?

是的,这是可能的。 它不仅仅是由于服务器繁忙,而且网络拥塞和故障路由可能导致数据“暂停”。 数据是一个流,可以“暂停”流中的任何位置,而与应用程序协议无关。

继续将流读入缓冲区。 查看第一个字符以确定期望的响应类型。 每次成功读取后检查缓冲区,直到缓冲区根据规范包含完整消息。


我原本以为阻塞SocketChannel就是这样的方式,因为我不想让调用者做任何事情,直到我处理他们的命令然后给他们回复。

我想你是正确的。 根据我对规范的快速了解,阻塞读取不适用于此协议。 由于它看起来基于行, BufferedReader可能有所帮助,但您仍然需要知道如何识别响应何时完成。

已经很久了,但是。

我目前正在使用非阻塞的SocketChannel

为了清楚起见,SocketChannels默认是阻塞的; 要使它们非阻塞,必须显式调用SocketChannel#configureBlocking(false)

我假设你做到了

我没有使用选择器

哇; 那就是问题所在; 如果您打算使用非阻塞通道,那么您应该始终使用选择器(至少用于读取); 否则,你会遇到你描述的混乱,即。 read(ByteBuffer) == 0并不意味着什么(当然,这意味着有在TCP没有字节在这一刻接收缓冲器)。

这类似于检查你的邮箱,它是空的; 这是否意味着这封信永远不会到来? 从来没发过?

我感到困惑的是SocketChannel.read()方法在非阻塞模式下的合同,具体来说,如何知道服务器何时完成发送,我有整个消息。

有一个契约 - >如果一个Selector为读操作选择了一个Channel, 那么下一次调用SocketChannel#read(ByteBuffer)保证返回> 0 (假设ByteBuffer arg中有空间)

这就是你使用Selector的原因,因为它可以在一个选择中调用“select”1Ks的SocketChannels,它们有字节可供读取

现在,在默认阻塞模式下使用SocketChannels没有任何问题; 鉴于你的描述(一个或两个客户),可能没有理由更简单; 但如果要使用非阻塞通道,请使用选择器

我正在使用标准while(socket.read()> 0){// append bytes}循环

这不是NIO的标准技术。 必须将读取的结果存储在变量中,并对其进行测试:

  1. -1,表示EOS,意味着你应该关闭频道
  2. 零,意味着没有要读取的数据,这意味着你应该返回select()循环,并且
  3. 一个正值,意味着你已经读过很多字节,然后你应该在继续之前从ByteBuffer(get()/ compact())中提取和删除。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM