Home Search Contact us About us
Title Sockets without MFC
Summary This is a Socket class based on the WinSock2 API. It is quite simple to use and uses asynchronous calles to enable smooth program flow. A simple client and server are provided for simple use.
Contributor John McTainsh
Published 1-Dec-2000
Last updated 5-Dec-2000
Page rating   93% for 29 votes Useless Brilliant
 Download Client and Server files - 31 Kb

Description.

Sockets are a simple way to communicate between computers and processes without the hassle of security and registration. You simply create a client and a server and away you go. This tutorial shows how to use my CWinSock2Async or Microsoft's CAsyncSocket. Both are similar but CWinSock2Async does not require MFC support so can be used in ATL controls, Services and other areas where MFC is not required.

How this tutorial is works.

We will create two applications, one a server and one a client. The server will start and listen for connections. The client will connect to the server to send and receive data. The sample applications and the CWinSockAsync files are included in the downloads.

Project settings.

It is necessary to add the following include files to a global header such as StdAfx.h. Note only the WinSock2.h is necessary if you are using MFC. You MUST also add the Ws2_32.lib to be linked into your application. To do this select Project->Settings to open the Project settings dialog. Select the Link tab and from the drop down box select the General Category. In the Object/Link modules add Ws2_32.lib.
    //Needed for CWinSock2Async
    #include <winsock2.h>
    #include "WinSock2Async.h"
    #include <windows.h>       //Not necessary if using MFC
    #include <TChar.h>         //..
    #include <assert.h>        //..

Process flow.

The basic socket process (without errors) is;

Details of the process flow are described in later sections.

Create Client.

Create a new project or open an existing project. Put WinSock2Async.h and WinSock2Async.cpp in the project directory.

Add WinSock2Async.cpp to the project work space.

Create a new class CSockClient derived from CWinSock2Async. From the menu select Insert->New Class ..., set the Name as CSockClient and Base class of CWinSock2Async, public.

Add the following prototypes to SockClient.h

//Event handlers
private:
    void OnRecieve( int nError );
    void OnSend( int nError );
    void OnClose( int nError );

Implement the On... handlers to display a message about each event in the SockClient.cpp file as follows

 
// ***************************************************************************
//DESCRIPTION:
//      An FD_WRITE network event is recorded when a socket is first connected 
//      with connect/WSAConnect or accepted with accept/WSAAccept, and then 
//      after a send fails with WSAEWOULDBLOCK and buffer space becomes 
//      available. Therefore, an application can assume that sends are 
//      possible starting from the first FD_WRITE network event setting and 
//      lasting until a send returns WSAEWOULDBLOCK. After such a failure the 
//      application will find out that sends are again possible when OnSend 
//      is fired.
//PARAMS:
//      nError    Error code
//CREATED:
//      26-11-2000, 19:46:38 by john@mctainsh.com
// ***************************************************************************
void CSockClient::OnSend( int nError )
{
    TRACE( _T("CSockClient::OnSend( %d )\n"), nError );
}


// ***************************************************************************
//DESCRIPTION:
//      The FD_CLOSE network event is recorded when a close indication is 
//      received for the virtual circuit corresponding to the socket. In TCP 
//      terms, this means that the FD_CLOSE is recorded when the connection 
//      goes into the TIME WAIT or CLOSE WAIT states. This results from the 
//      remote end performing a shutdown on the send side or a closesocket. 
//      FD_CLOSE being posted after all data is read from a socket. An 
//      application should check for remaining data upon receipt of FD_CLOSE 
//      to avoid any possibility of losing data.
//PARAMS:
//      nError    Error code
//CREATED:
//      26-11-2000, 16:23:21 by john@mctainsh.com
// ***************************************************************************
void CSockClient::OnClose( int nError )
{
    TRACE( _T("CSockClient::OnClose( %d )\n"), nError );
    //Check for any remaining data
    char chBuff[WINSOCK_READ_BUFF_SIZE+1];
    while( Receive( chBuff, WINSOCK_READ_BUFF_SIZE ) )
    {
        //TODO : Process the read data.
    }
}

Note how OnClose empties the buffer before exiting. Now implement the OnRecieve event to read in any data. Note, a read must be performed to ensure further events. This is where you process the incoming data.

// ***************************************************************************
//DESCRIPTION:
//      Received data event.
//      For FD_READ network event, network event recording and event object 
//      signalling are level-triggered. This means that if Receive() routine
//      is called and data is still in the input buffer after the call, the 
//      FD_READ event is recorded and FD_READ event object is set. This allows 
//      an application to be event-driven and not be concerned with the amount 
//      of data that arrives at any one time
//      With these semantics, an application need not read all available data 
//PARAMS:
//      nError    Error code
//CREATED:
//      26-11-2000, 16:54:46 by john@mctainsh.com
// ***************************************************************************
void CSockClient::OnRecieve( int nError )
{
    TRACE( _T("CSockClient::OnRecieve( %d )\n"), nError );
    char chBuff[WINSOCK_READ_BUFF_SIZE+1];
    int nRead;
    while( ( nRead = Receive( chBuff, WINSOCK_READ_BUFF_SIZE ) ) > 0 )
    {
        //TODO: Use the data here...
        //Display the input data
        TRACE( _T("Recieved( ")  );
        for( int nPos = 0; nPos < nRead; nPos++ )
            TRACE( _T("%c"), chBuff[nPos]  );
        TRACE( _T(" )\n") );
    }
}

Now create a socket object and connect to the server. Do this where it will not go out of scope during use. A member attribute is suitable.

#include "SockClient.h"
....
    if( CSockListener::SocketInit() )
        //TODO : Some error handling
....
    CSockClient m_sock;
....
    if( !m_sock.Create() ||
        !m_sock.Connect( _T("127.0.0.1"), 1055 ) )
    {
        TCHAR szErrorMsg[WSA_ERROR_LEN];
        CSockClient::WSAGetLastErrorMessage( szErrorMsg );
        TRACE( _T("Socket Create/Connect failed Error: %s"), szErrorMsg );
        return -1;
    }
...
    //When it is time to send
    if( m_sock.IsConnected() )
        m_sock.Send( "Hello", 5 );

Create Server.

Create a new project or open an existing project. Put WinSock2Async.h , WinSock2Async.cpp, SockClient.cpp and SockClient.h from the client project into this project directory.

Add WinSock2Async.cpp and SockCient.cpp to the project work space.

From the menu select Insert->New Class ..., set the Name as CSockListener and Base class of CWinSock2Async, public.

Add the following prototypes to the SockListener.h file.

... Before the class definition
#include "SockClient.h"

... In the class definition

//Event handlers
private:
    void OnAccept( int nError );

//Attributes
private:
    CSockClient* m_psockClient;    //Client connection

Implement the OnAccept handler to create a client connection and return it to communicate on. Note: this implementation allows only a single client at any one time. If another client attempts to connect the previous client will be disconnected.

// ***************************************************************************
//DESCRIPTION:
//      Called by the framework to notify a listening socket that it can 
//      accept pending connection requests by calling the Accept member 
//      function.
//PARAMS:
//      The most recent error on a socket. The following error codes 
//      applies to the OnAccept member function: 
//       0            The function executed successfully.
//       WSAENETDOWN  The Windows Sockets implementation detected 
//                    that the network subsystem failed. 
//CREATED:
//      2-12-2000, 7:10:28 AM by john@mctainsh.com
// ***************************************************************************
void CSockListener::OnAccept( int nError )
{
    TRACE( _T("CSockListener::OnAccept(%d)\n"), nError );
    //Close of the old connection and create a new one
    if( m_psockClient )
        delete m_psockClient;
    Sleep( 1 );

    //Create the new connection
    m_psockClient = new CSockClient;
    if( Accept( m_psockClient ) == 0 )
    {
        char szErrMessage[WSA_ERROR_LEN+1];
        WSAGetLastErrorMessage( szErrMessage );
        TRACE( _T("ERROR on Accept %s\n"), szErrMessage );
        return;
    }

    //Display connection details
    char szAddress[17];
    int nPort;
    if( m_psockClient->GetPeerName( szAddress, &nPort ) == 0 )
        TRACE( _T("Connected to %s on port %d\n"), szAddress, nPort );
}

... Also Modify the CSockClient::OnRecieve as follows
void CSockClient::OnRecieve( int nError )
{
    TRACE( _T("CSockClient::OnRecieve( %d )\n"), nError );
    char chBuff[WINSOCK_READ_BUFF_SIZE+1];
    int nRead;
    while( ( nRead = Recieve( chBuff, WINSOCK_READ_BUFF_SIZE ) ) > 0 )
    {
       //TODO: Use the data here...
       //Modify the data and echo
       for( int nPos = 0; nPos < nRead; nPos++ )
       {
           TRACE( "%c ", chBuff[nPos] );
           chBuff[nPos] = chBuff[nPos] + 1;
       }
       Send( chBuff, nRead );
    }
}

Now setup the listener in your applications initialisation routine.

#Include "SockListener.h"

...

    //Initialise Winsock API
    if( CSockListener::SocketInit() )
    {
        printf( _T("*** WinSock API could not be initialised\n") );
        return -1;
    }

    //Create a simple listener
    CSockListener sockListener;

    if( !sockListener.Create( 392 ) )   //enter port to listen in here
    {    
        char szWASError[WSA_ERROR_LEN];
        sockListener.WSAGetLastErrorMessage( szWASError );
        printf( _T("*** Create Error: %s\n"), szWASError );
        return -1;
    }

    if( !sockListener.Listen() )
    {    
        char szWASError[WSA_ERROR_LEN];
        sockListener.WSAGetLastErrorMessage( szWASError );
        printf( _T("*** Listen Error: %s\n"), szWASError );
        return -1;
    }

That should be it. Now just test our application be running telnet. From the command line type telnet 127.0.0.1 392. It should start and echo the characters you type in plus 1. Start another telnet session and the old one should terminate..

Things to watch out for.

Sockets are simple to use but do require attention in the following areas;

Reuse our code

In the sample code I have duplicated the Client and Listener code all over the place. If our are writing both the client and server share as much code a possible.

Data is not packets

Developers often fall into the trap off assuming data sent with a single Send() will be result in a single OnRecieve(). This is NOT guaranteed to occur, and must assume packets will be broken up or merged together.

Connection loss is not always detectable

The OnClose() event can be used to detect the remote machine shutting down only if network connection between the machines is available and the process terminates correctly. For this reason it is often advisable to maintain a ping or heartbeat message between the clients to ensure both machines are functioning correctly.

Don't try to reuse sockets

If connection is closed or lost for any reason, do not try to reopen the connection with the same socket object. It is best to create the objects on the heap with new and delete them once they are done with. If you need to reconnect. delete the object and use new to create a new one before reconnecting.

Local machine

It is handy to test and often connect to a server that is running on the same machine as the client this is know as the localhost or IP address 127.0.0.1.

Resolving address names takes time

Most operations are non-blocking, however when a connection is attempted the address must be resolved from a name to a 4 digit IP. If a Domain Name Server is not available this process can block for 30 seconds or more. To avoid this always use 4 digit IP addresses rather than domain names.

Comments Date
Binding is just wrong 30-Oct-2003 sqrly
In CWinSock2Async::Create() use this. The error caused the last commentor to have problems to.

//
//Setup for listening if necessary
//
//TCHAR szHostName[32];
//gethostname(szHostName, 32);
//u_long nRemoteAddress = LookupAddress(szHostName);
//u_long nRemoteAddress = LookupAddress(`127.0.0.1`);

/*
if (nRemoteAddress == INADDR_NONE)
{
TCHAR szWASError[WSA_ERROR_LEN];
WSAGetLastErrorMessage( szWASError );
_tprintf( _T(`*** lookup address: %s\n`), szWASError );
return false;
}
*/

sockaddr_in sinRemote;
sinRemote.sin_family = AF_INET;
sinRemote.sin_addr.s_addr = INADDR_ANY; //nRemoteAddress;
sinRemote.sin_port = htons((unsigned short)nPort);
Binding is just wrong 30-Oct-2003 sqrly
The key bit was to use INADDR_ANY.
Nice demo 30-Oct-2003 sqrly
While there were a couple of problems, I like it and will continue to use it. It is nice not having to use MFC!!
Handle leak 2-Jun-2004 Lev Elbert
Code has a handle leak. Namely: CtreateThread returnes a hanle to the thread, which is ignored in the code.
DWORD dwThreadId;
CreateThread(
NULL, // SD
102400, // initial stack size
StartPlayingThread, // thread function
this, // thread argument
0, // creation option
&dwThreadId ); // thread identifier

Should be:

HANDLE hThreadHandle = CreateThread(....); // error checking is ommited!!!
CloseHandle(hThreadHandle);

Home Search Contact us About us