使用tcp从PHP发送到JAVA后,数据已损坏


Data is corrupted after sending from PHP to JAVA using tcp

我正在尝试将数据从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中,80FF范围内有一些字节,当将其解释为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中进行类似的比较。