【问题标题】:Winsock - read integer from Java client in C++Winsock - 从 C++ 中的 Java 客户端读取整数
【发布时间】:2015-02-23 12:59:34
【问题描述】:

我有一个客户端-服务器应用程序,服务器部分用 C++ (Winsock) 编写,客户端部分用 Java 编写。

当从客户端发送数据时,我首先发送它的长度,然后是实际数据。发送长度,代码如下:

clientSender.print(text.length());

其中clientSender 的类型为PrintWriter

在服务器端,读取这个的代码是

int iDataLength;
if(recv(client, (char *)&iDataLength, sizeof(iDataLength), 0) != SOCKET_ERROR)
    //do something

我尝试在if 中打印iDataLength 的值,结果它总是一个随机的大整数。如果我将iDataLength 的类型更改为char,我会得到正确的值。但是,实际值很可能超过char 的容量。

在 C++ 中读取通过套接字传递的整数的正确方法是什么?

【问题讨论】:

  • 简单解释 - 使用具有明确的逐字节表示的协议,以便字节序、对齐、传递等无关紧要,并且始终有效。
  • 我认为您正在尝试将数字的文本表示直接转换为int。也许将其读入字符缓冲区,然后使用std::stoi() 之类的东西。这是PrintWriter 对您的int 所做的:docs.oracle.com/javase/7/docs/api/java/io/… “String.valueOf(int) 生成的字符串被转换为字节”
  • 您需要将其从平台端转换为网络端。然后您需要确保您发送的任何数据在另一侧具有相同的大小。在那里你转换回平台端。这并不像看起来那么容易。最好使用 JSON 之类的文本协议。

标签: c++ windows sockets types winsock


【解决方案1】:

我认为问题在于 PrintWriter 正在写入 文本,而您正在尝试读取 二进制数

这是 PrintWriter 对它发送的整数所做的:

http://docs.oracle.com/javase/7/docs/api/java/io/PrintWriter.html#print%28int%29

打印一个整数。 String.valueOf(int) 产生的字符串是 根据平台的默认字符翻译成字节 编码,并且这些字节完全按照 write(int) 方法。

试试这样的:

#include <sys/socket.h>
#include <cstring> // for std::strerror()

// ... stuff

char buf[1024]; // buffer to receive text
int len;

if((len = recv(client, buf, sizeof(buf), 0)) == -1)
{
    std::cerr << "ERROR: " << std::strerror(errno) << std::endl;
    return 1;
}

std::string s(buf, len);

int iDataLength = std::stoi(s); // convert text back to integer

// use iDataLength here (after sanity checks)

【讨论】:

    【解决方案2】:

    您确定字节顺序不是问题吗? (也许 Java 将其编码为大端,而您将其读取为小端)。

    此外,您可能需要实现receivall 函数(类似于sendall - as here)。确保您收到指定的确切字节数 - 因为recv 收到的字节数可能比它被告知的要少。

    【讨论】:

      【解决方案3】:

      您混淆了数值和它们的 ASCII 表示。

      当你在 Java 中编写 clientSender.print(text.length()); 时,你实际上是在编写一个 ascii 字符串 - 如果长度为 15,你将发送字符 1(代码 ASCII 0x31)和 5(代码 ASCII 0x35 )

      所以你必须:

      • 以可移植方式发送二进制长度(在 C 或 C++ 中,您有 htonntoh,但在 Java 中不确定)
      • 在 Java 端的文本长度之后添加一个分隔符(换行符)并在 C++ 中对其进行解码:

        char buffer[1024]; // a size big enough to read the packet
        int iDataLength, l;
        l = recv(client, (char *)&iDataLength, sizeof(iDataLength), 0);
        if (l != SOCKET_ERROR) {
            buffer[l] = 0;
            iDataLength = sscanf(buffer, "%d", &iDataLength);
            char *ptr = strchr(buffer, '\n');
            if (ptr == NULL) {
                 // should never happen : peer does not respect protocol
                 ...
            }
            ptr += 1; // ptr now points after the length
            //do something
        }
        

        Java 部分应该是:clientSender.println(text.length());

      编辑:

      根据 Remy Lebeau 的评论,TCP 中的发送和读取之间没有一对一的关系。 recv() 可以并且确实返回任意数量的数据,因此您不能假设单个 recv() 将读取整行文本。

      上面的代码不应该做一个简单的recv,而是准备连接多个读取以找到分隔符(留给读者作为练习:-))

      【讨论】:

      • TCP 中的发送和读取之间没有一对一的关系。 recv() 可以并且确实返回任意数量的数据,因此您不能假设单个 recv() 将读取整行文本。您必须缓冲所有接收到的数据,每次都搜索缓冲区,直到找到分隔符,然后处理缓冲区直到该分隔符,从缓冲区中删除已处理的内容,并根据需要重复,直到缓冲区中不再找到分隔符,然后将任何不完整的数据保留在缓冲区中,以便以后有更多数据到达时可以完成和处理。
      • @RemyLebeau :当然你是对的,上面的代码过于简单。但是对于如此简单的事情,我懒得明确地实现手动缓冲,并认为使用fdopen 稍后使用fgetsfscanf 可能会混淆OP。有了你的评论,现在更清楚了:-)
      • 我结束了你的建议和@galik 使用std::stoi() 的建议。长度前后都有一个定界字符 (char)。我只使用recv
      猜你喜欢
      • 1970-01-01
      • 2020-05-11
      • 1970-01-01
      • 2016-08-02
      • 2019-02-02
      • 2023-03-30
      • 2012-07-08
      • 1970-01-01
      • 2017-09-28
      相关资源
      最近更新 更多