I have the following class being utilized within my plugin. Every time i attempt to use it, I recieve an exception: Failed to receive challenge. Did something in mcpe change which could be causing the issue or is there some way I can fix it? The full plugin is at https://github.com/jasonwynn10/CrossOnlineCount PHP: <?phpnamespace jasonwynn10\CrossOnlineCount\libs;class MinecraftQuery{ /* * Class written by xPaw * * Website: http://xpaw.me * GitHub: https://github.com/xPaw/PHP-Minecraft-Query */ const STATISTIC = 0x00; const HANDSHAKE = 0x09; /** @var resource $Socket */ private $Socket; /** @var string[] $Players */ private $Players; /** @var array|bool $Info */ private $Info; /** * @param $Ip * @param int $Port * @param int $Timeout * @param bool $ResolveSRV * * @throws MinecraftQueryException */ public function Connect( $Ip, $Port = 25565, $Timeout = 3, $ResolveSRV = true ) { if( !is_int( $Timeout ) || $Timeout < 0 ) { throw new \InvalidArgumentException( 'Timeout must be an integer.' ); } if( $ResolveSRV ) { $this->ResolveSRV( $Ip ); } $this->Socket = @FSockOpen( 'udp://' . $Ip, (int)$Port, $ErrNo = null, $ErrStr = null, $Timeout ); if( $ErrNo || $this->Socket === false ) { throw new MinecraftQueryException( 'Could not create socket: ' . $ErrStr ); } Stream_Set_Timeout( $this->Socket, $Timeout ); Stream_Set_Blocking( $this->Socket, true ); try { $Challenge = $this->GetChallenge( ); $this->GetStatus( $Challenge ); } // We catch this because we want to close the socket, not very elegant catch( MinecraftQueryException $e ) { FClose( $this->Socket ); throw new MinecraftQueryException( $e->getMessage( ) ); } FClose( $this->Socket ); } /** * @return string[]|bool */ public function GetInfo( ) { return isset( $this->Info ) ? $this->Info : false; } /** * @return bool|string[] */ public function GetPlayers( ) { return isset( $this->Players ) ? $this->Players : false; } /** * @return string * @throws MinecraftQueryException */ private function GetChallenge( ) { $Data = $this->WriteData( self::HANDSHAKE ); if( $Data === false ) { throw new MinecraftQueryException( 'Failed to receive challenge.' ); //TODO fix errors } return Pack( 'N', $Data ); } /** * @param string $Challenge * * @throws MinecraftQueryException */ private function GetStatus( string $Challenge ) { $Data = $this->WriteData( self::STATISTIC, $Challenge . Pack( 'c*', 0x00, 0x00, 0x00, 0x00 ) ); if( !$Data ) { throw new MinecraftQueryException( 'Failed to receive status.' ); } $Last = ''; $Info = Array( ); $Data = SubStr( $Data, 11 ); // splitnum + 2 int $Data = Explode( "\x00\x00\x01player_\x00\x00", $Data ); if( Count( $Data ) !== 2 ) { throw new MinecraftQueryException( 'Failed to parse server\'s response.' ); } $Players = SubStr( $Data[ 1 ], 0, -2 ); $Data = Explode( "\x00", $Data[ 0 ] ); // Array with known keys in order to validate the result // It can happen that server sends custom strings containing bad things (who can know!) $Keys = Array( 'hostname' => 'HostName', 'gametype' => 'GameType', 'version' => 'Version', 'plugins' => 'Plugins', 'map' => 'Map', 'numplayers' => 'Players', 'maxplayers' => 'MaxPlayers', 'hostport' => 'HostPort', 'hostip' => 'HostIp', 'game_id' => 'GameName' ); foreach( $Data as $Key => $Value ) { if( ~$Key & 1 ) { if( !Array_Key_Exists( $Value, $Keys ) ) { $Last = false; continue; } $Last = $Keys[ $Value ]; $Info[ $Last ] = ''; } else if( $Last != false ) { $Info[ $Last ] = mb_convert_encoding( $Value, 'UTF-8' ); } } // Ints $Info[ 'Players' ] = IntVal( $Info[ 'Players' ] ); $Info[ 'MaxPlayers' ] = IntVal( $Info[ 'MaxPlayers' ] ); $Info[ 'HostPort' ] = IntVal( $Info[ 'HostPort' ] ); // Parse "plugins", if any if( $Info[ 'Plugins' ] ) { $Data = Explode( ": ", $Info[ 'Plugins' ], 2 ); $Info[ 'RawPlugins' ] = $Info[ 'Plugins' ]; $Info[ 'Software' ] = $Data[ 0 ]; if( Count( $Data ) == 2 ) { $Info[ 'Plugins' ] = Explode( "; ", $Data[ 1 ] ); } } else { $Info[ 'Software' ] = 'Vanilla'; } $this->Info = $Info; if( empty( $Players ) ) { $this->Players = null; } else { $this->Players = Explode( "\x00", $Players ); } } /** * @param $Command * @param string $Append * * @return bool|string * @throws MinecraftQueryException */ private function WriteData( $Command, string $Append = "" ) { $Command = Pack( 'c*', 0xFE, 0xFD, $Command, 0x01, 0x02, 0x03, 0x04 ) . $Append; $Length = StrLen( $Command ); if( $Length !== FWrite( $this->Socket, $Command, $Length ) ) { throw new MinecraftQueryException( "Failed to write on socket." ); } $Data = FRead( $this->Socket, 4096 ); if( $Data === false ) { throw new MinecraftQueryException( "Failed to read from socket." ); } if( StrLen( $Data ) < 5 || $Data[ 0 ] != $Command[ 2 ] ) { return false; } return SubStr( $Data, 5 ); } /** * @param string $Address */ private function ResolveSRV( string &$Address) { if( ip2long( $Address ) !== false ) { return; } $Record = dns_get_record( '_minecraft._tcp.' . $Address, DNS_SRV ); if( empty( $Record ) ) { return; } if( isset( $Record[ 0 ][ 'target' ] ) ) { $Address = $Record[ 0 ][ 'target' ]; } }}
Using AsyncTask or not has nothing to do with whether a socket can be successfully created. What values have you got? Did the target server respond with any data, or did you receive nothing at all?
Create a new file name CheckServer.php. PS: I have tested and work fine; Sorry for my bad english. I use my mobile If you misunderstood me in my explain. PHP: <?phpnamespace Name;use pocketmine\scheduler\AsyncTask;use pocketmine\Server;use pocketmine\utils\Utils;class CheckServer extends AsyncTask { public $arr; public function __construct(array $arr) { $this->arr = $arr; } public function onRun() { foreach($this->arr as $eid => $ip) { if(empty($ip)) { unset($this->arr[$eid]); continue; } $server = explode(":", $ip); $Query = new Query\MinecraftQuery( ); try { $Query->Connect($server[0], $server[1], 1 ); } catch (\Query\MinecraftQueryException $e) { return "Host not respond ".$server[0]." error:".$e->getMessage(); } if (($info = $Query->GetInfo()) !== false) { $this->setResult($info['Players']); } } public function onCompletion(Server $server) { if($this->hasResult()) { $server->getPluginManager()->getPlugin("yourPluginName")->counter = $this->getResult();//set the count in the Main.php use the Update() public $counter; //on your onUpdate() add '$this->getServer()->getScheduler()->scheduleAsyncTask(new CheckServer($this->arr));' the '$this->arr' it's for check your array and use on the CheckServer.php ;) /*$lines = explode("\n", $entity->getNameTag()); $lines[0] = $this->รงounter." Online"; $nametag = implode("\n", $lines); $entity->setNameTag($nametag);*/ } }}
You can call almost any class in an async task. You can even create a new Item instance in an async task. Many think only non-pocketmine related functions can be called in an async task, so I wanted to put my two cents in.
According to , you can even start a server instance in another thread and encounter no problems (if you have the proper configuration) (I think there's a problem with memory management though).
I looked into the issue from the class's repo and found that it is a known problem. I am now using this class instead: PHP: <?phpnamespace jasonwynn10\CrossOnlineCount\libs;/** * Class MCPEQuery * @package jasonwynn10\CrossOnlineCount\libs * @author m1x */class MCPEQuery{ /** * @param string $host * @param int $port * @param int $timeout * * @return array */ static public function query(string $host, int $port, int $timeout = 2) { $socket = @fsockopen('udp://' . $host, $port, $errno, $errstr, $timeout); if($errno || $socket === false) { return ['error' => $errstr]; } stream_Set_Timeout($socket, $timeout); stream_Set_Blocking($socket, true); $randInt = mt_rand(1, 999999999); $reqPacket = "\x01"; $reqPacket .= pack('Q*', $randInt); $reqPacket .= "\x00\xff\xff\x00\xfe\xfe\xfe\xfe\xfd\xfd\xfd\xfd\x12\x34\x56\x78"; // magic string $reqPacket .= pack('Q*', 0); fwrite($socket, $reqPacket, strlen($reqPacket)); $response = fread($socket, 4096); fclose($socket); if (empty($response) || $response === false) { return ['error' => 'server do not answer']; } if (substr($response, 0, 1) !== "\x1C") { return ['error' => 'error']; } // $firstPart = substr($response, 0, 16); // $magic = substr($response, 16, 16); $serverInfo = substr($response, 35); $serverInfo = preg_replace("#ยง.#", "", $serverInfo); $serverInfo = explode(';', $serverInfo); return [ 'motd' => $serverInfo[1], 'num' => $serverInfo[4], 'max' => $serverInfo[5], 'version' => $serverInfo[3], 'platform' => 'PE' ]; }} so far, the new method seems to be working.