Re: CSocket/CAsyncSocket sending and receiving

From: Joseph M. Newcomer (newcomer_at_flounder.com)
Date: 02/23/05


Date: Wed, 23 Feb 2005 12:36:11 -0500

Stop experimenting with CSocket; it is losing. Not only is it a bad paradigm, but
apparently the MFC support for CSocket is buggy. So forget completely that CSocket exists.

Lose the GetDlgItem. It is so rare that this makes sense in MFC that the simple rule is
"never use it" and when the very rare exceptions arise, you will know what they are. Read
my essay on Avoiding GetDlgItem on my MVP Tips site.

You have not shown the critical part of the code, which is how you opened the socket and
established the connection.

OnReceive only applies to CAsyncSocket, so I'm presuming you are using an asynchronous
sock in your client.

Note that Receive receives bytes, so it should be

TCHAR buff[4096];
int nRead = Receive(buff, sizeof(buff));

Whether you choose to interpret this as 8-bit or 16-bit characters depends on how you sent
the data. By declaring it as TCHAR, you require that the sender and receiver are using the
same protocol.

If you think you are only sending 8-bit characters, this is one of the very few places in
code where you will actually write "char" as a datatype. If the app is Unicode, the
CString constructor will do a MultiByteToWideChar to convert the string.

However, there is no guarantee that if you send 4096 bytes that you will receive 4096
bytes; you will get the message in whatever way it was fragmented by the sending logic,
and assembled by the receiving logic, and any correlation between the length sent by a
single Send and the length received in a single Receive is at best tenuous. If you are
using UDP, you have no idea how much you might receive, if anything. There is no
obligation for anything along the way to transmit more than the minimum UDP message size
(536 bytes, including the header, limiting the payload to 512 bytes). For TCP/IP, you are
guaranteed that if, through a sequence of Send operations, you have sent N bytes, then a
sequence of Receives will receive N bytes. But 100 Sends might translate to five Receives,
or one Send to 100 Receives, and there is no way to know.

This is why it is most common to preface each message in TCP/IP with a header which gives
the message length. You can either use a binary value or a text value. I;ve used both
techiques. In one protocol, we knew the strings would always be < 65K, so I transmitted 2
bytes of length, IN NETWORK BYTE ORDER, read them, convert back to host order, and then
read that many bytes. Note, however, that since I used 2 bytes, there was no guarantee
that I would receive more than one of them in a given Receive, so I had to use a Finite
State Machine model to parse the input. I started in the "read length" state. I tried to
read 2 bytes. If I got 2 bytes, I progessed to the "read data" state and read that many
bytes. If I received only 1 byte, I went into the "read next length byte" state, and read
only 1 additional byte. These two bytes then formed the length. Then I went into the "read
data" state and continued reading data (through some sequence of OnReceive calls) until
I'd filled up the buffer. In another application, where we were using a VB client and
couldn't send binary data, we set a limit of 9999 bytes to a message and sent the length
as a sequence of characters followed by a semicolon, e.g.
        00005;Hello0006; World
would have been a pair of valid messages. My states were "read message", "read semicolon"
and "read message"

No let's revisit that message type. You could have the server free to send either Unicode
or ANSI sequences, and you might do something like
        00005;1;Hello;0010;2;*H*e*l*l*o
note that to parse this you have to add more states to handle parsing each of the 4 length
bytes, the semicolon, the byte size, its semicolon, and the data.

BYTE buff[4096];
Receive(buff, sizeof(buff)); // representative of the Receive loop sequence...

(Note that normally your buffer, byte count, position, etc. are all kept as class members
of the CAsyncSocket-derived subclass you are working in)

if(bytesize == 1)
      CString s((LPTSTR)buff);
else
if(bytesize == 2)
      CString s((LPWSTR)buff);

then you could mix and match character widths of sender and receiver. A Send would send
sizeof(TCHAR) and it would send the number of bytes, e.g.,

void CMyAsyncSocket:::DoSoend(const CString & s)
{
 CStringA t;
 t.Format(_T("%04d;%d;"), s.GetLength() * sizeof(TCHAR), sizeof(TCHAR));
[this assumes you are using MFC 7, which supports CStringA and CStringW as specific types.
Note that the length is the length in bytes, and the sequence is transmitted as 8-bit
bytes independent of the mode of the content]

Send((LPCSTR)t, t.GetLength()); // sends 8-bit bytes for header

Send((LPCTSTR)s, s.GetLength() * sizeof(TCHAR));

                        joe
On 23 Feb 2005 06:07:33 -0800, trillenium3000@hotmail.com (Rocco) wrote:

>Hi,
>
>I am experimenting with using the CSocket (and CAsyncSocket) class to
>build a small instant messaging type program for a school project. At
>the moment I'm still at the very early stages of it all and i'm just
>trying to get used to the CSocket class.
>
>I've created 2 projects, one is a server and one a client. So far i
>have managed to connect and accept the connection, however whenever i
>try to Send a string it does not seem to be receiving the string.
>
>Here is the code used in the button for sending the string:-
>
>void CClientExampleDlg::OnSendButton()
>{
> //Get the value from the send text edit box
> //and make the edit box blank again
> CString sText;
> CEdit* pSendText = (CEdit*)this->GetDlgItem(IDC_SEND_TEXT);
> pSendText->GetWindowText(sText);
> pSendText->SetWindowText("");
>
> //Send it..
> int nResult = m_myClientSocket->Send(sText, sText.GetLength());
>
> CString ReturnValue;
> ReturnValue.Format("%d", nResult);
> AfxMessageBox(ReturnValue);
>}
>
>As far as I know, I am using the Send method correctly... and it
>returns with the number of characters in the string passed to it, which
>supposedly means that it has sent the string. So this leads me to
>believe that the problem lies with the Server side. If I am wrong tell
>me! lol..
>
>On the server side, I have overriden the OnReceive() method to try and
>receive the string when it is sent to the server.
>
>void CServSocket::OnReceive(int nErrorCode)
>{
> //Receive the string being sent to the port
> //and set it as the text in label IDI_TEXT (CStatic)
> CString sRecvString;
> TCHAR buff[4096];
> int nRead = Receive(buff, 4096);
>
> switch(nRead)
> {
> case 0:
> Close();
> break;
> case SOCKET_ERROR:
> if(GetLastError() != WSAEWOULDBLOCK)
> {
> AfxMessageBox("Error occurred");
> Close();
> }
> break;
> default:
> buff[nRead] = 0;
> CString szTemp(buff);
> sRecvString += szTemp; //sRecvString declared at start of function
>
> //Put received text in a message box
> AfxMessageBox(sRecvString);
>
> break;
> }
>
> CSocket::OnReceive(nErrorCode);
>}
>
>This should just receive the string sent and pop it up in a message
>box.. however even when running in debug mode with breakpoints in this
>method, it does not seem to be calling it.
>
>I'm lost as to what to do! Any help would be greatly appreciated..
>Thanks in advance
>
>Trill

Joseph M. Newcomer [MVP]
email: newcomer@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm