[...]
- ReceiveCallback calling beginReceive again: I do this but not exactly
how
you illustrate, I first check if there is more data to receive and only
do
it then. But I now understand what you are saying about always being
ready
to receive (calling beginReceive immediately inside the ReceiveCallback).
This seems like an important modification I need to consider. But it has
a
domino effect on my whole structure, of course.
Assuming you are using the same receive callback for each BeginReceive(),
then I don't see what the difference is. If you are using different
receive callbacks depending on the state of your connection, then yes...I
can see that you would have a hard time posting a new receive before
you've processed the current one. But then, I'd suggest that's not a good
design anyway.
If i'm understanding this
correctly (a big 'if' hehe), then I would need to change my 'protocol' by
embedding 'control' information in order to keep things in order.
This I definitely don't feel is true. How you receive the data should not
affect what is in the protocol at all. If you are currently using
different receive callbacks depending on where you are in the protocol
"conversation", then yes...that would have to change. But I think it
should anyway. Other than that, nothing else would need to change.
If you are using different receive callbacks depending on the state of the
conversation, then you are essentially maintaining that state in code.
IMHO, it is better to be data-driven, and to use your data structures to
maintain the state. If you do that, then you can use a single receive
callback to process all inbound data. What that callback does at any
given moment would depend on the state of your data structures, and it
would never be a problem to have a receive posted and ready to process
incoming data.
For
example: currently, i'm just sending back and forth the 'commands',
'parameters' needed by those commands, and the 'results' of running those
commands. The 'control' of this happening is within my programs
sequential
execution, whereas what I envision what you are saying is more or less a
perpetual sending/receiving motion where within the data received would
lie
what it is, what to do with it, where you are in a multi-step process
etc...
Having a sequential process that you expect your application protocol to
follow doesn't rule out using a single receive callback, and always
posting a new receive as soon as you start processing the current one.
The sequence of commands should be tracked in your data structures, and by
doing so you can use the same code to process any command.
to try to illustrate (what I'm doing now) (just the connection-specific
stuff, client-server communication, assume this is CommandA which has a
few
steps, assume everywhere I say 'wait' i mean using an autoResetEvent, all
sends and receives use the async begin*)
- client sends commandA and waits for confirmation from server of its
receipt
The client can call BeginReceive() as soon as its connected
(BeginConnect() completes). There is no need to delay that until after
sending "commandA". The posted receive won't be completed until after the
command has been sent and replied to, of course, but there's no harm in
being ready to receive beforehand.
- server receives command and after verifying it can work with it, send
back
confirmation of it's receipt and waits for next part
The server can call BeginReceive() as soon as it's connected
(BeginAccept() completes). This is perhaps more obvious since in your
application protocol it appears that the client initiates the
communications. But even if the server started things, it could still
call BeginReceive() as soon as it's connected.
As the very first thing in the receive callback, it would call another
BeginReceive(). Assuming it's already received all the data the client
sent (including the data currently being processed), that receive wouldn't
complete until after the server gets a chance to reply ("confirmation of
its receipt"), but it's not harmful to have the receive posted and
waiting.
- client receives confirmation of command's receipt from server so *now
knows* it can send, lets just say, part2 of commandA, it sends and waits
for
confirmation
In the client data structure, it should of course keep track of where in
the conversation it is. If the next thing to do is wait for confirmation
of the receipt of "commandA" and then send "part2" of "commandA", then the
data structure should reflect that somehow. Then when it completes
another receive, it knows what to do with that.
- server receives part2 of commandA and sends back confirmation of
receipt
and begins executing commandA
- client receives server's confirmation of receipt of part2 of commandA
and
waits for results
- server finishes executing and send results back to client
- client receives results
As above, assuming the state of the conversation is maintained in each
data structure (client and server), then the client and server simple take
the appropriate action according to the state of the conversation. Having
an extra receive (or several) posted isn't a problem.
You can have as many "parts" or "commands" or whatever as you like. It
doesn't matter...you can still keep track of the sequence in data and then
the code requires just a single receive callback that does particular
things based on the state of the conversation.
Note that nothing about the above requires a change to the application
protocol itself. Just the implementation of the code that handles the
protocol.
assume there are some commands that may have more than 2 parts (trying to
keep example short). The 'control' in this case is my code on both sides
sequentially going through those steps. Now, with this perpetual
send/receive machine (that sounds cool) I'm envisioning the 'control'
having
to be within the data sent so that it would be something like:
- server receives data, breaks it down per the protocol determining that
it
is part2 of commandA already in progress, and proceeds appropriately.
Like a
big switch statement based on the 'command' part of the protocol, then
within that case, potentially another switch statement or other control
structure for the particular 'part' of the command or where in the
commands
total process the data belongs. This way there is no waithandles and
control
is removed from the thread where it was and placed in the protocol,
allowing
this 'perpetual machine' to run.
does this sound like I'm getting it?
Almost. The main problem in the above is that you are assuming that you
need to change the application protocol so that it includes the "state"
information within it, when in fact the state information can be kept
locally in data structures.
If you want a protocol where various commands can be sent at random times,
then of course you would need some sort of data within the protocol to
tell you what command you're dealing with at any given time. But if the
protocol is strictly a sequential conversation, that state can be easily
maintained within the client and server data structures, rather than being
sent explicitly over the network.
Now, all that said, a couple of other wrinkles you may not be aware of:
* Any given receive may result in any number of bytes between 1 and
the total number of bytes sent but not yet received. This means that in
your receive callback (whether you have many or just one), you may or may
not receive enough data to complete a "command". You need to include
logic to keep track of what command you're working on, and how much of it
you've received so far, so that as you receive new data you can append it
to the command you're working on currently, and correctly detect when
you've actually received enough data to process (that is, the "command"
has been completed).
* Perhaps more confusingly, while it is not a problem to have multiple
receives posted via BeginReceive(), you do need to keep in mind that the
callbacks can be executed in any order for any receive that has completed,
due to threading issues. The buffers will be filled in the order in which
you post them; that much is guaranteed. However, it is possible to have a
BeginReceive() that you posted second, execute its callback first. That
is, you would wind up running the code in the callback that actually
processes the data for that buffer before the code in the same callback
that processes the data for the first buffer.
In the second issue, the usual implementation for dealing with it would be
to maintain a list of the buffers you've posted for receiving, flagging
them as completed when you get the callback, and treating the list as a
queue for the purpose of processing the data, locking it before processing
anything, and only processing data from the beginning up to but not
including the first one that has _not_ been marked as completed. Doing so
will result in a buffer being marked as completed in one callback thread,
but possibly being actually processed in a different one. For example,
suppose you have two buffers posted for a receive, and the callback for
the second buffer is actually called first:
The callback code:
lock the queue
mark buffer as received
while the first buffer in the queue exists and is marked received
{
process the buffer
}
unlock the queue
What each thread does:
Buffer 1 Buffer 2
-------- --------
lock the queue
mark the buffer as received
first buffer (Buffer 1) in the queue
isn't marked yet, so loop doesn't execute
unlock the queue
lock the queue
mark the buffer as received
first buffer (Buffer 1) in the queue is now marked, so process it
second buffer (Buffer 2) in the queue is also marked, so process it as
well
no more buffers, so exit loop
unlock the queue
The first issue above is mandatory. You absolutely have to deal with it.
The second is not, and if you find it too confusing, that's a good reason
to _not_ post a new receive as the very first thing in the receive
callback, and rather to post it after you've finished processing the
current buffer.
Note that you still don't need event handles; the only difference is where
in the processing of the current receive event do you wind up posting a
new buffer for receiving. Doing it first is more efficient, but more
complicated. For simplicity's sake you may prefer to do it last, after
you're done processing the current data. Note that doing so doesn't
change _whether_ you post a new receive (ie you will always post a new
receive regardless), nor does it change _how_ you post that new receive
(ie you will always post the same size buffer, or perhaps even the same
exact buffer in this case, you posted before). It only changes _where_
you post the new receive.
well, either way, I appreciate your help. Sorry my posts are so long but
I
don't know any other way to get out my thoughts/questions. I realize
what I
really should do is go get a good book specifically on async network
programming for .net.
I don't know if there is one. You may find Winsock books useful, but even
there I'm not aware of any that are considered _great_. "Network
Programming for Microsoft Windows, Second Edition" by Ohlund and Jones, as
well as "Windows Sockets Network Programming (Addison-Wesley Advanced
Windows Series)" by Quinn and Shute may be useful to you, but they aren't
specific to .NET and in fact the topics that would be most applicable to
.NET would be those regarding i/o completion ports, and frankly because
.NET simplifies the use of i/o completion ports so much (in a good way),
I'm not convinced that learning all the intracacies of doing them in
Winsock is necessary.
Pete