只有一个write()调用通过套接字连接发送数据

时间:2011-04-12 04:11:03

标签: c linux ios sockets asyncsocket

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

奇怪的是,当我删除发送JSON对象大小的第一个写入时,JSON对象的实际发送工作正常。它只是非常不可靠,我不得不希望它能在一次读取中发送它。为了给这种情况增加更多的奇怪性,当我确定第二次写入发送大量数据的数据大小时,iOS应用程序将正确读取它,但它会将数据放在一个空数组的中间。

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

请记住,在这个脚本中的其他地方,读取和写入工作正常,但这是我连续两次写入操作的唯一地方。

服务器脚本位于使用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对象)的中间接收数据。值得注意的是,我在AsyncSocket类的iOS端使用的方法读取的数据长度是从第一次写入调用中获取的数据。因此,如果我告诉它读取10000个字节,它将创建一个该大小的NSData对象,并在从套接字读取时将其用作缓冲区。

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

4 个答案:

答案 0 :(得分:6)

  

这是非常不可靠的,我不得不希望它一次性发送所有内容。

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

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

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

答案 1 :(得分:1)

感谢所有反馈!我将每个人的答案都纳入了解决方案。我创建了一个方法,使用iovec而不是writev向套接字写入write结构。我在iOS端使用的包装类,AsyncSocket(顺便说一句,这很棒......在这里查看 - &gt; 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)

答案 2 :(得分:0)

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

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

答案 3 :(得分:-1)

理想情况下,在服务器上,您希望以原子方式编写标头(大小)和数据,如果有多个线程可以写入,则我也可以使用分散/收集调用writev()。同时使用相同的套接字,您可能希望在写入调用中使用互斥锁。

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

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