首页 > 解决方案 > Java NIO OP_WRITE 事件触发 3 次

问题描述

我正在使用 Java NIO API 来实现一个简单的非阻塞 Web 服务器。我观察到的是,OP_WRITE每个套接字连接注册一次,会触发它三次:

while(true) {
  // ...

  // Handle SelectionKey.OP_ACCEPT
  if (key.isAcceptable) {
    val sch = key.channel.asInstanceOf[ServerSocketChannel]
    val ch  = sch.accept
    ch.configureBlocking(false)
    ch.register(this.sel, SelectionKey.OP_WRITE)
  }

  // Handle SelectionKey.OP_WRITE
  if (key.isWritable) {
    val ch = key.channel.asInstanceOf[SocketChannel]
    ch.write(writeByteBuffer.duplicate())
  }
}

完整代码

package zion

import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.nio.channels.{
  SelectionKey,
  Selector,
  ServerSocketChannel,
  SocketChannel
}
object HelloNIO {
  val response: Array[Byte] =
    HelloResponse.ok("HelloNIO.scala\n").getBytes()
  val writeByteBuffer: ByteBuffer = ByteBuffer.wrap(response)
  val readByteBuffer: ByteBuffer  = ByteBuffer.allocateDirect(1024 * 2)

  val PORT = 8081

  def main(args: Array[String]): Unit = {

    // Setup
    val sel                 = Selector.open
    val address             = new InetSocketAddress(PORT)
    val serverSocketChannel = ServerSocketChannel.open()
    serverSocketChannel.socket.bind(address)
    serverSocketChannel.configureBlocking(false)
    serverSocketChannel.register(sel, SelectionKey.OP_ACCEPT)
    new Thread(new SocketProcessor(sel)).start()
  }

  class SocketProcessor(val sel: Selector) extends Runnable {
    override def run(): Unit = {
      while (true) {
        this.sel.select()
        val keys     = this.sel.selectedKeys
        val iterator = keys.iterator
        while (iterator.hasNext) {
          val key = iterator.next()

          // Handle SelectionKey.OP_ACCEPT
          if (key.isAcceptable) {
            val sch = key.channel.asInstanceOf[ServerSocketChannel]
            val ch  = sch.accept
            ch.configureBlocking(false)
            ch.register(this.sel, SelectionKey.OP_WRITE) // Registering only once
          }

          // Handle SelectionKey.OP_WRITE
          if (key.isWritable) { // Key is writable 3 times for each HTTP request.
            val ch = key.channel.asInstanceOf[SocketChannel]
            ch.write(writeByteBuffer.duplicate())
          }
        }
        iterator.remove()
      }
    }
  }
}

标签: scalahttpnettynio

解决方案


OP_WRITE 表示通道已准备好写入。从java文档复制:

“假设选择键的兴趣集在选择操作开始时包含OP_WRITE。如果选择器检测到相应的通道已准备好写入,已被远程关闭以进一步写入,或者有错误未决,那么它将添加OP_WRITE 到密钥的就绪集,并将密钥添加到其选定密钥集。”


推荐阅读