2

第一个stackoverflow问题!我已经搜索过......我保证。我还没有找到任何答案来解决我的困境。我有一个……至少可以说是一个严重恶化的问题。长话短说,我正在为一款游戏开发基础架构,其中移动应用程序(Android 应用程序和 iOS 应用程序)使用套接字与服务器通信,将数据发送到数据库。后端服务器脚本(我称之为 BES 或后端服务器)有几千行代码。本质上,它有一个 main 方法接受到套接字的传入连接并将它们分叉,以及一个从套接字读取输入并确定如何处理它的方法。大多数代码位于从数据库发送和接收数据并将其发送回移动应用程序的方法中。除了我添加的最新方法外,它们都工作正常。此方法从数据库中抓取大量数据,将其编码为 JSON 对象,然后将其发送回移动应用程序,移动应用程序也会从 JSON 对象中对其进行解码并执行它需要执行的操作。我的问题是这个数据非常大,而且大多数时候不会在一次数据写入中通过套接字。因此,我在套接字中添加了一个额外的数据写入,通知应用程序它即将接收的 JSON 对象的大小。但是,在此写入发生后,下一次写入会将空数据发送到移动应用程序。我在套接字中添加了一个额外的数据写入,通知应用程序它即将接收的 JSON 对象的大小。但是,在此写入发生后,下一次写入会将空数据发送到移动应用程序。我在套接字中添加了一个额外的数据写入,通知应用程序它即将接收的 JSON 对象的大小。但是,在此写入发生后,下一次写入会将空数据发送到移动应用程序。

奇怪的是,当我删除发送 JSON 对象大小的第一个写入时,JSON 对象的实际发送工作正常。它非常不可靠,我必须希望它一口气发送所有内容。更奇怪的是,当我将第二次写入发送的数据的大小设置为一个巨大的数字时,iOS 应用程序会正确读取它,但它会将数据放在一个空数组的中间。

世界上到底发生了什么?非常感谢任何见解!下面只是我在服务器端的两个写命令的基本片段。

请记住,在此脚本中的其他任何地方,读取和写入都可以正常工作,但这是我背靠背执行 2 次写入操作的唯一地方。

服务器脚本位于使用 Berkeley 套接字的原生 C 语言的 Ubuntu 服务器上,而 iOS 使用称为 AsyncSocket 的包装类。

int n;
//outputMessage contains a string that tells the mobile app how long the next message
//(returnData) will be
n = write(sock, outputMessage, sizeof(outputMessage));
if(n < 0)
   //error handling is here
//returnData is a JSON encoded string (well, char[] to be exact, this is native-C)
n = write(sock, returnData, sizeof(returnData));
if(n < 0)
   //error handling is here

移动应用程序进行了两次读取调用,并且outputMessage很好,但returnData始终只是一堆空数据,除非我覆盖sizeof(returnData)到一些非常大的数字,在这种情况下,iOS 将在其他空数据的中间接收数据对象(确切地说是 NSData 对象)。还需要注意的是,我在 iOS 端的 AsyncSocket 类中使用的方法读取数据的长度达到了它从第一次写入调用接收到的长度。因此,如果我告诉它读取,比如 10000 字节,它将创建一个该大小的 NSData 对象,并在从套接字读取时将其用作缓冲区。

非常感谢任何帮助。提前谢谢大家!

4

4 回答 4

6

它非常不可靠,我必须希望它一口气发送所有内容。

使用 TCP 成功编程的关键是在应用程序级别没有 TCP“数据包”或“数据块”的概念。应用程序只看到一个字节,没有边界。当您write()在发送端调用一些数据时,TCP 层可能会选择以它认为合适的任何方式对您的数据进行切片和切块,包括将多个块合并在一起。

您可能会写入 10 个字节两次,然后读取 5 个字节然后 15 个字节。或者您的接收器可能会同时看到 20 个字节。您不能做的只是“希望”您发送的某些字节块将以相同的块到达另一端。

在您的特定情况下可能发生的情况是,两个背靠背的写入被合并为一个,而您的阅读逻辑根本无法处理。

于 2011-04-12T04:22:02.213 回答
1

感谢所有的反馈!我将每个人的答案都纳入了解决方案。我创建了一个方法,该方法iovec使用writev而不是write. 我在 iOS 端使用的包装类 AsyncSocket (这太棒了,顺便说一下......在这里查看 --> AsyncSocket Google Code Repo)处理接收 iovec 就好了,显然在幕后,如它不需要我做任何额外的努力就可以正确读取所有数据。AsyncSocket 类现在不会调用我的委托方法didReadData,直到它接收到iovec结构中指定的所有数据。

再次感谢大家!这有很大帮助。从字面上看,我在一夜之间得到了一个我已经反对了一周的问题的回复。我期待更多地参与 stackoverflow 社区!

解决方案示例代码:

//returnData is the JSON encoded string I am returning
//sock is my predefined socket descriptor
struct iovec iov[1];
int iovcnt = 0;
iov[0].iov_base = returnData;
iov[0].iov_len = strlen(returnData);
iovcnt = sizeof(iov) / sizeof(struct iovec);
n = writev(sock, iov, iovcnt)
if(n < 0)
   //error handling here
while(n < iovcnt)
   //rebuild iovec struct with remaining data from returnData (from position n to the end of the string)
于 2011-04-14T15:22:44.633 回答
0

您应该真正定义一个write_complete将缓冲区完全写入套接字的函数。检查 的返回值write,它也可能是一个正数,但小于缓冲区的大小。在这种情况下,您需要write再次使用缓冲区的剩余部分。

哦,使用sizeof也容易出错。因此,在上述write_complete函数中,您应该打印给定的大小并将其与您期望的大小进行比较。

于 2011-04-12T04:17:14.007 回答
-1

理想情况下,在您想要以原子方式写入标头(大小)和数据的服务器上,如果有任何机会多个线程可以同时写入同一个套接字,我也会使用 scatter/gather 调用writev(),您可能想要使用write 调用中的互斥锁。

writev()还将在返回之前写入所有数据(如果您使用阻塞 I/O)。

在客户端上,您可能必须有一个状态机来读取缓冲区的长度,然后循环读取,直到接收到所有数据,因为大型缓冲区将被分割并以各种大小的块进入。

于 2011-04-12T04:27:43.260 回答