From: TomChapman on
What is the best way for implementing a CAsyncSocket derived class for
being a TCP client?

I've been using a class for a few years, but I want a better class.


I'm thinking about writing a class that would incorporate a CAsyncSocket
derived class. That CAsyncSocket derived class would run in a separate
thread? Is using a separate thread the best way?

If in a separate thread, should the class send data out of the thread
every time it gets data, using ::PostMessage to the mainfrm? Is there a
better way?

So if I'm doing PostMessage I would first "new" some memory to hold the
data, the length of the data, and the "this" pointer so I would know
what instance of my class was generating the message. I would then pass
the pointer to this memory as parameters to PostMessage. Is there a
better way?

The mainfrm will receive the message and using the "this" pointer pass
the data back to my class where I would collect data for an entire
packet. Should I place complete packets on a deque. This would allow
packets to accumulate if I was receiving a ton of packets in a short
time interval.

Then how should my code check for data available on the deque? Do I use
a very short timer to trigger my code to check if data is available? I
would then process packets from the deque. Is there a better way?
From: Joseph M. Newcomer on
See below...
On Fri, 13 Nov 2009 16:36:34 -0600, TomChapman <TomChapman12(a)gmail.com> wrote:

>What is the best way for implementing a CAsyncSocket derived class for
>being a TCP client?
>
>I've been using a class for a few years, but I want a better class.
>
>
>I'm thinking about writing a class that would incorporate a CAsyncSocket
>derived class. That CAsyncSocket derived class would run in a separate
>thread? Is using a separate thread the best way?
****
Typically, you do not need to run a CAsyncSocket in a separate thread, unless the effort
required to respond to an input packet is substantial. So you need to justify this effort
based on performance. There are times when putting a CAsyncSocket in a separate thread is
actually necessary to meet overall performance goals, including maintaining responsiveness
of the GUI. So the answer is the usual "it depends"
****
>
>If in a separate thread, should the class send data out of the thread
>every time it gets data, using ::PostMessage to the mainfrm? Is there a
>better way?
****
Yes and no. The problem with using PostMessage (and it doesn't have to be ::PostMessage;
you can pass the CWnd* into the thread and use CWnd::PostMessage) is that you can get
"message queue saturation". If you manage to get more than 10,000 messages into the
queue, the others will be thrown away (you need to check the return code of PostMessage to
determine if this is a problem). In addition, if you have only a few thousand in the
queue, then mouse clicks and character messages end up being queued behind them, so the
GUI becomes quite non-responsive. See my essay on the use of I/O Completion Ports to see
how to avoid queue saturation; it's on my MVP Tips site.

There is no reason to use the mainfrm window. In fact, there are a lot of arguments
against it. You would typically PostMessage *to the window that cares*, which is hardly
ever the main frame. So if I had a doc/view, I might create an invisible window owned by
the document (not by the view) which is the target for data from a PostMessage; that way,
even if one of the views is closed, I have a known way to get data to it. Also, you might
have several active documents and each may have several active views, and sending data to
the mainframe is pointless if it is of interest only to some particular document. The
mess of sorting out where it goes can be avoided totally by simply not sending the data to
the mainframe! One of the worst things that can happen is that you go to your mainframe
and #include all your views or documents and then call subroutines in them to handle the
data. This is always poor design. It fails elementary tests for good modularity.

Usually when I find code like this I rip it out; the result is cleaner, easier to develop,
maintain, and reason about.

Assume the mainframe is the worst possible place to send the data, and let the user of
your class specify which CWnd* is supposed to receive the data. Then you have not
precluded the user doing a sensible design. Sending data to the mainframe is rarely
sensible, but in some situations it might be appropriate.

Note that in my anti-saturation, I PostQueuedCompletionStatus to an I/O port that is owned
by the CWinApp; when the CWinApp removes an element from the queue, it has the CWnd* that
the mesage was destined for.
****
>
>So if I'm doing PostMessage I would first "new" some memory to hold the
>data, the length of the data, and the "this" pointer so I would know
>what instance of my class was generating the message. I would then pass
>the pointer to this memory as parameters to PostMessage. Is there a
>better way?
***
There is no reason you need to pass the length separately; for example, I might use a
CByteArray* to pass the data, in which case the length is implicit in the data type. Or
you might use a 'new' of something like

class Msg {
public:
DWORD IPv4Address;
CString userid;
CString hostname;
CByteArray data;
};

so there is no reason to assume that 'new' is going to just allocate a raw block of
memory; in fact, I rarely do that. I'm usually allocating some richer structure.
****
>
>The mainfrm will receive the message and using the "this" pointer pass
>the data back to my class where I would collect data for an entire
>packet. Should I place complete packets on a deque. This would allow
>packets to accumulate if I was receiving a ton of packets in a short
>time interval.
****
Why does the mainframe get involved in this at all? Think carefully before you decide
this is the only way to handle it. I would avoid using the mainframe except under rare
and exotic circumstances. Generally, I use either a view, a dialog, or a private window
owned by a document. I rarely use the mainframe. Why do you think it matters? Why do
you not consider sending the data directly to whomever wants it?
****
>
>Then how should my code check for data available on the deque? Do I use
>a very short timer to trigger my code to check if data is available? I
>would then process packets from the deque. Is there a better way?
****
Why would it need to check? The message in-and-of-itself is the announcement that data is
ready, and you don't really need to poll for anything. THat's the whole point of this:
with CAsycSocket, you work in a data-event-driven world, and you can forget that the
poll-for-data world ever existed. There is absolutely no reason to "check for data
available". For the I/O Completion Port mechanism, I read until I hit the max-packet
limit or run out of queued messages, but don't do anything about "testing". Because of
the way it works, a timer request can be used to trigger the OnIdle handler, but it is not
"testing" anything to see if data is present. Either data is present, or it isn't, and
you never, ever have to ask that question. If you think you do, you are using
asynchronous communication incorrectly.

Stop thinking that you are algorithm-oriented and data just happens; think instead where
data happens, and algorithms exist solely to handle the asynchronous events. This is a
paradigm shift, much like the one where we used to issue prompts
Name:
and wait for the user to type their name, then prompt
Address:
wait for the user to type their address, etc. Or the notion that you can paint pixels on
the screen and they will always be there; the OnDraw/OnPaint handlers are the only places
that do drawing, and they must have *all* the required information necessary to draw or
paint. Ultimately, you absolutely must stop thinking in terms of "collecting packets"
(which is an irrelevant implementation detail) and start thinking in terms of "responding
to asynchronous inputs" where the presence of a data event drives what happens next.

You can read my essay on multithreaded sockets on my MVP Tips site; it is based on a
Microsoft example that managed to get synchronization, queuing, threading, and sockets
wrong, but other than those minor defects there was nothing wrong with it.
http://www.flounder.com/kb192570.htm
joe
Joseph M. Newcomer [MVP]
email: newcomer(a)flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm