简体   繁体   English

如果脚本从 CLI/shell 但不是从 APACHE/web 请求启动,PHP 上的 ZeroMQ 发送/接收有效

[英]ZeroMQ send/receive on PHP works if script launched from CLI/shell but not from APACHE/web request

I'm testing ZeroMQ for PHP.我正在为 PHP 测试 ZeroMQ。 My goal is to send messages to a Python script.我的目标是将消息发送到 Python 脚本。 Everything works fine if i launch my transmission script from PHP cli如果我从 PHP cli 启动传输脚本,一切正常

php /path/to/myscript.php

while it fails if it's a web request.如果它是 web 请求,则会失败。 I've tried executing the server script from PHP cli as above (which seems to be the more logical way) and with a web request.我已经尝试从上面的 PHP cli 执行服务器脚本(这似乎是更合乎逻辑的方式)并使用 web 请求。

I've got a Centos 7 server with PHP 7.2 and ZeroMQ 1.1.3 installed through PECL install.我通过 PECL 安装安装了 PHP 7.2 和 ZeroMQ 1.1.3 的 Centos 7 服务器。

I even tried launching the above command with shell_exec/exec inside the client script but it doesn't work.我什至尝试在客户端脚本中使用 shell_exec/exec 启动上述命令,但它不起作用。 Connection works fine, but it doesn't send nor receive.连接工作正常,但它不发送也不接收。

Client code:客户端代码:

$context = new ZMQContext();

//  Socket to talk to server
echo "Connecting to hello world server...\n";
$requester = new ZMQSocket($context, ZMQ::SOCKET_REQ);
$currentObject = $requester->connect("tcp://localhost:5555");


for ($request_nbr = 0; $request_nbr != 10; $request_nbr++) {
    printf ("Sending request %d...\n", $request_nbr);
    $risSend = $requester->send("Hello", ZMQ::MODE_NOBLOCK);
    print_r($risSend);
    $reply = $requester->recv();
    printf ("Received reply %d: [%s]\n", $request_nbr, $reply);
}

Server Code:服务器代码:

$context = new ZMQContext(1);

//  Socket to talk to clients
$responder = new ZMQSocket($context, ZMQ::SOCKET_REP);
$responder->bind("tcp://*:5555");

while (true) {
    //  Wait for next request from client
    $request = $responder->recv();

    printf ("Received request: [%s]\n", $request);

    //  Send reply back to client
    $responder->send("World");
}

The browser gets stuck, without any error.浏览器卡住了,没有任何错误。 Even using a timeout it reaches the limit and exits but I can't get any error message.即使使用超时,它也会达到限制并退出,但我无法收到任何错误消息。

OBSERVATION : The browser gets stuck, without any error.观察浏览器卡住了,没有任何错误。

This is pretty legal state.这是非常合法的 state。 For it to happen, it is quite enough to "miss" the arrival of the first REQ -side-already dispatched request and due to a pleasure do depend on a distributed-Finite-State-Automaton, we fall into an unsalvageable dead-lock, where the REQ -side waits for an answer, that will never arrive (see next) and the REP -side waits for a request, that will never arrive (see the REQ -side already waiting ) and such a state remains forever that.为了让它发生,“错过”第一个REQ端已经发送的请求的到来就足够了,并且由于很高兴确实依赖于分布式有限状态自动机,我们陷入了无法挽救的死锁,其中REQ端等待一个永远不会到达的答案(参见下一个),而REP端等待一个永远不会到达的请求(参见REQ端已经在等待),这样的 state 永远保持不变。


A best next step:最好的下一步:

In case one has never worked with ZeroMQ,如果从未使用过 ZeroMQ,
or have never met the concept of the art of Zen-of-Zero ,或者从来没有遇到过零之禅的艺术概念,
one may here enjoy to first look at "ZeroMQ Principles in less than Five Seconds " before diving into further details在深入了解更多细节之前,您可能会喜欢先看看“ZeroMQ原则在不到 5 秒内


Start开始
with unconditionally working archetypes - a pair of PUSH / PULL simplex-channels, that do not require a dFSA-two-step of REQ-REP-REQ-REP-REQ-REP-...-{deadlock} ... a principally unavoidable terminal state, about which one is just never sure when it happens, but it will... at some later time:o)具有无条件工作的原型 - 一对PUSH / PULL单纯形通道,不需要REQ-REP-REQ-REP-REQ-REP-...-{deadlock}的 dFSA 两步...不可避免的终端 state,关于哪一个永远不确定它何时会发生,但它会......在以后的某个时间:o)

Next,下一个,
may increase a robustness of the message-flow, using zmq_setsockopt( ZMQ_IMMEDIATE, 1 ) that avoids moving messages onto incomplete connections between / among peers.可以使用zmq_setsockopt( ZMQ_IMMEDIATE, 1 )来增加消息流的稳健性,以避免将消息移动到对等点之间的不完整连接上。

Always总是
prefer non-blocking forms of .recv() -methods, best with a pre-test of a message-presence with a .poll() -method.更喜欢.recv()方法的非阻塞 forms,最好使用.poll()方法对消息存在进行预测试。 Poller -class, while available in many language-bindings is not always as handy and as flexible as using explicit .poll() -method directly on a Socket -instance. Poller类虽然在许多语言绑定中都可用,但并不总是像直接在Socket实例上使用显式.poll()方法那样方便和灵活。

Also feel free to read more about fine-tuning the ZeroMQ tools and other implications of the Art of the Zen-of-Zero here .也可以随意阅读更多关于微调 ZeroMQ 工具和零之艺术的其他含义的信息


A Server-side mock-up: As a { PASS |服务器端模型:作为 { PASS | FAIL }-proof of .send()---.recv() -delivery chain works? FAIL } - .send()---.recv()链有效吗?

<?php                                      /* Create new PUSH-ing end */
$aCTX   = new ZMQContext();
try {                                      /* Try: things may turn wreck havoc */

      $PUSHer = $aCTX->getSocket(, ZMQ::SOCKET_PUSH );
      echo "POSACK'd: .getSocket() was made\n";
      }
catch ( ZMQSocketException $e ){
      echo "  NACK'd: I told you ...\n";   /* Handle with care ... */
      if ( $e->getCode() === ZMQ::ERR_... ) {
            echo " - Got ERR_..., read ZeroMQ API documentation for details\n";
        } else {
            die( " - Get ERR: " . $e->getMessage() );
        }
      }
try {                                      /* Try: things may turn wreck havoc */
      $PUSHer->bind( "tcp://A.B.C.D:NNN" ); /* IP address to .connect() */
      echo "POSACK'd: .bind() was made\n";
      }
catch ( ZMQSocketException $e ){
      echo "  NACK'd: I told you ...\n";   /* Handle with care ... */
      if ( $e->getCode() === ZMQ::ERR_... ) {
            echo " - Got ERR_..., read ZeroMQ API documentation for details\n";
        } else {
            die( " - Get ERR: " . $e->getMessage() );
        }
      }

$retries = 1234567;

do {                                       /* Start a loop */
    try {                                  /* Try: to PUSH.send() */
            echo "Trying to send a message #" . ( 1234568 - $retries ) . "\n";
            $PUSHer->send( "This is a message", ZMQ::MODE_DONTWAIT );
            echo "POSACK'd: PUSHer.send() was made\n";
        }
    } catch ( ZMQSocketException $e ) {
        echo "  NACK'd: I told you ...\n"; /* Handle with care ... */
        if ( $e->getCode() === ZMQ::ERR_... ) {
            echo " - Got ERR_..., read ZeroMQ API documentation for details\n";
        } else {                           /* For all ERR_... states */
            die( " - Got ERR_...: " . $e->getMessage() );
        }
    }
 /* --------------------------------------------------------------------
    Here one may add an attempt to .recv( $PULLer, ZMQ::MODE_DONTWAIT );
             and test for a non-empty string returned
    -------------------------------------------------------------------- */
    usleep( 1 );                           /* Sleep a bit between operations */
} while ( --$retries );
?>

Client-side mock-up , to test the PUSH-er lives and .send() -s客户端模型,用于测试 PUSH-er 生命和.send() -s

import time, datetime, zmq; print( "Thissssss Sssssssssssssssssssssssssssssssssssssssnake uses ZeroMQ ver:{0:}".format( zmq.__version__ ) )

aCtx = zmq.Context()
aPull= aCtx.Socket( zmq.PULL )
aPull.setsockopt(   zmq.LINGER, 0 )         # always ... be explicit
aPull_address2c = "tcp://A.B.C.D:NNN"

M0 = "{0:} try a .connect( {1:} ), if it gets to PUSH-er side"
M1 = "{0:} try a .recv(), if it gets any message"
M2 = "{0:} got a .recv()->[[[ {1:} ]]]"
M3 = "{0:} EXC'd           will gracefully release resources and terminate..."
M4 = "{0:} did"

try:
    print( M0.format( datetime.datetime.isoformat( datetime.datetime.now() ),
                      aPull_address2c
                      )
           )
    aPull.connect( aPull_address2c );

    while True:
        print( M1.format( datetime.datetime.isoformat( datetime.datetime.now() ) )
        m = aPull.recv( zmq.NOBLOCK )       # always ... avoid blocking waits
        if ( len( m ) > 0 ):
             print( M2.format( datetime.datetime.isoformat( datetime.datetime.now() ),
                               str( m )     # always ... fused to str()
                               )
                    )
             time.sleep( 5 )
        else:
             time.sleep( 1 )

        pass

        ################################################################
        # Here one may add an attempt to aPush.send( M4, zmq.NOBLOCK )
        #          and test if the reverse path aPush->$PULLer goes well
        ################################################################

except:
    print( M3.format( datetime.datetime.isoformat( datetime.datetime.now() ) )

finally:
    aPull.close()                           # always ... be explicit
    aCtx.term()                             # always ... be explicit

    print( M4.format( datetime.datetime.isoformat( datetime.datetime.now() ) )

Ok, i finally found the solution.好的,我终于找到了解决方案。 Thanks to @user3666197 i managed to get an error.感谢@user3666197,我设法得到了一个错误。 And it was a "permission denied" error.这是一个“权限被拒绝”错误。

With CentOS (and probably all linux sytems with SELinux) you gotta add a rule for the port used by ZMQ for the webserver .使用 CentOS(可能还有所有带有 SELinux 的 linux 系统),您必须为 ZMQ用于 webserver的端口添加一条规则。

Example:例子:

semanage port -a -t http_port_t -p tcp 5555

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM