Prev: Left/Right keys don't work in CDockablePane
Next: How to read text from a status bar (CStatus) of another application ?
From: TomChapman on 13 Nov 2009 17:36 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 14 Nov 2009 13:19
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 |