Reputation: 67
I'm using Amphp version 2.6 to create asynchronous socket connections to a Minecraft server. During this connection I send integers, strings and bytes to the server in order to retrieve info from a plugin which is running on the Minecraft server.
The connection process is as follows:
My problem is that sometimes the PHP code fails to read the server response (step 6), I have confirmed with logging that this is where the script times out. When the script fails to read the server response it won't send another byte to the Java plugin and that causes the EOFException I'm pretty sure. The error I get is the following:
[11:31:01 INFO]: StatusMC > Random code: 726462011
[11:31:01 INFO]: StatusMC > 119f9fcfa7e8d56002e755598950a130bbf999004445ce28e070f5d639157ac9757450bb50744e9552bde50e3a2fdc584f468351248854acc72c1c3c36ee931e
[11:31:03 INFO]: StatusMC > Successfully connected to the server!
[11:31:03 INFO]: StatusMC > IO exception 1: null
[11:31:03 WARN]: java.io.EOFException
[11:31:03 WARN]: at java.base/java.io.DataInputStream.readUnsignedByte(DataInputStream.java:290)
[11:31:03 WARN]: at java.base/java.io.DataInputStream.readByte(DataInputStream.java:268)
[11:31:03 WARN]: at StatusMCv1.3.7-dev.jar//net.statusmc.utils.SocketServer.run(SocketServer.java:99)
My PHP code:
function asyncTask(string $server_identifier, string $monitor_id, string $page_id, string $host, string $secret_key, int $port, int $timeout, int $writeTimeout): Promise {
return call(function () use ($server_identifier, $monitor_id, $page_id, $host, $secret_key, $port, $timeout, $writeTimeout) {
$tpsCompleted = false;
try {
$cstatus = new Status();
$tpsDouble = 0;
$message = "N/A";
// Create an asynchronous socket connection using Amphp
/** @var \Amp\Socket\ClientSocket $socket */
$socket = yield timeout(Socket\connect("tcp://{$host}:{$port}"), $timeout);
//Olen lisanud järgmisele kahele reale timeouti, sest isegi kui connection ei timeouti ja connectib edukalt, siis
//on võimalik, et serverilt data kätte saamisega võib tekkida probleeme, nt on pandud Serveri port mitte StatusMC plugina port
//ja kui siin timeoute pole, siis see võib kogu scripti timeoutida ja tekib Gateway time-out
//teistel ridadel pole seda pannud, sest tõenäoliselt ta timeoutib kohe alguses
//https://chatgpt.com/c/67767b7d-a7c8-8010-a4e5-679615fbf5ca
yield timeout($socket->write(chr(1)), $writeTimeout);
$randomInt = yield timeout(readRawInt($socket), $writeTimeout);
$cstatus->log("TPS MONITOR ERROR", 'admin', 'Random int has been read. Random int: '.$randomInt.' Monitor ID: '.$monitor_id);
//Stringi writimine, secret key saatmine koos random Intiga
$message = hash("SHA512", $randomInt . $secret_key);
$stringSize = 0;
// Write the length of the hash (as an integer)
yield $socket->write(pack("N", strlen($message)));
// Write the hash itself
yield timeout($socket->write($message), $writeTimeout);
$cstatus->log("TPS MONITOR ERROR", 'admin', 'Message has been written. Message: '.$message.' Monitor ID: '.$monitor_id);
$serverResponse = yield timeout(readRawInt($socket), $writeTimeout);
$cstatus->log("TPS MONITOR ERROR", 'admin', 'Server response has been read. Server response: '.$serverResponse.' Monitor ID: '.$monitor_id);
if($serverResponse == 1) {
$cstatus->log("TPS MONITOR ERROR", 'admin', 'Server response 1. Monitor ID: '.$monitor_id);
yield timeout($socket->write(chr(2)), $writeTimeout);
$tps = yield timeout(readRawDouble($socket), $writeTimeout);
$cstatus->log("TPS MONITOR ERROR", 'admin', 'TPS double has beenr read. TPS: '.$tps. ' Monitor ID: '.$monitor_id);
if($tps == 404500) { //This is a fail-safe, if we receive a double 404500 it means that we didn't get a correct TPS, trying again
yield timeout($socket->write(chr(2)), $writeTimeout);
$tps = yield timeout(readRawDouble($socket), $writeTimeout);
if($tps<0) {
//Kogume data API-le, et saata hiljem kõikide monitoride data koos
$result = [
'server_identifier' => $server_identifier, //Minu enda välja mõeldud mingi pikk jada, millega verifyme, et request tuli just sellelt API-lt
'page_id' => $page_id,
'monitor_id' => $monitor_id,
'tps' => $tps,
'status' => 'invalid_data'
];
} else {
//Kogume data API-le, et saata hiljem kõikide monitoride data koos
$result = [
'server_identifier' => $server_identifier, //Minu enda välja mõeldud mingi pikk jada, millega verifyme, et request tuli just sellelt API-lt
'page_id' => $page_id,
'monitor_id' => $monitor_id,
'tps' => $tps,
'status' => 'successful'
];
}
} else { //Kui saadud TPS ei ole 404500, siis toimime tavapäraselt
if($tps<0) {
//Kogume data API-le, et saata hiljem kõikide monitoride data koos
$result = [
'server_identifier' => $server_identifier, //Minu enda välja mõeldud mingi pikk jada, millega verifyme, et request tuli just sellelt API-lt
'page_id' => $page_id,
'monitor_id' => $monitor_id,
'tps' => $tps,
'status' => 'invalid_data'
];
} else {
//Kogume data API-le, et saata hiljem kõikide monitoride data koos
$result = [
'server_identifier' => $server_identifier, //Minu enda välja mõeldud mingi pikk jada, millega verifyme, et request tuli just sellelt API-lt
'page_id' => $page_id,
'monitor_id' => $monitor_id,
'tps' => $tps,
'status' => 'successful'
];
}
}
} else { //Kui $serverResponse pole 1 ehk ei connectinud ära, siis
$cstatus->log("TPS MONITOR ERROR", 'admin', 'Server response not 1. Monitor ID: '.$monitor_id.' Server response: '.$serverResponse);
//Kogume data API-le, et saata hiljem kõikide monitoride data koos
$result = [
'server_identifier' => $server_identifier, //Minu enda välja mõeldud mingi pikk jada, millega verifyme, et request tuli just sellelt API-lt
'page_id' => $page_id,
'monitor_id' => $monitor_id,
'tps' => 0,
'status' => 'connection_error'
];
}
yield timeout($socket->write(chr(3)), $writeTimeout);
//Annan Javale veidi aega, et closida connection
yield new \Amp\Delayed(100); // 100ms delay
$socket->close();
} catch (TimeoutException $e) {
$cstatus = new Status();
$cstatus->log("TPS MONITOR ERROR", 'admin', 'TPS Monitor timeout exception occured. Monitor ID: '.$monitor_id.' Exception: '.$e);
//Kogume data API-le, et saata hiljem kõikide monitoride data koos
$result = [
'server_identifier' => $server_identifier, //Minu enda välja mõeldud mingi pikk jada, millega verifyme, et request tuli just sellelt API-lt
'page_id' => $page_id,
'monitor_id' => $monitor_id,
'tps' => 0,
'status' => 'connection_error'
];
} catch (\Exception $e) {
$cstatus = new Status();
$cstatus->log("TPS MONITOR ERROR", 'admin', 'TPS Monitor exception occured. Monitorid ID: '.$monitor_id.' Exception: '.$e);
if (isset($socket)) {
yield timeout($socket->write(chr(3)), $writeTimeout);
//Annan Javale veidi aega, et closida connection
yield new \Amp\Delayed(100); // 100ms delay
$socket->close();
}
//Kogume data API-le, et saata hiljem kõikide monitoride data koos
$result = [
'server_identifier' => $server_identifier, //Minu enda välja mõeldud mingi pikk jada, millega verifyme, et request tuli just sellelt API-lt
'page_id' => $page_id,
'monitor_id' => $monitor_id,
'tps' => 0,
'status' => 'connection_error'
];
}
return $result;
});
}
// Method to read an integer from the socket's response
function readRawInt($socket): Promise {
return Amp\call(function () use ($socket) {
$buffer = '';
while (strlen($buffer) < 4) {
$chunk = yield $socket->read();
if ($chunk === null) {
throw new \Exception("Socket closed before reading 4 bytes.");
}
$buffer .= $chunk;
}
// Convert the first 4 bytes of the buffer to an integer
$intData = unpack('N', substr($buffer, 0, 4))[1];
return $intData;
});
}
function readRawDouble(ResourceSocket $socket): \Amp\Promise {
return \Amp\call(function () use ($socket) {
// Read 8 bytes from the socket
$data = yield $socket->read(8);
// Ensure we got exactly 8 bytes
if ($data === null || strlen($data) !== 8) {
throw new \RuntimeException("Expected 8 bytes for the double, got " . (strlen($data) ?: 0) . " bytes.");
}
// Reverse the data if the machine is little-endian
if (pack('L', 1) === pack('V', 1)) { // Detect little-endian
$data = strrev($data);
}
// Unpack the data as a double
$unpacked = unpack('d', $data);
// Validate the unpacked result
if ($unpacked === false || !isset($unpacked[1])) {
throw new \RuntimeException("Failed to unpack the double. Data: " . bin2hex($data));
}
echo "Raw data: " . bin2hex($data) . PHP_EOL; // Print the raw bytes read
echo "Unpacked double: " . $unpacked[1] . PHP_EOL; // Print the resulting double
return $unpacked[1]; // Return the unpacked double
});
}
My Java plugin code:
@Override
public void run(){
try{
//msptCalculator = new SocketOnGetMSPTEvent(); // Initialize the MSPTCalculator instance
listenSock = new ServerSocket(port);
while(true){
sock = listenSock.accept();
in = new DataInputStream(sock.getInputStream());
out = new DataOutputStream(sock.getOutputStream());
this.connect_status = true;
InetAddress addr = sock.getInetAddress();
try{
if(in.readByte() == 1){
int random_code = new SecureRandom().nextInt();
//See on lisatud siia juhuks, kui random_code peaks olema negatiivne. See funktsioon võtab random_codeist
//absoluutväärtuse, mis on alati positiivne. See on vajalik sellepärast kuna veebipoolel tekib int-i lugemisega
//probleeme kui arv on negatiivne
int random_code_processed = Math.abs(random_code);
Utils.log(Level.INFO, "Random code: "+random_code_processed);
out.writeInt(random_code_processed);
boolean success = Utils.readString(in, false).equals(Utils.hash(random_code_processed + ServerConfig.secretKey));
if(success){ //Kui secret key-ga on korras,
//Kontrollib, kas IP, mis üritab connectida on minu API whitelistis olemas
//Teeme seda siin, et tagada, et IP kontroll oleks alati tehtud, hiljem on võimalik seda bypassida
String incomingIP = addr.getHostAddress();
//Saab incoming connectioni hostnamei
String incomingHostname = addr.getHostName();
if(APIRequest.checkIP(incomingIP, incomingHostname)) { //Kui function returnib true, siis käivitub see kood
//Kõik on korras
out.writeInt(1);
out.flush();
Utils.log(Level.INFO, Utils.format(ServerConfig.successfulLogin));
} else { //Kui connection tuleb teiselt IPlt, siis
//Kui function returnib false ehk vale IP üritab connectida, siis käivitub see kood
Utils.log(Level.ERROR, "A connection from an unknown IP was refused. Please report this to the StatusMC Support staff. Blocked IP: " + addr.getHostAddress() + " Blocked hostname: "+ addr.getHostName());
out.writeInt(0);
out.flush();
this.connect_status = false;
}
}else{
out.writeInt(0);
out.flush();
Utils.log(Level.INFO, Utils.format(ServerConfig.wrongSecretKey));
this.connect_status = false;
}
}else{
out.writeInt(0);
out.flush();
Utils.log(Level.INFO, Utils.format(ServerConfig.unexpectederror));
this.connect_status = false;
}
while(this.connect_status){
final byte packetNumber = in.readByte();
if(packetNumber == 2) { //GET TPS FUNCTION
double tps = Math.round(SocketOnGetTPSEvent.getTPS() * 100.0D) / 100.0D;
out.writeDouble(tps);
if(ServerConfig.log_performance_metrics.equals(true)) {
Utils.log(Level.INFO, "The current TPS is " + tps + ".");
}
} else if(packetNumber == 4) { //Saab min MSPT
double minMSPTValue = SocketOnGetMSPTEvent.getMinMSPT();
out.writeDouble(minMSPTValue);
if(ServerConfig.log_performance_metrics.equals(true)) {
Utils.log(Level.INFO, "Min MSPT for the last 1 minute: "+minMSPTValue);
}
} else if(packetNumber == 5) { //Saab avg MSPT
double avgMSPTValue = SocketOnGetMSPTEvent.getAvgMSPT();
out.writeDouble(avgMSPTValue);
if(ServerConfig.log_performance_metrics.equals(true)) {
Utils.log(Level.INFO, "Average MSPT for the last 1 minute: "+avgMSPTValue);
}
} else if(packetNumber == 6) { //Saab max MSPT
double maxMSPTValue = SocketOnGetMSPTEvent.getMaxMSPT();
out.writeDouble(maxMSPTValue);
if(ServerConfig.log_performance_metrics.equals(true)) {
Utils.log(Level.INFO, "Max MSPT for the last 1 minute: "+maxMSPTValue);
}
} else if(packetNumber == 3){ // CLOSE_CHANNEL
Utils.log(Level.INFO, "Socket packet 3 close");
out.flush();
closeConnectionGracefully();
} else {
Utils.log(Level.INFO, "Packet not found! Packet: " + packetNumber + " Please contact StatusMC Support staff.");
}
}
}catch(IOException ex){
out.writeDouble(404500); //Kui tekib IO Exception 1, siis writein doublei 404500
//Kui PHP saab tpsi/mspt-d lugeda üritades doublei 404500, siis teeb uue katse lugeda serveri TPSi, sest TPS tekkis viga TPSi
//saamisel. Valisin 404500 sellepärast, et MC serveri TPS JA MSPT ei saa kunagi olla nii suur
out.flush();
/*closeConnectionGracefully();*/
Utils.log(Level.INFO, "IO exception 1: "+ex.getMessage());
ex.printStackTrace();
}
}
}catch(IOException ex){
Utils.log(Level.INFO, "IO exception 2: "+ex.getMessage());
ex.printStackTrace();
}
}
private void closeConnectionGracefully() {
try {
in.close();
out.close();
sock.close();
this.connect_status = false;
} catch (IOException e) {
Utils.log(Level.WARNING, "Error while closing socket connection: "+e.getMessage());
}
}
Upvotes: -1
Views: 36
Reputation: 67
The problem was in the Java code line:
if(APIRequest.checkIP(incomingIP, incomingHostname)) {
}
Turns out the API request to check if the IP is in the list of allowed IPs took too long which caused the PHP script to timeout.
I removed the API request to check and moved the IP check into the java code itself for testing, basically I hard-coded the hostname which it needed to check if the incoming hostname matches that since then I wouldn't need to wait for the API to respond.
But that also gave me some troubles as addr.getHostName();
and contains()
in an if
statement still caused the script to take too long sometimes so I removed it temporarily. After I removed the addr.getHostName()
check completely, the script works perfectly. I'm not sure why it still times out even with hard-coded values.
Upvotes: 1