Winsock - 从 C++ 中的 Java 客户端读取整数

Posted

技术标签:

【中文标题】Winsock - 从 C++ 中的 Java 客户端读取整数【英文标题】:Winsock - read integer from Java client in C++ 【发布时间】: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 之类的文本协议。 【参考方案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

以上是关于Winsock - 从 C++ 中的 Java 客户端读取整数的主要内容,如果未能解决你的问题,请参考以下文章

如何检查客户端是不是通过 C++ 中的 Winsock 断开连接?

在 C++ 中的 Winsock 中发送 HTTP GET 请求后,Recv() 函数挂起

Winsock 仅在程序关闭时发送数据

winsock2 的替代品,带有 C++ 中的示例服务器源

服务器套接字只能连接几次(Winsock C++)

C++ Winsock 结构发送/接收