views:

181

answers:

3

Hi!

I think I'm in a problem. I have two TCP apps connected to each other which use winsock I/O completion ports to send/receive data (non-blocking sockets).

Everything works just fine until there's a data transfer burst. The sender starts sending incorrect/malformed data.

I allocate the buffers I'm sending on the stack, and if I understand correctly, that's a wrong to do, because these buffers should remain as I sent them until I get the "write complete" notification from IOCP.

Take this for example:

void some_function()
{
    char cBuff[1024];

    // filling cBuff with some data

    WSASend(...); // sending cBuff, non-blocking mode

    // filling cBuff with other data

    WSASend(...); // again, sending cBuff

    // ..... and so forth!
}

If I understand correctly, each of these WSASend() calls should have its own unique buffer, and that buffer can be reused only when the send completes.
Correct?

Now, what strategies can I implement in order to maintain a big sack of such buffers, how should I handle them, how can I avoid performance penalty, etc'?
And, if I am to use buffers that means I should copy the data to be sent from the source buffer to the temporary one, thus, I'd set SO_SNDBUF on each socket to zero, so the system will not re-copy what I already copied. Are you with me? Please let me know if I wasn't clear.

A: 

I don't think it is a good idea to do a second send before the first send is finished.

Similarly, I don't think it is a good idea to change the buffer before the send is finished.

I would be inclined to store the data in some sort of queue. One thread can keep adding data to the queue. The second thread can work in a loop. Do a send and wait for it to finish. If there is more data do another send, else wait for more data.

You would need a critical section (or some such) to nicely share the queue between the threads and possibly an event or a semaphore for the sending thread to wait on if there is no data ready.

Michael J
"I don't think it is a good idea to do a second send before the first send is finished" <<-- seem to be correct. Sadly, this gives a lot of overhead to the overall operation.
Poni
A: 

Now, what strategies can I implement in order to maintain a big sack of such buffers, how should I handle them, how can I avoid performance penalty, etc'?

It's difficult to know the answer without knowing more about your specific design. In general I'd avoid maintaining your own "sack of buffers" and instead use the OS's built in sack of buffers - the heap.

But in any case, what I would do in the general case is expose an interface to the callers of your code that mirror what WSASend is doing for overlapped i/o. For example, suppose you are providing an interface to send a specific struct:

struct Foo
{
   int x;
   int y;
};

// foo will be consumed by SendFoo, and deallocated, don't use it after this call
void SendFoo(Foo* foo);

I would require users of SendFoo allocate a Foo instance with new, and tell them that after calling SendFoo the memory is no longer "owned" by their code and they therefore shouldn't use it.

You can enforce this even further with a little trickery:

// After this operation the resultant foo ptr will no longer point to
// memory passed to SendFoo
void SendFoo(Foo*& foo);

This allows the body of SendFoo to send the address of the memory down to WSASend, but modify the passed in pointer to NULL, severing the link between the caller's code and their memory. Of course, you can't really know what the caller is doing with that address, they may have a copy elsewhere.

This interface also enforces that a single block of memory will be used with each WSASend. You are really treading into more than dangerous territory trying to share one buffer between two WSASend calls.

Doug T.
he heap is exactly what I'm about to use, as it seems. As said, currently I send a buffer which is on the sending thread's stack, and it doesn't work well. Using new/delete is a real problem when having thousands of WSASend() calls in a second, so, I'll pool them. Get from the pool when needed, and release when needed. As for Foo struct, I'll flag it if it's being used or not, so, no problem with that.
Poni
What really bothers me is the fact that I'll need some sort of mutual exlusion mechanism in order to manage that pool of buffers, which slows things down. By the way, the heap also has a mutual exlusion mechanism. "You are really treading into more than dangerous territory trying to share one buffer between two WSASend calls" <<-- that's what I was thinking and this is why I posted this question.
Poni
+2  A: 

Take a serious look at boost::asio. Asynchronous IO is its specialty (just as the name suggests.) It's pretty mature library by now being in Boost since 1.35. Many people use it in production for very intensive networking. There's a wealth of examples in the documentation.

One thing for sure - it take working with buffers very seriously.

Edit:

Basic idea to handling bursts of input is queuing.

  • Create, say, three linked lists of pre-allocated buffers - one is for free buffers, one for to-be-processed (received) data, one for to-be-sent data.
  • Every time you need to send something - take a buffer off the free list (allocate a new one if free list is empty), fill with data, put it onto to-be-sent list.
  • Every time you need to receive something - take a buffer off the free list as above, give it to IO receive routine.
  • Periodically take buffers off to-be-sent queue, hand them off to send routine.
  • On send completion (inline or asynchronous) - put them back onto free list.
  • On receive completion - put buffer onto to-be-processed list.
  • Have your "business" routine take buffers off to-be-processed list.

The bursts will then fill that input queue until you are able to process them. You might want to limit the queue size to avoid blowing through all the memory.

Nikolai N Fetissov
I wouldn't say "wealth of examples" (: But yes, I've been wondering maybe I'm just "re-inventing" the wheel here and I better use ASIO. The question is if ASIO can handle a burst of data to be sent (and received). I'll probably have to write a mini-test client/server apps just to check that.... and of course, if you (or anyone else here) know the answer already or can give some pointers - I'd be EXTREMELY grateful!
Poni
Hmm, compared to other docs I've seen - yes, 73 distinct examples is *a lot*.
Nikolai N Fetissov
Thanks Nikolai I appriciate your support here. Well, I already have a to-be-processed queue. Also a free-pre-allocated-buffers queue. I do not have a to-be-sent queue, and that's because, and correct me if I'm wrong, I may post many "send" operations at once, and let the transport layer (winsock) really send them in the posting order, considering the ACKs it gets from the remote machine. Correct, or not? Tests show it's incorrect, and that's when I'm starting to question the whole "scalability" part of IOCP!
Poni
Just added: look at the bottom lines at the 'buffers' link you've provided; I quote: 'When you call an asynchronous read or write you need to ensure that the buffers for the operation are valid until the completion handler is called.'. So, how do you ensure that? For example, I know for sure that my "read" buffers are valid - never deallocated (app's life time, one per connection) and never used by anyone, except when a read operation is complete.
Poni
I also, as mentioned already, have "write" buffers which I post to winsock, and they are being pooled back only when the "write completed" notification has been arrived.
Poni
By the way, if I'm gonna have to pool these buffers with ASIO too then it gives me nothing new. What can I do, re-invented the wheel already (:
Poni
Well, I'm not a Windows guy, so can't comment on IOCP - but if the documentation doesn't explicitly say that async sends are done in-order, don't rely on it, do your own. Also if you use threads, all bets are off on the ordering - the outbound queue is a necessity.
Nikolai N Fetissov
So far so good, yet how would I determine the amount I can send the other side at a given time without flooding it? I mean, how do I know what is the limit to send at a time so that I will only queue the next send request(s)? Do I have to manually set it? Is there some indication? Some hint the receiver would give me?
Poni
TCP will rate-limit the sender. You don't have to worry about it.
Nikolai N Fetissov
Nikolai thank you very much. I understand now, yet I'm not sure how to get down to details. I've posted another question ( http://stackoverflow.com/questions/3034047/limiting-tcp-sends-with-a-to-be-sent-queue-and-other-design-issues ) and you're more than welcome to follow it and contribute your expertise. Thank you again.
Poni