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