Closed ghost closed 4 years ago
Have you solved it?How?
I have the same issue. Anybody knows the solution?
Same issue here
Severity: error --> Exception: Empty read; connection dead? Stream state: {"timed_out":true,"blocked":true,"eof":false,"stream_type":"tcp_socket\/ssl","mode":"r+","unread_bytes":0,"seekable":false} application/vendor/textalk/websocket/lib/Base.php 269
Hello guys, in my case I had to remove the "->close()", because somehow the connection was already closing automatically, then I tried to close something already closed...
Hello guys, in my case I had to remove the "->close()", because somehow the connection was already closing automatically, then I tried to close something already closed...
I am having the same problem, where did you remove the "->close()" from?
The same problems guys. I have an empty read on my PHP script. But with another site, it's working (example: echo.websocket.org). Any suggestings?
我的处理方法是注释掉 $client->receive(); 因为我这边不需要他返回的数据 如果需要的话,建议仔websocket server 的 onClose 里面做处理,不断掉当前的fd 希望的我处境,对你们有帮助。谢谢。
If someone can post a code snippet that re-produces this issue, I can have a look at it.
The error implies that the servers has closed connection because of timeout settings. As the code is written, it is expected to throw exception on failed send/receive operations. The current solution is to wrap operations in a try/catch clause, and have the client reconnect and repeat the failed operation.
For me, this piece of code (which subscribes to a couple of streams containing live crypto trading information and then prints incoming data) inevitably triggers this error, usually in less than a minute. I've tried with another websocket client which does not appear to have similar disconnects. Anyway, here goes:
require('vendor/autoload.php');
use WebSocket\Client;
define('TRADES', 'live_trades_');
define('ORDERS', 'live_orders_');
$tps = [
"btcusd", "btceur", "eurusd", "xrpusd", "xrpeur",
"xrpbtc", "ltcusd", "ltceur", "ltcbtc", "ethusd",
"etheur", "ethbtc", "bchusd", "bcheur", "bchbtc"
];
function getSubscribeMessage(string $collectionType, string $tradingPair):string{
$json_message = '{
"event": "bts:subscribe",
"data": {
"channel": "%s%s"
}
}';
return sprintf($json_message, $collectionType, $tradingPair);
}
$ws = new Client('wss://ws.bitstamp.net/');
foreach($tps as $tp){
$ws->send(getSubscribeMessage(TRADES, $tp));
}
while (true) {
printf("%s\n\n", $ws->receive());
}
@babipanghang
A stream can be closed at any given time, in this case by server time out settings. So it's all about how the client manages streams. I suppose that many clients silently reconnects when encountering a closed stream, but this code doesn't.
Possibly we can add a method or option for continuously reading the stream, handling stream closing, but for now you can achieve the same effect by putting the receive operation within a try/catch clause;
while (true) {
try {
printf("%s\n\n", $ws->receive());
} catch (\WebSocket\ConnectionException $e) {
;
}
}
@sirn-se Your solution doesn't appear to solve anything. After the first execption gets caught, it just causes the client to keep throwing the same exception over and over, while apparently not receiving any more data. So in my mind, there's one of two scenarios going on here.
In the second scenario, i have a theory. According to specifications, a ping system may be used to keep the connection alive. Does the client automatically send a proper response to pings received from the server? Does it send pings every now and then to the server itself? If not, can we manually intercept/construct/send a ping message?
After some debugging I found there were 2 issues actually going on.
First, a short default timeout period (5 seconds). This was easily fixed by doing
$ws = new Client('wss://ws.bitstamp.net/', ['timeout' => 60]);
Second, as guessed in my previous message, it appears the client does indeed not send a proper response to pings. Adding the following to receive_fragment() in Base.php (somewhere below the point where $opcode_int is generated) appears to fix the issue:
if (9 == $opcode_int){
$this->send('pong', 'pong');
}
@babipanghang
The lacking ping-pong is fixed and scheduled for v1.3. It's already merged to master, if you want to try it out.
Ping/pong fixed in v.1.3
. Also added documentation on how to continuously listen to a connection.
Will consider a listen()
convenience method for future release.
I have the same problem.
In my case reconnect is not solution because after reconnect the context is lost and for server it is new connection (reading in the loop is interrupted by timeout and I get this error, because for new connections the server does not respond to the request from the previous connection). Need to repeat some operation to repair context.
So, it’s important for me to be able to understand when a close event or disconnection occurred. It seems that close event is final state when reading (from this commit bbf589059f9c1). It seems that 1.3.0 is just released, good. Thanks.
But what about disconnection whithout explicitly closing the connection?
It seems that in such keys we get ConnectionException with ["timed_out":false,"blocked":true,"eof":true ... ]
The opcode: close
frame concerns closing connection in an orderly fashion, enabling parts to adjust to changed state. The underlying connection should not be closed until corresponding part has acknowledged the close frame.
The underlying socket-stream might also close on a lower level. Server shutdown, of course. But sockets also have a time out, which allows servers to "garbage collect" inactive sockets.
Both of these cases will result in a re-connect if any send/receive function is called after connection has closed. The second case will throw exception, as the code expects an open connection, but there isn't. v1.3
decreases the risk, as it always checks underlying connection before sending/receiving, but it's never a 100% guarantee.
Not sure what you mean by "final state". The $final
in the code signifies it is the final frame in a fragmented message.
For continuous connections, there are two strategies. Both of them require some code on top of the functionality provided by this library;
ping
calls to let corresponding part know you're still hooked up to the connectionThere is still a bug with ping/pong in v1.3. The code for sending ping in response to pong is now
if ($opcode === 'ping') {
$this->send($payload, 'pong', true);
}
Without being actually sure, it seems possible that $payload actually remains empty (why would a ping frame have a payload?). This causes the pong frame not to be sent, because (in Base.php, send() method):
$fragment_cursor = 0;
// while we have data to send
while ($payload_length > $fragment_cursor) {
Since $payload_length and $fragment_cursor are actually both 0, the condition evaluates to false and no data is sent.
Yes, according to rfc application data may be omitted. Thanks. I create #82 that fix this problem.
But i see another problems in project.
The first problem is reconnect if connection lost
public function send($payload, $opcode = 'text', $masked = true)
{
if (!$this->isConnected()) {
$this->connect();
}
public function receive()
{
if (!$this->isConnected()) {
$this->connect();
}
I think when connection is lost then do not need to do reconnect because of lack of previous context. So, its totally wrong. I think the client code should decide when to reconnect and what additional steps should be performed in this case. So, need to add additional connect method to explicitly perform connect.
The second problem is stopping writing to socket when written less than the length of the sending message.
protected function write($data)
{
$written = fwrite($this->socket, $data);
if ($written < strlen($data)) {
throw new ConnectionException(
"Could only write $written out of " . strlen($data) . " bytes."
);
}
}
I sometime have this error (may be when conneciton is lost, but I'm not sure what actually case). I found this note in php doc of fwrite function and it seems that need to try write whole message instead of part. It seems that may be written 0 bytes when perform fwrite (i know that this links from another php func).
fwrite(): send of 106 bytes failed with errno=32 Broken pipe
/mnt/files/local_mount/modules/FOO/build/vendor/textalk/websocket/lib/Base.php:274
/mnt/files/local_mount/modules/FOO/build/vendor/textalk/websocket/lib/Base.php:138
/mnt/files/local_mount/modules/FOO/build/vendor/textalk/websocket/lib/Base.php:80
/mnt/files/local_mount/modules/FOO/build/vendor/dmore/chrome-mink-driver/src/DevToolsConnection.php:71
Indeed, there is still some issue with the fwrite()
part, but the patch in #82 seems to solved other issues.
Experimented with this fwrite_stream()
trick mentioned in the note of fwrite()
but it does not seem to solve the issue, continuing with socket_write()
.
I think when connection is lost then do not need to do reconnect because of lack of previous context. So, its totally wrong.
In addition, the connect()
method is only defined in Client and not in Base class.
Yes, i experemented too. It seems that fwrite value is always not false even when the connection is closed. The only way to determine if the connection is alive is to make fread or send very big message.
So, if fwrite is always return false then we can not use code from this note.
But socket_write works as expected, it return false when connection is dead. So, it seems that need to use socket_create/socket_read/socket_write instead of stream_socket_create/fread/fwrite.
fwrite(): send of 106 bytes failed with errno=32 Broken pipe
This error occured when connection is broken/closed (you send big message when connection is closed).
@mxr576 The missing Server.connect() is fixed in v1.3
.
As of reconnecting, the auto reconnect do include headers, auth and stream-context specified in the constructor. Any session context or equivalent need to be handled by implementing code.
That said, I do believe the library should separate low level I/O and convenience methods. It's a bit mixed as of now. That would be a breaking change though, which means 2.0
candidate.
Possibly, we could make the connect() method public and add reconnect
as an option, set to true
as default. Doing so would enable implementing code to handle connect() itself, without breaking implementations that rely on auto-reconnect.
I thought a lot about what to do, and i had some vision for this situation.
First I focus on the next moment: when the connection is broken, the only way to find out that the connection is interrupted is to read from socket (sometimes sending to a socket, but not always).
When the connection is broken (there was no websocket close request), such code
if (!$this->isConnected()) {
$this->connect();
}
does not essentially lead to a reconnection and i consider it correct.
So, when applicaiton get exception {"timed_out":true,"eof":false ... }
it means that server doesn't reply for some amount of time for some reasons and application code need correctly handle this error.
When application get exception {"timed_out":false,"eof":true ...}
it means that connection closed without websocket close message.
When application get exception Could only write ... out of ..
it means that connection closed without websocket close message.
So, i think that this topic need to close. It seems all works correctly and need correctly handle errors in application code. The only way when reconnection happens is when socket receive a close packet so its correct too.
Some improvements I can offer:
Correctly send message Wrote above about it. Need to add this logic. But it dodn't work as expected because in my tests fwrite always return NOT false. So need to replace stream_socket_create/fread/fwrite by socket_create/socket_read/socket_write.
It is not convenient to get the connection status at the time of the error. https://github.com/Textalk/websocket-php/blob/master/lib/Base.php#L309 It would be nice to get it in some method.
What about this pull request https://github.com/Textalk/websocket-php/pull/82? Do you doubt accept it or not? We can discuss this moment here.
It seems all works correctly and need correctly handle errors in application code.
Can we discuss what is the expected behavior for certain scenarios in an application level? I am trying to fix an issue in this library: https://gitlab.com/DMore/chrome-mink-driver/-/merge_requests/85/diffs?diff_id=93733985
Hm, maybe I finally managed to solve it based on the detailed summary provided by @Logioniz, kudos! https://gitlab.com/DMore/chrome-mink-driver/-/merge_requests/85/diffs?diff_id=94001321
Yes, sure. I will use pseudo code close to php. Need to use minimum 1.3 version. I will give an example code that takes into account timeout, broken connections and other websocket frames
func connect($url)
{
...
$this->clinet = new Client($url, ['timeout' => 5]);
}
func send($command)
{
...
$this->client->send($payload);
$data = $this->waitFor(function ($data) use (&$payload) {
return $payload['id'] == $data[id];
}, 20);
}
func waitFor($is_ready, $operation_timeout)
{
$endTime = microtime(true) + $operation_timeout;
while (microtime(true) < $endTime) {
try {
$respose = $this->client->receive();
} catch (ConnectionException $e) {
$pos = strpos($e->getMessage(), '{');
if ($pos !== false) {
$state = json_decode(substr($e->message, $pos), true);
if ($state['eof']) {
// connection is closing unexpectedly and you not get answer for your send in new connection becuase lack of context in new connection
// options what to do:
// 1. repeat last send command
// 2. repeat whole procedure
// 3. throw exception
// I don't know what vaiant is best
// but keep in mind that your context is lost and your state is lost
} else if ($state['time_out']) {
// if timeout then need to check on operation_timeout and wait a little longer
continue;
}
}
}
$opcode = $this->client->getLastOpcode();
if ($opcode === 'close') {
// connection is closing and you not get answer for your send in new connection becuase lack of context
// options what to do:
// 1. repeat last send command
// 2. repeat whole procedure
// 3. throw exception
// I don't know what vaiant is best
// but keep in mind that your context is lost and your state is lost
}
// exists ping/pong requests, need to skip
if (!in_array($opcode, ['text', 'binary']))
continue;
...
// all code below to process response and check is_ready
}
}
@Logioniz The PR looks fine to me. I'll merge and schedule for v1.3.1
release.
The socket_create/socket_read/socket_write functions require the sockets
compile time extensions, and is not compatible with some other functions used. So I think we should avoid them if possible.
Also, I do see the need to check stream state in application code, but extracting from embedded json seem … well. Maybe we should make it more accessible? Either by;
stream_get_meta_data
in a public method in Base.php@sirn-se it would be great. I don't know which variant is more convenient.
Tried to apply suggestion from https://github.com/Textalk/websocket-php/issues/64#issuecomment-638755354 but the code is still failing with the fwrite()
error when $this->client->send($payload);
is called, so waitFor() is not even called... I think some further adjustments are still needed in this lib because the auto-reconnect is not working. (Or I still misunderstand something.)
@mxr576 Your code need to catch any error in the send()
operations, just as with the receive()
operation in your linked PR. It will re-connect when you make another send/receive, but the error will still be there and need to be handled.
https://github.com/Textalk/websocket-php/pull/83 adds error code to exceptions in read()
and write()
. Constants in ConnectionException class;
const TIMED_OUT = 1024;
const EOF = 1025;
const BAD_OPCODE = 1026;
Example code above can then be simplified;
try {
$respose = $this->client->receive();
} catch (ConnectionException $e) {
$code = $e->getCode();
switch ($code) {
case ConnectionException::TIMED_OUT:
// Do things
break;
case ConnectionException::EOF:
// Do things
break;
default:
throw $e;
}
}
Version 1.3.1
with fixes referred to in this issue is released.
As this issue refers to a number of problems, including things fixed in 1.3
versions, I'm closing it. If there are still problems with 1.3
, they should be reported in a new issue.
$ws_url = config(ENVIRONMENT . '.page_ws_url');
$str = '{"req":"market.' . $symbol . '.kline.1min","id":' . microtime(true) . ',"from":' . time() . ',"to":' . time() . '}' . "\r\n";
try {
$client = new \WebSocket\Client($ws_url);
$client->send($str);
$data = $client->receive();
$list = json_decode(gzdecode($data), true);
if (isset($list['status']) && ($list['status'] === 'ok')) {
$end = end($list['data']);
$newprice = $end['close'];
}
} catch (RequestException $e) {
if ($e->hasResponse()) {
$mark = $e->getResponse()->getBody()->getContents();
} else {
$mark = $e->getMessage();
}
Log::info($ws_url);
Log::info($mark);
}
[0] ConnectionException in Base.php line 269 Empty read; connection dead? Stream state: {"timed_out":true,"blocked":true,"eof":false,"stream_type":"tcp_socket\/ssl","mode":"r+","unread_bytes":0,"seekable":false}