swoole-src
swoole-src copied to clipboard
WebSocket: After calling $webSockerServer->push() from within onMessage event, the WebSocket connection closes by calling onClose event.
Please answer these questions before submitting your issue.
- What did you do? If possible, provide a simple script for reproducing the error.
$this->server->on('Open', function($server, $request) {
echo "server: handshake success with fd{$request->fd}\n";
include_once __DIR__ . '/Controllers/WebSocketController.php';
// $server->tick(1000, function() use ($server, $request) {
// $server->push($request->fd, json_encode(["hello", time()]));
// });
});
$this->server->on('Message', function($webSocketServer, $frame) {
if ($frame == false) {
echo 'errorCode: ' . swoole_last_error() . "\n";
} else if ($frame->opcode == 0x08) {
echo "Close frame received: Code {$frame->code} Reason {$frame->reason}\n";
} else {
$i=0;
while (!$frame->finish) {
if ($i > 4000) {
$server->disconnect($frame->fd, SWOOLE_WEBSOCKET_CLOSE_NORMAL, "Frame Finish Time Exceeded.");
} else {
$i++;
echo "Frame is not Finished".PHP_EOL;
continue;
}
}
$sw_websocket_controller = new WebSocketController($webSocketServer, $frame, $this->dbConnectionPools);
$response = $sw_websocket_controller->handle();
$webSocketServer->push($frame->fd, json_encode($response));
}
});
$this->server->on('Close', function($server, $fd) {
echo "client {$fd} closed\n";
});
$this->server->on('Disconnect', function(Server $server, int $fd) {
echo "connection disconnect: {$fd}\n";
});
Below is WebSocket Controller Class as included with include_once DIR . '/Controllers/WebSocketController.php' in onOpen event:
use Swoole\Coroutine;
use Al\Swow\Context;
use DB\DBFacade;
use Swoole\Runtime;
use Swoole\Http\Request;
class WebSocketController
{
protected $webSocketServer;
protected $frame;
protected $dbConnectionPools;
protected $postgresDbKey = 'pg';
protected $mySqlDbKey = 'mysql';
public function __construct($webSocketServer, $frame, $dbConnectionPools, $postgresDbKey = 'pg', $mySqlDbKey = 'mysql'){
$this->webSocketServer = $webSocketServer;
$this->frame = $frame;
$this->dbConnectionPools = $dbConnectionPools;
$this->postgresDbKey = $postgresDbKey;
$this->mySqlDbKey = $mySqlDbKey;
}
public function handle() {
if ($this->frame->data == 'reload-code') {
echo "Reloading Code Changes (by Reloading All Workers)".PHP_EOL;
$this->webSocketServer->reload();
} else {
echo "receive from {$this->frame->fd}:{$this->frame->data},opcode:{$this->frame->opcode},fin:{}\n";
return array('data'=>"You sent {$this->frame->data} to the server");
}
}
}
Note: i am using WebsocketClient.php (and websocketclient_examples.php ) as provided in repo. 2. What did you expect to see?
I expect not to see the message "client 1 closed" as echoed by the onClose event handler. I am new to WebSocket Programming so i think that WebSocket connection remains opened even after something is pushed from the server to the browser / client.
- What did you see instead?
The message "client 1 closed" (1 is $fd here) as echoed by the onClose event handler The fd '1' here is also incremented, everytime i re-send a frame using WebSocket client.
- What version of Swoole are you using (show your
php --ri swoole)?
swoole
Swoole => enabled Author => Swoole Team [email protected] Version => 5.1.2 Built => May 26 2024 22:51:27 coroutine => enabled with thread context debug => enabled trace_log => enabled epoll => enabled eventfd => enabled signalfd => enabled cpu_affinity => enabled spinlock => enabled rwlock => enabled sockets => enabled openssl => OpenSSL 3.0.2 15 Mar 2022 dtls => enabled http2 => enabled json => enabled curl-native => enabled c-ares => 1.18.1 zlib => 1.2.11 mutex_timedlock => enabled pthread_barrier => enabled futex => enabled mysqlnd => enabled async_redis => enabled coroutine_pgsql => enabled
Directive => Local Value => Master Value swoole.enable_coroutine => On => On swoole.enable_library => On => On swoole.enable_fiber_mock => Off => Off swoole.enable_preemptive_scheduler => On => On swoole.display_errors => On => On swoole.use_shortname => On => On swoole.unixsock_buffer_size => 8388608 => 8388608
- What is your machine environment used (show your
uname -a&php -v&gcc -v) ?
Linux HP-Laptop 5.17.5-76051705-generic #202204271406~1651504840~20.04~63e51bd-Ubuntu SMP PREEMPT Wed Ma x86_64 x86_64 x86_64 GNU/Linux
PHP 8.3.8 (cli) (built: Jun 6 2024 16:58:27) (NTS) Copyright (c) The PHP Group Zend Engine v4.3.8, Copyright (c) Zend Technologies with Zend OPcache v8.3.8, Copyright (c), by Zend Technologies
COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 11.4.0-1ubuntu1~22.04' --with-bugurl=file:///usr/share/doc/gcc-11/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-11 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-11-XeT9lY/gcc-11-11.4.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-11-XeT9lY/gcc-11-11.4.0/debian/tmp-gcn/usr --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2 Thread model: posix Supported LTO compression algorithms: zlib zstd gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)
You need to check if the third parameter $reactorId in the onClose event is -1 to determine if the connection was closed by the client or the server.
@NathanFreeman I echoed third parameter like below:
$this->server->on('Close', function($server, $fd, $reactorId) {
echo "client {$fd} closed\n ReactorId:{$reactorId}";
});
and got:
client 1 closed ReactorId:2
In order to keep a connection opened, do i need to push() (stream) some data continously from server-side (or from the client-side) by puting push() (or send() ) inside a loop ? If the answer is "Yes" then what i will take from this is that push() only sends the data (from the server to the client) and then websocket connection is closed after every push.
$reactorId greater than 0 indicates that the client has actively closed the connection. You need to check the client's code.
@NathanFreeman I think that the issue is in int $flags of the Server::pack() function.
For the (WebSocket's) client-side, I am using this gist (with swoole version 5.1.2)
https://gist.github.com/NHZEX/946275e9b32a832d579a36c5d3d0b7fc
If you just look at this code, the $this->socket->send() function is passing four parameters to Server::pack() (as shown in function below at the end, so initially it was not working). I removed the fourth parameter in order to make the code work (by conforming to the definition of Server::pack(), as per documentation), however i found two things that;
A) That, the third parameter (which is int $flags) work only for value "true", or non-zero value, but not for "false" or 0 ) which means i can not set $flag a value other than "WEBSOCKET_FLAG_FIN" (same is also on server-side) and ... B) That, the client connection is closed after push() (as reported in this Issue)
So it looks like that the problem lies in how you deal internally with the value of the argument int $flags (as passed in the function Server::pack() to make a Frame object).
//Original Function
public function send($data, $type = 'text', $masked = false)
{
switch ($type) {
case 'text':
$_type = WEBSOCKET_OPCODE_TEXT;
break;
case 'binary':
case 'bin':
$_type = WEBSOCKET_OPCODE_BINARY;
break;
case 'ping':
$_type = WEBSOCKET_OPCODE_PING;
break;
default:
return false;
}
return $this->socket->send(Server::pack($data, $_type, true, $masked));
}
In code above, i changed ...
// see, four parameters (may be because it is code targeting older version of swoole) return $this->socket->send(Server::pack($data, $_type, true, $masked));
to ...
// Three parameters instead of four return $this->socket->send(Server::pack($data, $_type, true));
It works with only "true" and "non-zero" values not with "false" and 0. (and parhaps passing the "true" and "non-zero" value also disconnects the connection, just as you say that disconnection is coming from client-side of code; May be this is because when we pass "true" or "non-zero" value then it is same as the default value of int $flags (which is "WEBSOCKET_FLAG_FIN"), whereas if i try to pass a Zero value or a boolean false, i get error as it takes the flow of code into ....
if ($data === false) { } , condition inside function recv() of this gist.
So, again the question is:
How to use server::pack(), and push() (with new type of paramater "int $flags") such that the WebSocker connection do not auto disconnect ?
Any update on this issue ? Please, also see my last reply.
@NathanFreeman @matyhtf @twose
@NathanFreeman
I have found that there comes an issue in your client-side recv() function of socket object ...
$this->socket->recv();
When server-side pushes all the messages, the function recv() returns not only "false" (false is just fine, as per documentation) but it also issues a warning as below:
PHP Warning: Swoole\Client::recv(): recv() failed, Error: Resource temporarily unavailable[11] in /var/www/html/swoole-prac/websocketclient/wbsocketclient.php on line 87"
And, it also gives a warning that there is no property $errMsg on socket object.
Reference to Client-side Code: Please, see on line 85 of this gist which i am using now.
Same problem after update from v4.8.12 to 5.1.3, push($fd, $data) works every other time.
@XJIOP
Way around:
-
I think
push()on server-side is working fine (i am using 5.1.2, i have not tested on 5.1.3). The issue of this PHP Warning and Error Code 11 occurs both on Swoole and OpenSwoole (i tested on both). -
In order to avoid disconnection, we need to put the function
recv()which contains the command$this->socket->recv();inside awhile(true) { ... }loop (in its consumer side code). -
In order to ignore / suppress the PHP Warning on your TCP-Client code use the sign
@just before$this->socket->recv();to make it as@$this->socket->recv();Note: This PHP warning which comes only when there is no data pushed in socket.
here $this->socket is $this->socket = Swoole\Client(SWOOLE_SOCK_TCP); // may be different variable in your code
- Now from server-side if you send the data using
push()repeatedly (like, by putingpush()inside a loop), the client-side will show the data. As a test, i calledpush()twice with a co:sleep(10) in the middle.
FYI.
@NathanFreeman @matyhtf @twose
@fakharksa
I recommend using Swoole\Coroutine\Http\Client as your WebSocket client instead of implementing it yourself. Also, please be mindful of the timeout settings; the default receive timeout for Swoole\Client is 500ms, while your server sends data to the client every second, which might cause the client to experience a premature receive timeout.
go(function () use ($pm) {
$cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort());
$cli->set(['timeout' => -1]);
$cli->setHeaders([]);
$ret = $cli->upgrade('/');
if (!$ret) {
echo "ERROR\n";
return;
}
echo $cli->recv()->data;
for ($i = 0; $i < 5; $i++) {
$cli->push('hello server');
echo ($cli->recv())->data;
co::sleep(0.1);
}
});
If you can consistently reproduce the issue, you could write a PHPT test and submit it to us.
reference: https://github.com/swoole/swoole-src/blob/master/tests/swoole_http_client_coro/websocket/1.phpt