我正在尝试将数据从PHP TCP服务器发送到JAVA TCP客户端。我通过比较数据的十六进制值来比较我的结果。
PHP脚本读取STDIN,通过套接字一次发送一个字节,java使用DataInputStream.read((读取,转换为十六进制并显示。
如果我手动在脚本中键入数据,它可以正常工作。如果我使用带有数据的文件,它可以正常工作但是,当我分配/dev/urandom(甚至只有几个字节(时,java端的数据就会被破坏。在随机位置总是有一个值为efbfbd的十六进制,而不是正确的数据。请帮我解决这个问题。PHP代码:
$f = fopen( 'php://stdin', 'rb' );
while($line = fread($f, 1)){
$length = 1;
echo bin2hex($line)."'n";
echo socket_write($client, $line, 1)."'n";
$sent = socket_write($client, $line, $length);
if ($sent === false) {
break;
}
// Check if the entire message has been sented
if ($sent < $length) {
// If not sent the entire message.
// Get the part of the message that has not yet been sented as message
$line = substr($line, $sent);
// Get the length of the not sented part
$length -= $sent;
}
Java代码:
in = new DataInputStream(clientSocket.getInputStream());
byte[] data = new byte[1];
int count = 0;
while(in.available() > 0){
//System.out.println(in.available());
in.read(data);
String message = new String(data);
System.out.println(message);
//System.out.flush();
System.out.println( toHex(message) );
//in.flush();
message = "";
}
您在编码方面遇到了麻烦。通过调用new String(data)
,字节数组将使用默认编码转换为字符串,无论这种编码是什么(例如,您可以将java -Dfile.encoding=UTF-8
的编码设置为UTF-8
(。
您想要的Java代码很可能如下所示:
in = new DataInputStream(clientSocket.getInputStream());
byte[] data = new byte[1];
int count = 0;
while (in.available() > 0) {
// System.out.println(in.available());
in.read(data);
String hexMessage = Integer.toHexString(data[0] & 0xFF);
String stringMessage = new String(data, "UTF-8"); // US-ASCII, ISO-8859-1, ...
System.out.println(hexMessage);
}
更新:我错过了32位的问题。用Java签名的8位byte
被符号扩展为32位int
。为了有效地撤消这个符号扩展,可以用0xFF
屏蔽byte
。
Java程序有两个主要问题。
首先是in.available()
的使用。它不会告诉您消息中还有多少字节。它只是说明流中有多少字节已准备就绪,可用于读取而不会被阻止。例如,如果服务器通过套接字发送两个数据包,其中一个已经到达,但另一个仍在通过Internet发送,并且每个数据包有200个字节(这只是一个示例(,那么在第一次调用中,您将得到答案200
。如果你读取了200个字节,你肯定不会被阻止。但是,如果第二个数据包还没有到达,您对in.available()
的下一次检查将返回0
。如果你在这一点上停下来,你只有一半的数据。不是你想要的。
通常,您必须读取,直到到达流的末端(InputStream.read()
返回-1(,然后您不能再使用同一个流,然后关闭套接字,或者您有一个特定的协议,该协议告诉您期望多少字节,然后您读取该字节数。
但这并不是您在程序输出中看到奇怪值的原因。原因是Java和PHP对字符串的表示方式完全不同。在PHP中,一个字符串可以包含任何字节,并且将它们解释为字符取决于按比例编程程序。
这基本上意味着PHP字符串相当于Java中的byte[]
。
但是Java字符串完全不同。它在内部由一个char
数组组成,在UTF-16编码中,char
总是两个字节。当您将读取的字节转换为Java String
时,总是通过使用某些字符编码对字节进行编码来完成,以便将适当的字符存储在字符串中。
例如,如果您的字节是44 4F 4C 4C
,而字符编码是ISO-8859-1,则这将被解释为字符'u0044
、'u004F
、'u004C
、'u004C
。它将是一个由四个字符组成的字符串——"DOLL"
。但是,如果您的字符编码是UTF-16
,则字节将被解释为'u444F
和'u4C4C
。只有两个字符的字符串,"䑏䱌"
。
当您从控制台或文件中读取数据时,数据可能采用Java默认要求的编码。当文件是用纯英语写的,只有英文字母、空格和标点符号时,通常会出现这种情况。这些都是7位字符,在ISO-8859-1和UTF-8中是相同的,这是常见的默认值。但在/dev/urandom
中,80
到FF
范围内有一些字节,当将其解释为UTF-16 Java字符串时,可能会有不同的处理方式。
此外,您没有在Java中显示您的toHex()
方法。它可能会再次从字符串中读取字节,但使用哪种编码?如果使用ISO-8859-1
将字节读取到String
中,并在UTF-8
中取出,则会得到完全不同的字节。
如果你想知道PHP到底发送了什么,不要把字节放在String
中。编写一个适用于字节数组的toHex
方法,并使用直接读取的byte[]
。
此外,请始终记住检查read()
返回的字节数,并仅解释该字节数!read()
不而总是填充整个数组。因此,在新的toHex()
方法中,您还需要将读取的字节数作为参数传递,这样它就不会在它们之后显示数组的部分。在您的情况下,您只有一个单字节数组(这是不推荐的(,但即使在这种情况下,read()
也可以返回0,这是一个完全合法的值,表明在对read()
的特定调用中没有可用的字节,尽管在下一个read()
中可能有一些可用的字节。
正如上面的评论所说,您可能在字节String message = new String(data);
的字符串表示方面遇到了问题。可以肯定的是,您应该获取数据字节,并以Base64对其进行编码。您可以使用ApacheCommons或Java8之类的库来实现这一点。您应该能够在PHP中进行类似的比较。