我使用 ZMQ_PUB
看到一种奇怪的行为。
我有一个生产者.connect()
- s到不同的流程,.bind()
在ZMQ_SUB
套接字上。
订阅者.bind()
,发布商.connect()
- s。
当一个生产者启动时,它会创建一个ZMQ_PUB
套接字并将.connect()
创建到不同的进程。然后它会立即开始定期发送消息。
正如预期的那样,如果没有连接的用户,它将丢弃所有消息,直到用户启动。
当用户启动时,流程正常,然后从该时刻开始接收消息。
现在,问题是:
所以我看到的是,当订阅者关闭时,生产者将所有消息排入队列。一旦套接字重新连接,由于订户进程重新启动,它就会发送所有排队的消息。
据我所知here,发布商应在没有关联订阅者时删除所有已发送的消息:
ZeroMQ examples
“发布商没有关联的订阅者,那么它只会丢弃所有消息。”
为什么会这样?
顺便说一句,我在Linux上使用C ++来进行这些测试。
我尝试在绑定时在订阅者上设置不同的身份,但它不起作用。发布者仍会将消息排入队列,并在用户重新启动时将其全部传递。
提前致谢,
路易斯
更新
重要更新!!!!!
在发布此问题之前,我尝试了不同的解决方案。一个是将ZMQ_LINGER
设置为0,这不起作用。
我添加了ZMQ:IMMEDIATE
,但它确实有效,但我刚发现仅ZMQ:IMMEDIATE
不起作用。它还需要ZMQ_LINGER
。
Luis Rojas 3 hours ago
更新 根据要求,我将添加一些简单的测试用例来表明我的观点。 一个是简单订阅者,它在命令行上运行并接收uri绑定的位置,例如:
$ ./sub tcp://127.0.0.1:50001
另一个是发布者,它接收要连接的uris列表,例如:
./ pub tcp://127.0.0.1:50001 tcp://127.0.0.1:50002
订阅者最多接收5条消息,然后关闭套接字并退出。我们可以在wireshark上看到FIN / ACK的交换,两种方式,以及套接字如何移动到TIME_WAIT状态。然后,发布者开始发送SYN,尝试重新连接(探测ZMQ_PUB知道该连接已关闭)
我明确地没有取消订阅套接字,只是关闭它。在我看来,如果套接字关闭,发布者应该自动结束该连接的任何订阅。
所以我看到的是:我启动订阅者(一个或多个),我启动发布者,开始发送消息。订阅者收到5条消息并结束。在此期间,发布者继续发送消息,没有连接的订阅者。我重新启动订阅者,并立即收到几条消息,因为它们在发布者端排队。我认为这些排队的消息会破坏发布/订阅模式,其中消息应仅传递给已连接的订阅者。如果susbcriber关闭了连接,则应删除该订户的消息。更重要的是,当用户重新启动时,它可能决定订阅其他消息,但它仍将接收那些在同一端口绑定的“之前的版本”订阅的消息。
我的建议是ZMQ_PUB(在连接模式下),当检测到套接字断开时,应该清除该套接字上的所有订阅,直到它重新连接并且新订户决定重新订阅。
我为语言错误道歉,但英语不是我的母语。
Pub的代码:
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <unistd.h>
#include <string>
#include <zeromq/zmq.hpp>
int main( int argc, char *argv[] )
{
if ( argc < 2 )
{
fprintf( stderr, "Usage : %s <remoteUri1> [remoteUri2...]\n",
basename( argv[0] ) );
exit ( EXIT_FAILURE );
}
std::string pLocalUri( argv[1] );
zmq::context_t localContext( 1 );
zmq::socket_t *pSocket = new zmq::socket_t( localContext, ZMQ_PUB );
if ( NULL == pSocket )
{
fprintf( stderr, "Couldn't create socket. Aborting...\n" );
exit ( EXIT_FAILURE );
}
int i;
try
{
for ( i = 1; i < argc; i++ )
{
printf( "Connecting to [%s]\n", argv[i] );
{
pSocket->connect( argv[i] );
}
}
}
catch( ... )
{
fprintf( stderr, "Couldn't connect socket to %s. Aborting...\n", argv[i] );
exit ( EXIT_FAILURE );
}
printf( "Publisher Up and running... sending messages\n" );
fflush(NULL);
int msgCounter = 0;
do
{
try
{
char msgBuffer[1024];
sprintf( msgBuffer, "Message #%d", msgCounter++ );
zmq::message_t outTask( msgBuffer, strlen( msgBuffer ) + 1 );
printf("Sending message [%s]\n", msgBuffer );
pSocket->send ( outTask );
sleep( 1 );
}
catch( ... )
{
fprintf( stderr, "Some unknown error ocurred. Aborting...\n" );
exit ( EXIT_FAILURE );
}
}
while ( true );
exit ( EXIT_SUCCESS );
}
子代码
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <unistd.h>
#include <string>
#include <zeromq/zmq.hpp>
int main( int argc, char *argv[] )
{
if ( argc != 2 )
{
fprintf( stderr, "Usage : %s <localUri>\n", basename( argv[0] ) );
exit ( EXIT_FAILURE );
}
std::string pLocalUri( argv[1] );
zmq::context_t localContext( 1 );
zmq::socket_t *pSocket = new zmq::socket_t( localContext, ZMQ_SUB );
if ( NULL == pSocket )
{
fprintf( stderr, "Couldn't create socket. Aborting...\n" );
exit ( EXIT_FAILURE );
}
try
{
pSocket->setsockopt( ZMQ_SUBSCRIBE, "", 0 );
pSocket->bind( pLocalUri.c_str() );
}
catch( ... )
{
fprintf( stderr, "Couldn't bind socket. Aborting...\n" );
exit ( EXIT_FAILURE );
}
int msgCounter = 0;
printf( "Subscriber Up and running... waiting for messages\n" );
fflush( NULL );
do
{
try
{
zmq::message_t inTask;
pSocket->recv ( &inTask );
printf( "Message received : [%s]\n", inTask.data() );
fflush( NULL );
msgCounter++;
}
catch( ... )
{
fprintf( stderr, "Some unknown error ocurred. Aborting...\n" );
exit ( EXIT_FAILURE );
}
}
while ( msgCounter < 5 );
// pSocket->setsockopt( ZMQ_UNSUBSCRIBE, "", 0 ); NOT UNSUBSCRIBING
pSocket->close();
exit ( EXIT_SUCCESS );
}
答案 0 :(得分:2)
因为SUB
实际上仍然是连接的(不是&#34;断开&#34;足够)。
是的,可能会令人惊讶,但杀死 SUB
-process,无论是.bind()
还是.connect()
- 在插座的传输介质的附加侧,并不意味着,I / O泵的有限状态机已经移动了#34;进入断开状态。
鉴于此, PUB
-side没有其他选择,只能考虑 SUB
- 仍然存在并且已连接(即使这个过程在PUB
- 侧的视线之外被默默地杀死了,并且对于这样的&#34;分布式&#34; -state有一个ZeroMQ协议定义的行为(PUB
({1}} - SUB
方面仍然认为可以公平生活(但可能只是暂时存在一些间接问题)的所有临时信息某个地方较低,在传输I / O级别或某些远程CPU资源starvations或并发引入的瞬时间歇{local | remote}阻塞状态等。
如果你暗杀 PUB
- 代理似乎更优雅(使用归零SUB
+足够ZMQ_LINGER
在socket-resource实例上) .close()
-side将识别&#34;分布式&#34;系统范围的有限状态自动机转换为确实&#34 ; DISCONNECT&#34; -ed状态和应有的行为改变将发生在&#34; distributed-FSA&#34;的PUB
侧,而不是为此存储任何消息&#34;明显&#34;确实&#34; DISCONNECT&#34; -ed PUB
- 正是文档所说的。
&#34;分布式FSA&#34;有一种相当薄弱的手段来识别状态变化事件&超越它的 localhost contols的视野。 SUB
- 一个远程流程,它实现了&#34; distributed-FSA&#34;是一个毁灭性的事件,而不是一个如何保持系统工作的方法。这种外部风险的一个很好的选择可能是
哦,是的,确实很复杂。这正是ZeroMQ为我们解决这个问题的原因所在,我们可以自由地享受基于这些(已经解决的)低级复杂性的设计应用程序架构。
想象一下隐藏在引擎盖下的东西,想象一下只有一对简单的串联FSA-FSA - 正是 KILL
实例试图处理的对我们处于最简单的1:1 .Context()
场景中,用例PUB/SUB
- KILL
侧的所有子FSA-s,没有尝试向SUB
方面表达意图。甚至TCP协议(同时居住在PUB
- 侧和PUB
- 侧)也有一些状态转换,从[ SUB
]到[ ESTABLISHED
]州。
(为了清楚起见,仅描述了TCP协议FSA)
<强> CLOSED
强>侧的:
PUB
实例的行为FSA:
<强> .socket( .. )
强>侧的:
(由nanomsg提供)。
答案 1 :(得分:1)
Bind and Connect虽然无动于衷,但在这里有特定的含义。
选项1:
将代码更改为这种方式,没有问题:
bind
到地址connect
到该地址'如果您绑定订阅者然后中断它,则发布者无法知道订阅者是未绑定的,因此它将消息排入绑定端口,并且当您在同一端口上再次重新启动时,排队的消息将被抽干了。
选项2:
但是如果你想按自己的方式去做,你需要做以下事情:
SIGINT
)
unsubscribe
主题close
子插座<强>更新强>
关于身份点,不要假设设置标识将唯一地标识连接。如果留给zeromq,它将使用唯一的任意数字分配传入连接的标识。
一般而言,身份不会用于回复客户。它们用于在使用ROUTER
套接字的情况下响应客户端。
'Coz ROUTERsockets是异步的,因为REQ / REP是同步的。在Async中,我们需要知道我们回复的对象。它可以是n / w地址或随机数或uuid等。
<强>更新强>
我不认为这与zeromq有关,因为在整个指南中PUB / SUB的解释方式是Publisher通常是静态的(服务器并绑定到端口),订阅者一路走来(客户端)连接到端口。)
还有另一种选择完全符合您的要求
ZMQ_IMMEDIATE
或ZMQ_DELAY_ATTACH_ON_CONNECT
在发布者上设置上述套接字选项时,如果消息没有活动连接,则不会让消息排队。