只有一次 write() 调用通过套接字连接发送数据
第一个 stackoverflow 问题!我已经搜索过了...我保证。我还没有找到任何解决我困境的答案。至少可以说,我有一个严重恶化的问题。长话短说,我正在开发一款游戏的基础设施,其中移动应用程序(Android 应用程序和 iOS 应用程序)使用套接字与服务器进行通信,将数据发送到数据库。后端服务器脚本(我称之为 BES,或后端服务器)有数千行代码长。本质上,它有一个 main 方法,用于接受传入套接字的连接并分叉它们,以及一个从套接字读取输入并确定如何处理它的方法。大部分代码位于从数据库发送和接收数据并将其发送回移动应用程序的方法中。除了我添加的最新方法之外,所有这些都工作正常。此方法从数据库中获取大量数据,将其编码为 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)< /code> 到某个非常大的数字,在这种情况下,iOS 将在一个空数据对象(准确地说是 NSData 对象)中间接收数据。还需要注意的是,我在 AsyncSocket 类中的 iOS 端使用的方法读取的数据长度达到了从第一次写入调用接收到的长度。因此,如果我告诉它读取,比如说 10000 字节,它将创建一个该大小的 NSData 对象,并在从套接字读取时将其用作缓冲区。
非常感谢任何帮助。预先感谢大家!
First stackoverflow question! I've searched...I promise. I haven't found any answers to my predicament. I have...a severely aggravating problem to say the least. To make a very long story short, I am developing the infrastructure for a game where mobile applications (an Android app and an iOS app) communicate with a server using sockets to send data to a database. The back end server script (which I call BES, or Back End Server), is several thousand lines of code long. Essentially, it has a main method that accepts incoming connections to a socket and forks them off, and a method that reads the input from the socket and determines what to do with it. Most of the code lies in the methods that send and receive data from the database and sends it back to the mobile apps. All of them work fine, except for the newest method I have added. This method grabs a large amount of data from the database, encodes it as a JSON object, and sends it back to the mobile app, which also decodes it from the JSON object and does what it needs to do. My problem is that this data is very large, and most of the time does not make it across the socket in one data write. Thus, I added one additional data write into the socket that informs the app of the size of the JSON object it is about to receive. However, after this write happens, the next write sends empty data to the mobile app.
The odd thing is, when I remove this first write that sends the size of the JSON object, the actual sending of the JSON object works fine. It's just very unreliable and I have to hope that it sends it all in one read. To add more oddity to the situation, when I make the size of the data that the second write sends a huge number, the iOS app will read it properly, but it will have the data in the middle of an otherwise empty array.
What in the world is going on? Any insight is greatly appreciated! Below is just a basic snippet of my two write commands on the server side.
Keep in mind that EVERYWHERE else in this script the read's and write's work fine, but this is the only place where I do 2 write operations back to back.
The server script is on a Ubuntu server in native C using Berkeley sockets, and the iOS is using a wrapper class called 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
The mobile app makes two read calls, and gets outputMessage
just fine, but returnData
is always just a bunch of empty data, unless I overwrite sizeof(returnData)
to some hugely large number, in which case, the iOS will receive the data in the middle of an otherwise empty data object (NSData object, to be exact). It may also be important to note that the method I use on the iOS side in my AsyncSocket class reads data up to the length that it receives from the first write call. So if I tell it to read, say 10000 bytes, it will create an NSData object of that size and use it as the buffer when reading from the socket.
Any help is greatly, GREATLY appreciated. Thanks in advance everyone!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
TCP 成功编程的关键是在应用程序级别不存在 TCP“数据包”或“数据块”的概念。应用程序只能看到没有边界的字节流。当您在发送端使用某些数据调用
write()
时,TCP 层可能会选择以任何它认为合适的方式对您的数据进行切片和切块,包括将多个块合并在一起。您可能会写入 10 个字节两次,然后读取 5 个字节,然后读取 15 个字节。或者您的接收器可能会同时看到 20 个字节。您不能做的只是“希望”您发送的某些字节块将以相同的块形式到达另一端。
在您的特定情况下可能发生的情况是,两个连续的写入被合并为一个,而您的读取逻辑根本无法处理这一点。
The key to successful programming with TCP is that there is no concept of a TCP "packet" or "block" of data at the application level. The application only sees a stream of bytes, with no boundaries. When you call
write()
on the sending end with some data, the TCP layer may choose to slice and dice your data in any way it sees fit, including coalescing multiple blocks together.You might write 10 bytes two times and read 5 then 15 bytes. Or maybe your receiver will see 20 bytes all at once. What you cannot do is just "hope" that some chunks of bytes you send will arrive at the other end in the same chunks.
What might be happening in your specific situation is that the two back-to-back writes are being coalesced into one, and your reading logic simply can't handle that.
感谢您的所有反馈!我将大家的答案合并到解决方案中。我创建了一个方法,使用
writev
而不是write
将iovec
结构写入套接字。我在 iOS 端使用的包装类 AsyncSocket (顺便说一下,这非常棒......在这里查看 -->AsyncSocket Google Code Repo )可以很好地处理接收 iovec,并且显然是在幕后,因为它不需要我做任何额外的工作来正确读取所有数据。 AsyncSocket 类现在不会调用我的委托方法didReadData
,直到它收到iovec
结构中指定的所有数据。再次感谢大家!这很有帮助。一夜之间,我已经遇到了一周的问题得到了回复。我期待更多地参与 stackoverflow 社区!
解决方案示例代码:
Thanks for all of the feedback! I incorporated everyone's answers into the solution. I created a method that writes to the socket an
iovec
struct usingwritev
instead ofwrite
. The wrapper class I'm using on the iOS side, AsyncSocket (which is fantastic, by the way...check it out here -->AsyncSocket Google Code Repo ) handles receiving an iovec just fine, and behind the scenes apparently, as it does not require any additional effort on my part for it to read all of the data correctly. The AsyncSocket class does not call my delegate methoddidReadData
now until it receives all of the data specified in theiovec
struct.Again, thank you all! This helped greatly. Literally overnight I got responses for an issue I've been up against for a week now. I look forward to becoming more involved in the stackoverflow community!
Sample code for solution:
您确实应该定义一个函数
write_complete
,将缓冲区完全写入套接字。检查write
的返回值,它也可能是正数,但小于缓冲区的大小。在这种情况下,您需要再次写入
缓冲区的剩余部分。哦,使用
sizeof
也很容易出错。因此,在上面的 write_complete 函数中,您应该打印给定的大小并将其与您期望的大小进行比较。You should really define a function
write_complete
that completely writes a buffer to a socket. Check the return value ofwrite
, it might also be a positive number, but smaller than the size of the buffer. In that case you need towrite
the remaining part of the buffer again.Oh, and using
sizeof
is error-prone, too. In the abovewrite_complete
function you should therefore print the given size and compare it to what you expect.理想情况下,在服务器上,您希望以原子方式写入标头(大小)和数据,如果有任何机会多个线程可以写入,我会使用分散/聚集调用
writev()
来做到这一点同时连接到同一个套接字,您可能希望在写入调用中使用互斥锁。writev()
还将在返回之前写入所有数据(如果您使用的是阻塞 I/O)。在客户端上,您可能必须有一个状态机来读取缓冲区的长度,然后进行循环读取,直到收到所有数据,因为大缓冲区将被分段并以不同大小的块形式出现。
Ideally on the server you want to write the header (the size) and the data atomically, I'd do that using the scatter/gather calls
writev()
also if there is any chance multiple threads can write to the same socket concurrently you may want to use a mutex in the write call.writev()
will also write all the data before returning (if you are using blocking I/O).On the client you may have to have a state machine that reads the length of the buffer then sits in a loop reading until all the data has been received, as large buffers will be fragmented and come in in various sized blocks.