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