使用ROUTER和DEALER进行ZeroMQ异步多线程处理

时间:2018-03-16 20:49:16

标签: java multithreading scala zeromq

我想知道ZeroMQ套接字是否只能读取或只写入。因为,在我看来,即使有异步/多线程示例,每个线程仍然使用recv-then-send循环。我遇到的问题是,我想让receiveMessage()从ZeroMQ套接字读取,sendMessage(msg)写入ZeroMQ套接字。但是这些方法中的每一个都将在另一个类中构造的单独线程中运行。这是我的代码(我使用Scala的jeromq):

trait ZmqProtocol extends Protocol {

  val context: ZContext = new ZContext(1)
  private val frontendSocket: ZMQ.Socket = context.createSocket(ZMQ.ROUTER)
  private val backendSocket: ZMQ.Socket = context.createSocket(ZMQ.DEALER)

  frontendSocket.bind("tcp://*:5555")
  backendSocket.bind("inproc://backend")


  new Thread(() => {

    println("Started receiving messages")
    // Connect backend to frontend via a proxy
    ZMQ.proxy(frontendSocket, backendSocket, null)

  }).start()


  override def receiveMessage(): (String, String) = {

    val inprocReadSocket: ZMQ.Socket = context.createSocket(ZMQ.DEALER)
    inprocReadSocket.connect("inproc://backend")

    //  The DEALER socket gives us the address envelope and message
    val msg = ZMsg.recvMsg(inprocReadSocket)

    // Message from client's REQ socket contains 3 frames: address + empty frame + request content
    // (payload)
    val address = msg.pop
    val emptyFrame = msg.pop
    val request = msg.pop

    assert(request != null)
    msg.destroy()

    println(s"RECEIVED: $request FROM: $address")

    (address.toString, request.toString)
  }

  override def sendMessage(address: String, response: String): Unit = {

    val inprocWriteSocket: ZMQ.Socket = context.createSocket(ZMQ.DEALER)
    inprocWriteSocket.connect("inproc://backend")

    val addressFrame = new ZFrame(address)
    val emptyFrame = new ZFrame("")
    val responseFrame = new ZFrame(response)

    addressFrame.send(inprocWriteSocket, ZFrame.REUSE + ZFrame.MORE)
    // Sending empty frame because client expects such constructed message
    emptyFrame.send(inprocWriteSocket, ZFrame.REUSE + ZFrame.MORE)
    responseFrame.send(inprocWriteSocket, ZFrame.REUSE)

    addressFrame.destroy()
    emptyFrame.destroy()
    responseFrame.destroy()
  }

}

以下是我将如何使用它:

class TrafficHandler(val requestQueue: LinkedBlockingQueue[(String, Message)],
                     val responseQueue: LinkedBlockingQueue[(String, String)])
  extends Protocol {

def startHandlingTraffic(): Unit = {

    new Thread(() => {

      while (true) {

        val (address, message) = receiveMessage()

        requestQueue.put((address, message))
      }
    }).start()

    new Thread(() => {
      while (true) {

        val (address, response) = responseQueue.take()
        sendMessage(address, response)
      }
    }).start()
  }

在调试过程中,我注意到我收到了消息,正确地从响应队列(并发阻塞队列)中获取了正确的目标地址,但是无声地发送了它。我在jeromq代码中潜水了一下,在我看来它与身份有关,因为outPipe为null。我猜它是因为我没有正确的recv-send循环。

编辑@ user3666197后响应代码有效! (尽管如果您首先启动服务器,则绑定并连接到PUSHPULL套接字需要时间)
以下是使用PUSHPULL套接字的修改代码:

trait ZmqProtocol extends Protocol {

  val context: ZContext = new ZContext(1)

  val frontendSocket: ZMQ.Socket = context.createSocket(ZMQ.ROUTER)
  frontendSocket.bind("tcp://*:5555")

  val requestQueueSocket: ZMQ.Socket = context.createSocket(ZMQ.PUSH)
  requestQueueSocket.bind("inproc://requestQueueSocket")

  val responseQueueSocket: ZMQ.Socket = context.createSocket(ZMQ.PULL)
  responseQueueSocket.bind("inproc://responseQueueSocket")

  val inprocRequestQueueSocket: ZMQ.Socket = context.createSocket(ZMQ.PULL)
  inprocRequestQueueSocket.connect("inproc://requestQueueSocket")

  val inprocResponseQueueSocket: ZMQ.Socket = context.createSocket(ZMQ.PUSH)
  inprocResponseQueueSocket.connect("inproc://responseQueueSocket")

  new Thread(() => {

    println("Started receiving messages")

    while (true) {

      val msg = ZMsg.recvMsg(frontendSocket)

      // Message from client's REQ socket contains 3 frames: address + empty frame + request content
      // (payload)
      val reqAddress = msg.pop
      val emptyFrame = msg.pop
      val reqPayload = msg.pop

      assert(reqPayload != null)
      msg.destroy()

      println(s"RECEIVED: $reqPayload FROM: $reqAddress")

      requestQueueSocket.send(s"$reqAddress;$reqPayload")

      val responseMessage = new String(responseQueueSocket.recv(0))
      val respMessageSplit = responseMessage.split(";")

      val respAddress = respMessageSplit(0)
      val respPayload = respMessageSplit(1)

      val array = new BigInteger(respAddress, 16).toByteArray
      val respAddressFrame = new ZFrame(array)
      val respEmptyFrame = new ZFrame("")
      val respPayloadFrame = new ZFrame(respPayload)

      respAddressFrame.send(frontendSocket, ZFrame.REUSE + ZFrame.MORE)
      // Sending empty frame because client expects such constructed message
      respEmptyFrame.send(frontendSocket, ZFrame.REUSE + ZFrame.MORE)
      respPayloadFrame.send(frontendSocket, ZFrame.REUSE)

      respAddressFrame.destroy()
      respEmptyFrame.destroy()
      respPayloadFrame.destroy()

    }

  }).start()


  override def receiveMessage(): (String, String) = {

    val message = new String(inprocRequestQueueSocket.recv(0))
    val messageSplit = message.split(";")

    val address = messageSplit(0)
    val payload = messageSplit(1)

    (address, payload)
  }

  override def sendMessage(address: String, response: String): Unit = {

    inprocResponseQueueSocket.send(s"$address;$response")
  }
}

如果需要,这是客户:

trait ZmqClientProtocol extends ClientProtocol {

  val context: ZMQ.Context = ZMQ.context(1)
  val socket: ZMQ.Socket = context.socket(ZMQ.REQ)

  println("Connecting to server")
  socket.connect("tcp://localhost:5555")

  override protected def send(message: String): String = {

    //  Ensure that the last byte of message is 0 because server is expecting a 0-terminated string
    val request = message.getBytes()

    // Send the message
    println(s"Sending request $request")
    socket.send(request, 0)

    //  Get the reply.
    val reply = socket.recv(0)

    new String(s"$message=${new String(reply)}")
  }
}

1 个答案:

答案 0 :(得分:1)

ZeroMQ套接字有没有办法只读或只写?

是的,有几种方式。

a)使用串联的单纯形原型: PUSH/PULL 写入, PULL/PUSH 读取
b)使用串联的单纯形原型: (X)PUB/(X)SUB 写入和 (X)SUB/(X)PUB 读取

...仍然使用 .recv() -then- .send() 循环。

嗯,这个观察更多地与实际的socket-archetype有关,其中一些确实需要强制性的两步(在内部FSA内部硬连线) .recv() 的排序 - 的 .send() --...

...但是这些方法中的每一个都将在单独的线程

中运行

嗯,挑战开始了:ZeroMQ自启动以来主要设计为零共享,以促进性能和独立性。 Zen-of-Zero是设计中有趣的设计原则。

然而,最近的重新设计工作已经在API 4.2+中提出了实现ZeroMQ套接字接入点以实现线程安全的意愿(这违背了无任何共享的初始原则),因此,如果要朝着这个方向进行实验,你可能会到达领土,这是有效的,但代价是禅宗的衰落。

由于设计纯度,即使可能,也不应共享ZeroMQ套接字接入点。

如果你努力分离OOP问题,那么更好地为这样的类配备另一对单纯PUSH/PULL - s,但你的头端只有这样的只读专用+只写 - 当一个“远程”(超出外部类抽象边界)ZeroMQ Socket-archetype FSA及其设置和性能调整和错误状态以及“远程”类将具有处理案例时,安排所有这些加上调解到本地ZeroMQ-socket的所有消息传输(主要是为两个头端(专用)类隔离和隐藏)。

在任何情况下,都可以通过适当的设计护理。

ZeroMQ资源不是任何便宜的可组合/一次性垃圾

想法:

...
override def sendMessage( address:  String,
                          response: String
                          ): Unit = {

             val inprocWriteSocket: ZMQ.Socket  = context.createSocket( ZMQ.DEALER )
                 inprocWriteSocket.connect( "inproc://backend" )
                 ...

在源代码中似乎很容易,但是忽略了实际的设置开销,并且还应该尊重这样一个事实:没有套接字( inproc:// -transport-class是一个特例) RTO(Ready-To-Operate)在Context()内实例化的微秒内,在与远程交易对手进行所有握手后,完全.connect() - 和RTO-ed就越少,因此最佳设置事先很好地保留SIG / MSG基础设施,并将其作为半持久性通信层保持最佳状态,而不是任何临时/即时启动的可组合/一次性......(资源生态学)

inproc:// -transport-class在API 4.x之前还有一个要求:

  

连接插座

  使用带有 zmq_connect() 传输的 inproc:// 将套接字连接到对等地址时,端点应被解释为标识名称的任意字符串连接至。在4.0版之前,必须先通过将名称分配给与正在连接的套接字相同的ØMQ上下文中的至少一个套接字来创建名称。从版本4.0开始, zmq_bind() zmq_connect() 的顺序与 tcp:// 无关运输类型。

因此,如果您的部署不确定实际的localhost API版本,请注意强制执行.bind() / .connect()的正确顺序,否则 inproc:// 管道不适合你。