Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Add send queue to TCPObject #658
Implements a fix for issue #655 by adding a simple send queue to TCPObject. This also adds a SimpleDataQueue class to the engine (let me know if I somehow missed an already-existing streaming queued buffer class!).
All connected TCPObject instances are processed in the run loop to ensure all data has been sent. The send queue is iterated until either the queue is empty or the TCP layer stops sending bytes through.
Due to the varying mysterious nature of send buffers in TCP implementations it is advised that this code is tested simulating a pretty bad connection to ensure the buffer is actually made use of. In most cases the code will at least act like the old send() as a call to send the data is issued immediately.
This implementation looks completely wrong. First of all
Secondly, your Simple queue implementation is very complicated for what it's trying to do. If you really want good performance you'll want a ring buffer implementation, however ring buffers don't normally support buffer size expansion cause they're static size for simplistic sake. Alternatively you could use std::vector directly for dynamic memory allocation, however it is expensive on popping front units as it'll move everything one unit forward each time, so kind of bad for this usage case, however unless you only use std::vector.reserve() and std::vector.size() functions and directly manipulate the buffer yourself.
To address your points
"This implementation looks completely wrong. First of all TCPObject::processSendBuffer() assumes either it's all or nothing of data up to 4096 long"
All processSendBuffer does is write data from the mSendQueue in 4kb chunks. It uses the return value from Net::send in order to determine how many bytes were actually "written" to the TCP stream.
"check the returned byte count to know how much it really did accept and and offset that by how much you tried to send"
This is what the code does. Provided no error is emitted by Net::send it will destroy the data in mSendQueue ( https://github.com/jamesu/Torque3D/blob/tcpobjectfix/Engine/source/app/net/tcpObject.cpp#L437 )
"Your simple queue is wrong too. It should have a get head data and seek/skip like functions "
copyToBuffer copies data from the buffer without expending the queue bytes. expendBuffer actually destroys the data from the head position. write appends data onto the tail of the queue. This is loosely based on the boost asio streambuf class.
"Secondly, your Simple queue implementation is very complicated for what it's trying to do. If you really want good performance you'll want a ring buffer implementation"
Not the best code In the world, I admit. Based on the original design constraints however it worked. Admittedly performance wasn't a huge consideration since I wasn't making a bit torrent client or some kind of elaborate network renderer.
"Third, TCPObject's send() method shouldn't be a send and forget call."
That's debatable. Do you really expect scripters to need to handle partial TCP sends with convoluted schedule calls? A simple send("HELLO"); call would need a fair bit of scripting overhead in order to properly handle data which isn't sent.
In any case, this improvement is not designed to be able to send large files through the send script call (that's impractical especially considering you can't send '\0'). Rather it's designed to handle the case where for whatever reason besides a fatal error data simply isn't sent fully.
Originally this was used in conjunction with the file resource which allows files to be streamed through TCPObject using a FileStream. In this case the normal send() calls were processed after the file had finished sending, thus one of the main reasons why its stuffed in a send queue. If you really want I could just make send return the number of bytes sent and make everyone do it the hard way! ;)
I am using the TCPObject to make a custom http server system in torquescript since from what I read the HTTP object is limited and broken for the most part. Plus it gives me more control and I can do stuff like use Websockets.
Anyway I added a sendFile method to TCPObject that simply takes a path, reads every byte of the data into a vector of U8 and then sends that vector. The issue is the entire file is never sent and there is no return from send or any way of knowing when how much was sent. So the header can't be changed to notify the browser that it is being chunked instead.
There could be other issues similar to this. And although it isn't a direct bug with your code, it is a limitation. There needs to be both a return added to send (console method and engine) of how much data was sent, maybe even a heads up for how much will be sent. And then some sort of callback.
Or any other solution you think would work.
If you need more info or code I can try and put some of it up. Or post in a comment or something. But it's pretty simple.
@jamesu, thx for your work :)
After a initial review (not tested code):
Implementations can be moved, though since this class was more of a one-off only used by TCPObject I didn't see much point in sticking them in a cpp.
As there doesn't seem to be a real consensus on this about the implementation, and I don't think any of the current SC are knowledgeable enough to step in to take a shot at a implementation that assuages concerns, I'm going to put this down for 3.7 instead of 3.6 to give a bit more time to be percolated on as it doesn't seem to be game breaking, just rather inconvenient.