Winsock 3 
The Winsock3 Tutorial shows how to detect the presence of other applications on the network.  This program detects and displays all instances of Winsock3 running on the network.  To do this, the application must be multi-threaded.  Winsock 3 generally has 3 threads running at all times.  There is the main thread, which controls the user interface and sending data on the network.  When the application starts up, this thread broadcasts a message asking for a reply from other Winsock 3 applications running.  There is a UDP listen thread that listens for the startup broadcasts.  When a broadcast is detected, the UDP thread tries to create a TCP connection with the broadcaster.  Therefore, there must also be a TCP thread running at all times waiting to receive these connection requests. 

The "Find Hosts" button will refresh the list of other hosts found.  This will also cause all the other hosts on the network to redetect the refreshing host.  The "Kill Threads" button will terminate the listening UDP and TCP listening threads.  When the threads are not running the application will not be able to detect broadcasts or connect with other hosts.  Use the "Find Hosts" button to restart the threads and refresh the list of hosts. 

Winsock3 applications will only detect other hosts if they are broadcasting to and listening on the same UDP port.  You may have different TCP listen ports and still make a connection, provided that the UDP listen ports are the same.  Changes to the UDP and TCP listen ports are only reflected after you refresh the host list by pressing the "Find Hosts" button. 



  


Downloads 
Program Only  Winsock3.exe.  No source files. 
Program and Source Files  Winsock3 source files, project files and executable. 
Note:  You must unzip these files with subdirectories!  (example:  "pkunzip -d ws1_full.zip") 


Project Files 


res\Toolbar.bmp  
res\Winsock3.ico  
res\Winsock3.rc2  
res\Winsock3Doc.ico  
DGModeless.cpp  
DGModeless.h  
MainFrm.cpp  
MainFrm.h  
resource.h  
stdafx.cpp  
stdafx.h  
THListenUDP.cpp 
THListenUDP.h 
VWSocket.cpp  
VWSocket.h  
Winsock3.aps  
Winsock3.clw  
Winsock3.cpp  
Winsock3.h  
Winsock3.mak  
Winsock3.mdp  
Winsock3.ncb  
Winsock3.rc  
Winsock3Doc.cpp  
Winsock3Doc.h  

Like the Winsock2 Tutorial, the Winsock3 Tutorial uses a Single Document Interface (SDI) as opposed to a dialog based interface.  All of these files, with the exception of VWSocket.cpp, VWSocket.h, THListenUDP.cpp, THListenUDP.h and the Modeless Dialog files, were generated by App Wizard, using the SDI application template.  VWSocket.cpp and VWSocket.h were added to implement the form view class that we use for this application.  THListenUDP.cpp and THListenUDP.h implement the listening threads that are necessary for this application.  One line was added to stdafx.h to enable socket support.See the section below for discussions on the socket code.  A line was also changed in Winsock3.cpp to make our form view load instead of the default view. 

For details on the Modeless Dialog files, see the discussion on the Modeless Message Box


Discussion 

To understand how this application works, all we need to do is look at the files  VWSocket.cpp and THListenUDP.cpp.  As always, we need to include the Winsock header files into the project.  This is done in stdafx.h

#include <afxsock.h>  // MFC socket extensions 

We also needed to change the document template in Winsock3.cpp to load our form view instead of the default view. 

    CSingleDocTemplate* pDocTemplate; 
    pDocTemplate = new CSingleDocTemplate( 
        IDR_MAINFRAME, 
        RUNTIME_CLASS(CWinsock3Doc), 
        RUNTIME_CLASS(CMainFrame),       // main SDI frame window 
        RUNTIME_CLASS(CVWSocket)); 
    AddDocTemplate(pDocTemplate); 

CVWSocket is our form view that replaces the default view.  Simply replace it as the last parameter to the CSingleDocTemplate constructor.  First, let's take a look at the UDP and TCP threads in THListenUDP.cpp.  Each class has a thread member of type CWinThread.  This thread is created and launched by the thread class.  The thread class also has a KillThread() function to stop the execution of the thread and clean it up.  The UDP listener comes first. 

/***********************************************************************************/ 
/*************************  UDP  ***************************************************/ 
/***********************************************************************************/ 
CUDPListener::CUDPListener(int UDPPort, int TCPPort, CListBox* pListBox, CObList* pol) 
{ 
    m_bKillMe          = 0; 
    m_UDPListenPort    = UDPPort; 
    m_TCPListenPort    = TCPPort; 
    m_pConnectionList  = pListBox; 
    m_polConnections   = pol; 
} 
Standard C++ constructor.  Just initializes all the critical values.  The m_bKillMe member is used to terminate the thread.  You'll see in the controlling function below that it is in a tight loop.  When the m_bKillMe turns true, the loop will exit.  When the controling function then exits, the thread is terminated.  m_UDPListenPort is the port to listen for broadcasts on.  m_TCPListenPort is the port to attempt a TCP connection on with a remote host.  Each remote host will put what TCP port should be used in its broadcast message.  m_pConnectionList is a pointer to the list box that displays which hosts are connected.  The thread uses this pointer to add entries when new TCP connections are made.  m_polConnections is not used. 

CUDPListener::~CUDPListener() 
{ 
    KillThread(); 
#ifdef _DEBUG 
    Sleep(1000); 
#endif 
} 
This is the thread class destructor.  It calls KillThread() which terminates the thread member.  In addition, due to quirkiness of the Microsoft developer environment, an erroneous memory leak message will be reported unless you give the thread some time to die.  Therefore, in debug mode you must sleep for some arbitrary length of time and hope the thread is cleaned up before it wakes.  Otherwise the application will report memory leaks. 

void CUDPListener::CreateThread() 
{ 
    m_pWinThread = AfxBeginThread(CUDPListener::ControllingFunction, this, THREAD_PRIORITY_HIGHEST, 0, CREATE_SUSPENDED); 
} 
This simply creates a thread in suspended mode.  The following function will commence execution of the thread.  The thread's controlling function is a static function within this class called CUDPListener::ControllingFunction

void CUDPListener::RunThread() 
{ 
    if (m_pWinThread != NULL) 
        m_pWinThread->ResumeThread(); 
} 
This function begins execution of the suspended thread. 

void CUDPListener::KillThread() 
{ 
    m_bKillMe = 1; 
    closesocket(sock_Receive); 
} 
This function will kill the executing thread.  First it sets the loop parameter m_bKillMe to true, so the loop will exit next time through. Then it closes the socket that is executing the blocking call.  This causes the blocking function to return.  The controlling function will then exit because it fails the test on m_bKillMe

UINT CUDPListener::ControllingFunction(void* pParam) 
{ 
    CUDPListener* pData = (CUDPListener*)pParam; 

    while (!pData->m_bKillMe) 
    { 
        pData->ListenForNewPeers(); 
    } 

    return 89; 
} 
This is the thread's controlling function.  The thread will continue to execute until this function returns.  All this function does is repeatedly listen for new broadcasts indicating the presence of other Winsock3 applications on the network.  Note that this is a static member function.  Therefore the class data that needs to be accessed must be passed in as a pointer.  The return code is completely arbitrary. 

This is the function that listens for the UDP broadcasts of other Winsock3 applications.  It contains most of the functionality of this class.  For the most part it behaves identically to the UDP listening function that we have discussed in the prior tutorials. 
void CUDPListener::ListenForNewPeers() 
{ 
First go through the standard creation of a UDP socket and wait to recieve a broadcast. 
    SOCKADDR_IN    addr_Srv, addr_Cli; 
    char           szMessage[256] = {0}, buf[255] = {0}; 
    int            clilen; 
    int            val = 1; 

//-- Open a UDP socket  
    if ((sock_Receive = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
        AfxMessageBox("Unable to create socket"); 

//-- Fill in structure fields for binding to local host 
    memset((char *) &addr_Srv, 0, sizeof(addr_Srv)); 
    addr_Srv.sin_family      = AF_INET; 
    addr_Srv.sin_addr.s_addr = htonl(INADDR_ANY); 
    addr_Srv.sin_port        = htons(m_UDPListenPort); 

//-- Bind it 
    if (bind(sock_Receive, (sockaddr*)&addr_Srv, sizeof(addr_Srv)) < 0) 
        AfxMessageBox("Error:  bind() failed."); 

//-- Prepare Client address to receive new data 
    memset(&addr_Cli, 0, sizeof(addr_Cli)); 
    clilen = sizeof(addr_Cli); 

//-- Receive a message (this blocks) 
    if (recvfrom(sock_Receive, szMessage, 256, 0, (sockaddr*)&addr_Cli, &clilen) == SOCKET_ERROR) 
    { 
        if (m_bKillMe) 
        { 
        //-- No problem, Application is simply exiting.  This object is in process of  
        //-- being destroyed.  Get out!  (BTW, this is normal) 
        } 
        else 
        { 
        //-- Something bad happened.  Cause this function to terminate.  
        //-- Controlling function will start it over. 
            AfxMessageBox("Error:  recvfrom() failed."); 
        //-- Give user a chance to exit between error messages 
            Sleep(1000); 
        } 
    } 
    else 
    { 
Now we have recieved legitimate data.  We need to ensure that it came from another Winsock3 application. 
    //-- We have recieved legitimate data.  Process it. 
    //-- First make sure it didn't come from itself! 
        unsigned int   uiFrom, uiThis; 
        int            port; 
        CString        csTemp; 
  
Attempt to pull the TCP connect port out of the broadcasted message.  If the message is indeed from a Winsock3 application, this will succeed. 
    //-- Try to extract a port number 
        csTemp = szMessage; 
        csTemp = csTemp.Right(csTemp.GetLength() - csTemp.Find("[") - 1); 
        csTemp = csTemp.SpanExcluding("[]"); 
        port = atoi(csTemp.GetBuffer(0)); 
        csTemp.ReleaseBuffer(-1); 

    //-- Make sure that 1) the message is not from this machine and  
    //-- 2) that it is from another Winsock3 app 
        uiFrom = CVWSocket::uGetIPFromSockAddr_In(&addr_Cli); 
        uiThis = CVWSocket::uGetLocalIP(); 
        if (uiFrom != uiThis) 
Winsock3 applications will always broadcast the same message, make sure it matches! 
            if (strncmp(szMessage, "Winsock3:  New Peer, Requesting TCP Connection.", 47) == 0) 
            { 
Okay, now we know this message is announcing the presence of a remote Winsock3 application.  We also have the port number of its TCP listen thread.  Now all that's left is to make the connection. 
            //-- Make a TCP connection to the originator of the message 
                while (!MakeTCPConnection(CVWSocket::uGetIPFromSockAddr_In(&addr_Cli), port)) 
                { 
                    AfxMessageBox("TCP connect() Attemp Failed"); 
                    Sleep(1000); 
                } 
We have achieved the new connection.  Add it to the the list of connections on the screen. 
            //-- We have a new TCP connection 
                csTemp = CVWSocket::IPString(CVWSocket::uGetIPFromSockAddr_In(&addr_Cli), buf, 255); 
                m_pConnectionList->AddString(csTemp);  
            } 
    } 
    closesocket(sock_Receive); 
} 

This function is just makes a TCP connection to a remote host iven an address structure and a port.  It follows the same procedures that are explained in previous tutorials. 
BOOL CUDPListener::MakeTCPConnection(unsigned int uiDestAddr, int TCPPort) 
{ 
    SOCKADDR_IN  addr_Dest; 
    SOCKET       sock_Send; 

//-- Create TCP socket 
    if ((sock_Send = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
        return 0; 

//-- Fill in target addr 
    memset((char *) &addr_Dest, 0, sizeof(addr_Dest)); 
    addr_Dest.sin_family      = AF_INET; 
    addr_Dest.sin_addr.s_addr = uiDestAddr;  //-- don't need to use htonl()!  
    addr_Dest.sin_port        = htons(TCPPort);  

//-- Connect to destination -- THIS DEFINITELY CAUSES DIALER TO COME UP! 
    if (connect(sock_Send, (sockaddr*) &addr_Dest, sizeof(addr_Dest)) != 0) 
        return 0; 

    closesocket(sock_Send); 

    return 1; 
} 

Now for the TCP listen thread.  The sole purpose of this thread is to pick up connection requests that result from the broadcast that announced the presence of this Winsock3 app.  It has the exaxt same format as the UDP thread, with only a slightly different controlling function. 
/***********************************************************************************/ 
/*************************  TCP  ***************************************************/ 
/***********************************************************************************/ 
CTCPListener::CTCPListener(int TCPPort, CListBox* pListBox, CObList* pol) 
{ 
    m_bKillMe          = 0; 
    m_TCPListenPort    = TCPPort; 
    m_pConnectionList  = pListBox; 
    m_polConnections   = pol; 
} 

CTCPListener::~CTCPListener() 
{ 
    KillThread(); 
#ifdef _DEBUG 
    Sleep(1000); 
#endif 
} 

void CTCPListener::CreateThread() 
{ 
    m_pWinThread = AfxBeginThread(CTCPListener::ControllingFunction, this, THREAD_PRIORITY_HIGHEST, 0, CREATE_SUSPENDED); 
} 

void CTCPListener::RunThread() 
{ 
    if (m_pWinThread != NULL) 
        m_pWinThread->ResumeThread(); 
} 

void CTCPListener::KillThread() 
{ 
    m_bKillMe = 1; 
    closesocket(sock_Listen); 
} 
The constuctor, destructor, CreateThread(), RunThread(), and KillThread() are identical in form and purpose to the UDP thread. 

UINT CTCPListener::ControllingFunction(void* pParam) 
{ 
    CTCPListener* pData = (CTCPListener*)pParam; 

    while (!pData->m_bKillMe) 
    { 
        pData->ListenForNewConnections(); 
    } 

    return 79; 
} 
Once again, the controlling function simply waits to process TCP connection attemps in a tight loop.  The return code is completely arbitrary. 

This is the function that listens for new TCP connections.  It is very similar to TCP listening function in previous tutorials. 
void CTCPListener::ListenForNewConnections() 
{ 
    SOCKADDR_IN  addr_Srv, addr_Cli; 
    SOCKET       sock_Accept; 
    char         buf[255]; 
    int          clilen; 
    short        listenport; 
    CString      csTemp; 

    listenport = m_TCPListenPort; 
//-- Open a TCP socket to listen on  
    if ((sock_Listen = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
        AfxMessageBox("Unable to create socket"); 

//-- Fill in structure fields for binding to local host 
    memset((char *) &addr_Srv, 0, sizeof(addr_Srv)); 
    addr_Srv.sin_family      = AF_INET; 
    addr_Srv.sin_addr.s_addr = htonl(INADDR_ANY); 
    addr_Srv.sin_port        = htons(listenport); 

//-- Bind it 
    if (bind(sock_Listen, (sockaddr*)&addr_Srv, sizeof(addr_Srv)) < 0) 
        AfxMessageBox("Error:  bind() failed."); 

//-- Put the socket in listen mode 
    if (listen(sock_Listen, SOMAXCONN) < 0) 
        AfxMessageBox("Error:  listen() failed."); 

//-- Prepare Client address to receive new data 
    memset(&addr_Cli, 0, sizeof(addr_Cli)); 
    clilen = sizeof(addr_Cli); 

//-- Wait for an incoming message (Note:  This blocks!) 
    while (1) 
    { 
        if ((sock_Accept = accept(sock_Listen, (sockaddr*)&addr_Cli, &clilen)) == INVALID_SOCKET) 
        { 
            if (m_bKillMe) 
            { 
            //-- No problem, Application is simply exiting.  This object is in process of  
            //-- being destroyed.  Get out!  (BTW, this is normal) 
                break; 
            } 
            else 
            { 
            //-- Something bad happened.  Cause this function to terminate.  
            //-- Controlling function will start it over. 
                AfxMessageBox("Error:  TCP accept() failed."); 
            //-- Give user a chance to exit between error messages 
                Sleep(1000); 
                break; 
            } 
        } 
If the program got to this point, it has accepted a new TCP connection.  Now it will put this information in the remote host list. 
    //-- Hey!  No errors.  We have a new connection 
        csTemp = CVWSocket::IPString(CVWSocket::uGetIPFromSockAddr_In(&addr_Cli), buf, 255); 
        m_pConnectionList->AddString(csTemp);  
For this application we really don't do anything with the connected socket, so go ahead and close it. 
        closesocket(sock_Accept); 
    } 

    closesocket(sock_Listen); 
} 

The previous section covers the functionality of the thread classes.  Now lets look at the rest of the application in VWSocket.cpp

IMPLEMENT_DYNCREATE(CVWSocket, CFormView) 
Standard dynamic creation macro. 

BEGIN_MESSAGE_MAP(CVWSocket, CFormView) 
    //{{AFX_MSG_MAP(CVWSocket) 
    ON_BN_CLICKED(IDC_BN_FIND, OnBnFind) 
    ON_BN_CLICKED(IDC_BN_KILL, OnBnKill) 
    ON_BN_CLICKED(IDC_BN_QUIT, OnBnQuit) 
    //}}AFX_MSG_MAP 
END_MESSAGE_MAP() 
Standard MFC message map.  The functions of interest are OnBnFind() and OnBnKill()

CVWSocket::CVWSocket() 
 : CFormView(CVWSocket::IDD) 
{ 
    //{{AFX_DATA_INIT(CVWSocket) 
    //}}AFX_DATA_INIT 
    m_pTCPListener = NULL; 
    m_pUDPListener = NULL; 
} 
Standard constructor.  Only initializes the thread class members. 

CVWSocket::~CVWSocket() 
{ 
    if (m_pTCPListener) 
    { 
        delete m_pTCPListener; 
    } 

    if (m_pUDPListener) 
    { 
        delete m_pUDPListener; 
    } 

    RemoveSockets(); 
} 
The destructor.  This is where we clean up the thread class members, if they are still running. 

void CVWSocket::DoDataExchange(CDataExchange* pDX) 
{ 
    CFormView::DoDataExchange(pDX); 
    //{{AFX_DATA_MAP(CVWSocket) 
    DDX_Control(pDX, IDC_UDPLISTENPORT, m_edUDPListen); 
    DDX_Control(pDX, IDC_TCPLISTENPORT, m_edTCPListen); 
    DDX_Control(pDX, IDC_LOCAL, m_edLocalInfo); 
    DDX_Control(pDX, IDC_LB_REMOTE, m_lbRemote); 
    //}}AFX_DATA_MAP 
} 
Standard DoDataExchange()

This OnInitialUpdate() is fairly standard as well.  It initializes the WinSock APIs and loads the default data.  In addition it launches the TCP and UDP threads.  After they are up and ready to process responses, this function broadcasts the presence of a new Winsock3 on the network. 
void CVWSocket::OnInitialUpdate()  
{ 
    char      buf[255]; 
    CString   csTemp; 

    CFormView::OnInitialUpdate(); 

    if (!InitSockets_1_1()) 
        AfxMessageBox("Winsock error.  Could not initialize."); 

    csTemp =  GetHostName(buf, 255); 
    csTemp =  "(" + csTemp + ") "; 
    csTemp += IPString(uGetLocalIP(), buf, 255); 

    m_edLocalInfo.SetWindowText(csTemp); 

//-- Set default data 
    m_edUDPListen.SetWindowText("3000"); 
    m_edTCPListen.SetWindowText("4000"); 

Launch the TCP thread with default values. 
//-- Start Listening for TCP 
    m_pTCPListener = new CTCPListener(4000, &m_lbRemote, &m_olConnections); 
    m_pTCPListener->CreateThread(); 
    m_pTCPListener->RunThread(); 

Launch the UDP thread with default values. 
//-- Start Listening for UDP 
    m_pUDPListener = new CUDPListener(3000, 4000, &m_lbRemote, &m_olConnections); 
    m_pUDPListener->CreateThread(); 
    m_pUDPListener->RunThread(); 

Broadcast the presence of this Winsock3 application. 
    BroadcastNewPeer(4000); 
} 

This function, BroadcastNewPeer(), simply makes a UDP broadcast to announce the presence of this Winsock3 application.  The TCP port that is passed in as a parameter is put in the broadcasted message so that other Winsock3 applications will know which TCP port to connect on.  This broadcast is handled exactly like the UDP broadcast covered in the Winsock2 Tutorial
void CVWSocket::BroadcastNewPeer(int TCPPort)  
{ 
    SOCKADDR_IN  addr_Broadcast; 
    SOCKET       sock_Send; 
    char         szMessage[256]; 
    int          val = 1; 
    short        destport; 
    CString      csTemp; 

//-- Prepare the message 
    sprintf(szMessage, "Winsock3:  New Peer, Requesting TCP Connection.[%d]", TCPPort); 

//-- Get the UDP port 
    m_edUDPListen.GetWindowText(csTemp); 
    destport = atoi(csTemp.GetBuffer(0)); 
    csTemp.ReleaseBuffer(-1); 
//-- Create UDP socket 
    if ((sock_Send = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
        AfxMessageBox("Error:  socket() failed."); 

//-- Fill in target addr (for a broadcast message) 
    memset((char *) &addr_Broadcast, 0, sizeof(addr_Broadcast)); 
    addr_Broadcast.sin_family      = AF_INET; 
    addr_Broadcast.sin_addr.s_addr = htonl(INADDR_BROADCAST); 
    addr_Broadcast.sin_port        = htons(destport);  

//-- Allow socket to broadcast 
    if (setsockopt(sock_Send, SOL_SOCKET, SO_BROADCAST, (char*)&val, sizeof(int)) == SOCKET_ERROR) 
        AfxMessageBox("Error:  setsockopt() failed."); 

//-- Send it 
    if (sendto(sock_Send, szMessage, 256, 0, (sockaddr*)&addr_Broadcast, sizeof(addr_Broadcast)) == SOCKET_ERROR) 
        AfxMessageBox("Error:  sendto() failed."); 
  
    closesocket(sock_Send); 
} 

This function refreshes the remote host list.  It does this by clearing the remote host list box and then rebroadcasting its presence.  Then, all the other Winsock3 apps will respond and form connections. 
void CVWSocket::OnBnFind()  
{ 
    int       UDPPort, TCPPort; 
    CString   csTemp; 

//-- Get the new port numbers and make sure they are valid 
    m_edTCPListen.GetWindowText(csTemp); 
    TCPPort = atoi(csTemp.GetBuffer(0)); 
    csTemp.ReleaseBuffer(-1); 
    m_edUDPListen.GetWindowText(csTemp); 
    UDPPort = atoi(csTemp.GetBuffer(0)); 
    csTemp.ReleaseBuffer(-1); 
    if ((UDPPort < 1) || (TCPPort < 1)) 
    { 
        AfxMessageBox("Invalid ports.  Enter valid ports and try again."); 
        return; 
    } 

Clear the connection list. 
//-- Clear the connection list, and waste the current threads 
    m_lbRemote.ResetContent(); 
Kill the existing TCP and UDP threads. 
    OnBnKill(); 

Restart the TCP thread with the ports gotten from the user. 
//-- Start Listening for TCP 
    m_pTCPListener = new CTCPListener(TCPPort, &m_lbRemote, &m_olConnections); 
    m_pTCPListener->CreateThread(); 
    m_pTCPListener->RunThread(); 

Restart the UDP thread with the ports gotten from the user. 
//-- Start Listening for UDP 
    m_pUDPListener = new CUDPListener(UDPPort, TCPPort, &m_lbRemote, &m_olConnections); 
    m_pUDPListener->CreateThread(); 
    m_pUDPListener->RunThread(); 

Rebroadcast the presence of this Winsock3 application. 
    BroadcastNewPeer(TCPPort); 
} 

This function terminates the UDP and TCP listening threads. 
void CVWSocket::OnBnKill()  
{ 
Because this will cause the application to pause for a few seconds in debug mode, we want to show the user a message to let them know what is happening. 
    CDGModelessMessage dg("Killing Threads.\n\nThis takes a few seconds in debug mode.", "Winsock3"); 

    if ((m_pUDPListener == NULL) && (m_pTCPListener == NULL)) 
        return; 

#ifdef _DEBUG 
    dg.Show(); 
#endif 
    if (m_pUDPListener) 
    { 
        delete m_pUDPListener; 
        m_pUDPListener = NULL; 
    } 
    if (m_pTCPListener) 
    { 
        delete m_pTCPListener; 
        m_pTCPListener = NULL; 
    } 
#ifdef _DEBUG 
    dg.Kill(); 
#endif 
} 

void CVWSocket::OnBnQuit()  
{ 
    GetParentFrame()->SendMessage(WM_CLOSE); 
} 
This function closes the application. 


Back to Winsock Tutorials Main Page