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

时间:2011-02-07 20:54:45

标签: java nio nonblocking socketchannel

我目前正在使用非阻塞的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()返回字节,然后在后续调用中返回没有字节,但在另一个后续调用中又返回一些字节?
  2. 基本上,如果我收到至少1个字节并最终 read()返回0,我可以相信服务器已完成响应我然后我知道我已经完成了,或者是否可能服务器很忙,如果我等待并继续尝试,可能会丢掉更多的字节?

    如果可以继续发送字节,即使read()返回0字节(之前成功读取之后),那么我不知道如何判断服务器何时完成与我交谈-fact我很困惑java.io. *风格的通信如何知道服务器何时“完成”。

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

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

    谢谢。

    奖金问题:

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

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

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

4 个答案:

答案 0 :(得分:4)

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

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

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

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

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

答案 1 :(得分:3)

  

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

阅读并遵循协议:

http://redis.io/topics/protocol

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

  

<强>回复

     

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

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

单行回复

     

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

     

...

     

多批量回复

     

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


  

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

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

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


  

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

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

答案 2 :(得分:2)

  

我正在使用标准   while(socket.read()&gt; 0){//追加   bytes} loop

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

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

答案 3 :(得分:2)

已经很久了,但是。 。 。

  

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

为了清楚起见,SocketChannels默认是阻止的;为了使它们成为非阻塞,必须明确地调用SocketChannel#configureBlocking(false)

我假设你做到了

  

我没有使用选择器

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

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

  

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

有合同 - &gt;如果选择器为读取操作选择了一个通道,则保证SocketChannel#read(ByteBuffer)的下一次调用返回&gt; 0 (假设ByteBuffer arg中有空间)

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

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