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++ 中,您有hton
和 ntoh
,但在 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
稍后使用fgets
或fscanf
可能会混淆OP。有了你的评论,现在更清楚了:-)
我结束了你的建议和@galik 使用std::stoi()
的建议。长度前后都有一个定界字符 (char
)。我只使用recv
。以上是关于Winsock - 从 C++ 中的 Java 客户端读取整数的主要内容,如果未能解决你的问题,请参考以下文章
如何检查客户端是不是通过 C++ 中的 Winsock 断开连接?