Winsock 4 
The Winsock4 Tutorial is a full fledged chat application.  It can communicate with any number of hosts on the network.  It is built directly on top of the Winsock3 Tutorial.  A quick inspection reveals most of the Winsock3 controls right there on the Winsock 4 screen.  This is because Winsock4 uses the Winsock3 functionality to detect and connect with remote hosts.  What is new is the chat interface.  Now Winsock4 keeps the TCP connections alive for as long as both hosts are running.  It uses these connections to send and recieve messages between the hosts. 

Winsock 4 is a work in progress.  Although it is a fully functional chat application, it has some bugs and lacks of functionality.  Most notably, it does not gracefully handle a host leaving the chat room. 

Using the chat interface is fairly simple.  Type in the message you want to send in the edit box by the "Send" button.  The press the "Send" button or just hit enter to send the message to all connected hosts.  The connected hosts are listed in the "Hosts:" list box.  You can refresh this list by pressing the "Refresh" button.  Of course, you will only be connected to hosts that are using the same UDP listen port. 



 


Downloads 
Program Only  Winsock4.exe.  No source files. 
Program and Source Files  Winsock4 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\Winsock4.ico  
res\Winsock4.rc2  
res\Winsock4Doc.ico  
DGModeless.cpp  
DGModeless.h  
MainFrm.cpp  
MainFrm.h  
resource.h  
stdafx.cpp  
stdafx.h  
THListenUDP.cpp 
THListenUDP.h 
VWSocket.cpp  
VWSocket.h  
Winsock4.aps  
Winsock4.clw  
Winsock4.cpp  
Winsock4.h  
Winsock4.mak  
Winsock4.mdp  
Winsock4.ncb  
Winsock4.rc  
Winsock4Doc.cpp  
Winsock4Doc.h  

As with the last two tutorials, the Winsock4 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 


This is the most advanced tuturial in this series.  It is also a work in progress, so it has some lack of functionality.  In its present state, it does satisfy the role of a chat program.  Any number of hosts can start up and join in a common discussion.  However, when a host shuts down there are some problems.  Despite this, the tutorial still demonstrates what is necessarry to implement a full fledged chat application. 

As in prior tutorials, most of the functionality is in VWSocket.cpp and THListenUDP.cpp.  The thread classes in this tutorial are nearly identical to those in the Winsock3 Tutorial.  Therefore discussion on these classes is omitted.  The only significant difference is that instead of putting an entry in the remote host list when a connection is made, the threads now call a function in the main thread, AddConnection(), when a new TCP connection is established.  This can be seen by viewing the links to the thread source files on this page. 

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(CWinsock4Doc), 
        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.  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, OnBnRefreshHosts) 
    ON_BN_CLICKED(IDC_BN_KILL, OnBnKill) 
    ON_BN_CLICKED(IDC_BN_QUIT, OnBnQuit) 
    ON_BN_CLICKED(IDC_BN_SEND, OnBnSend) 
    //}}AFX_MSG_MAP 
END_MESSAGE_MAP() 
Standard MFC message map.  The functions of interest are OnBnRefreshHosts() and OnBnSend()

CVWSocket::CVWSocket() 
 : CFormView(CVWSocket::IDD) 
{ 
    //{{AFX_DATA_INIT(CVWSocket) 
    //}}AFX_DATA_INIT 
    m_pTCPListener = NULL; 
    m_pUDPListener = NULL; 
    m_NumConnections = 0;  
} 
Standard constructor.  Initializes the thread class members and the number of active TCP connections, m_NumConnections

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

    if (m_pUDPListener) 
    { 
        delete m_pUDPListener; 
    } 
    KillMessageThreads(); 
    RemoveSockets(); 
} 
The destructor.  This is where we clean up the thread class members, if they are still running.  We also need to close all active TCP connections by calling KillMessageThreads()

void CVWSocket::DoDataExchange(CDataExchange* pDX) 
{ 
    CFormView::DoDataExchange(pDX); 
    //{{AFX_DATA_MAP(CVWSocket) 
    DDX_Control(pDX, IDC_SEND, m_edSend); 
    DDX_Control(pDX, IDC_LB_CHAT, m_lbChat); 
    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 Winsock4 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, this); 
    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, this); 
    m_pUDPListener->CreateThread(); 
    m_pUDPListener->RunThread(); 

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

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."); 
  
    shutdown(sock_Send, 2); 
    closesocket(sock_Send); 
} 
This function, BroadcastNewPeer(), is identical to the one found in the Winsock3 Tutorial

void CVWSocket::AddConnection(SOCKET newsocket) 
{ 
    CMessageListener* p; 

    m_saConnections[m_NumConnections] = newsocket; 
//-- Start Listening for messages 
    p = new CMessageListener(m_saConnections[m_NumConnections], this); 
    p->CreateThread(); 
    p->RunThread(); 

    m_ThreadPointers[m_NumConnections] = p; 

    ++m_NumConnections; 
} 
This is a new function for the chat functionality.  This function's purpose is create new listener threads that read messages from the remote hosts.  Each time a new TCP conection is established, a thread is launched to listen for messages from that remote host. 

void CVWSocket::ShowMessage(CString csMessage) 
{ 
    m_lbChat.AddString(csMessage); 
//-- Scroll to end of Chat box 
    m_lbChat.SetCurSel(m_lbChat.GetCount()-1); 
} 
The message listener threads call this function when they have recieved a message from a remote host.  All this function does is put the new message in the chat window. 

void CVWSocket::KillMessageThreads() 
{ 
    int k; 

    for (k = 0; k < m_NumConnections; ++k) 
        if (m_ThreadPointers[k] != NULL) 
            delete m_ThreadPointers[k]; 
} 
This function is necessary to clean up all the message listener threads.  Recall that each TCP connection that is active will have a message listener thread associated with it. 

void CVWSocket::OnBnRefreshHosts()  
{ 
    int       UDPPort, TCPPort; 
    CString   csTemp; 

//-- Kill all connection info 
    for (int k = 0; k < MAX_CONNECTIONS; ++k) 
    { 
        m_saConnections[k] = 0; 
        m_ThreadPointers[k] = NULL; 
    } 
    m_NumConnections = 0;  

//-- 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, and waste the current threads 
    m_lbRemote.ResetContent(); 
    OnBnKill(); 

//-- Start Listening for TCP 
    m_pTCPListener = new CTCPListener(TCPPort, &m_lbRemote, this); 
    m_pTCPListener->CreateThread(); 
    m_pTCPListener->RunThread(); 

//-- Start Listening for UDP 
    m_pUDPListener = new CUDPListener(UDPPort, TCPPort, &m_lbRemote, this); 
    m_pUDPListener->CreateThread(); 
    m_pUDPListener->RunThread(); 

    BroadcastNewPeer(TCPPort); 
} 
This function, OnBnRefreshHosts(), is identical to the OnBnFind() found in the Winsock3 Tutorial

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."); 

    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. 

This function, OnBnSend(), is called when the user types a message and hits the "Send" button.  Its purpose is to format and distribute the message to all the connected remote hosts. 
void CVWSocket::OnBnSend()  
{ 
    int       k; 
    char      szMessage[255]; 
    char      buf[255]; 
    CString   csTemp; 

Get the message from the edit box and format it, including the local IP address with it. 
    m_edSend.GetWindowText(csTemp); 
    sprintf(szMessage, "[%s] %s", IPString(uGetLocalIP(), buf, 255), csTemp.GetBuffer(0)); 
    csTemp.ReleaseBuffer(-1); 

Send the message to all connected hosts. 
    for (k = 0; k < m_NumConnections; ++k) 
    { 
        if (send(m_saConnections[k], szMessage, 256, 0) == SOCKET_ERROR) 
            AfxMessageBox("Error:  send() failed."); 
    } 

Put the message in the local chat window as well. 
//-- Echo it locally 
    m_lbChat.AddString(szMessage); 
//-- Erase the send box 
    m_edSend.SetWindowText(""); 
//-- Scroll to end of Chat box 
    m_lbChat.SetCurSel(m_lbChat.GetCount()-1); 
} 


Back to Winsock Tutorials Main Page