我想知道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后响应代码有效! (尽管如果您首先启动服务器,则绑定并连接到PUSH
和PULL
套接字需要时间)
以下是使用PUSH
和PULL
套接字的修改代码:
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)}")
}
}
答案 0 :(得分:1)
是的,有几种方式。
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是distributed-system设计中有趣的设计原则。
然而,最近的重新设计工作已经在API 4.2+中提出了实现ZeroMQ套接字接入点以实现线程安全的意愿(这违背了无任何共享的初始原则),因此,如果要朝着这个方向进行实验,你可能会到达领土,这是有效的,但代价是禅宗的衰落。
由于设计纯度,即使可能,也不应共享ZeroMQ套接字接入点。
如果你努力分离OOP问题,那么更好地为这样的类配备另一对单纯PUSH/PULL
- s,但你的头端只有这样的只读专用+只写 - 当一个“远程”(超出外部类抽象边界)ZeroMQ Socket-archetype FSA及其设置和性能调整和错误状态以及“远程”类将具有处理案例时,安排所有这些加上调解到本地ZeroMQ-socket的所有消息传输(主要是为两个头端(专用)类隔离和隐藏)。
在任何情况下,都可以通过适当的设计护理。
想法:
...
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://
管道不适合你。