Winsock 1 - TCP Server
The TCP Server tutorial is the counterpart to the
TCP Client tutorial. These two programs demonstrate how to set up
a Client application and communicate with a Server via TCP. Together,
these two apps create a simple connection and can facilitate one-way communication
between two hosts. This tutorial explains the fundamental mechanics
involved with a TCP Server. Subsequent tutorials will assume an understanding
of these concepts.
All of these files, with the exception of Modeless Dialog files, were generated
by App Wizard, using a the dialog-based application template. Only
the files TCPServerDlg.cpp
and TCPServerDlg.h were added
to for the tutorial code. One line was added to stdafx.h
to enable socket support. With the exception of the resource files,
no other files were changed. See the section below for discussions
on the socket code.
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 take a detailed look at the file TCPServerDlg.cpp.
This section will also briefly introduce some MFC programming concepts,
in case you are unfamiliar with them. But before we can do anything
with sockets, we need to include the Winsock header files into the project.
This is done in stdafx.h.
#include <afxsock.h> // MFC socket extensions
This is the last line stdafx.h.
It was added manually to allow us to use the Winsock data structures and
APIs in our code. Including this line in stdafx.h
lets us use Winsock API's anywhere in our project. Now, let's take
a look at TCPServerDlg.cpp.
BEGIN_MESSAGE_MAP(CTCPServerDlg, CDialog) //{{AFX_MSG_MAP(CTCPServerDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_BN_QUIT, OnBnQuit) ON_BN_CLICKED(IDC_BN_LISTEN, OnBnListen) //}}AFX_MSG_MAP END_MESSAGE_MAP() This is the Message Map. This is simply used to associated functions
with user-interface events, or "messages." The line that
we care about in the Message Map is:
ON_BN_CLICKED(IDC_BN_LISTEN, OnBnListen) This line tells us that the function OnBnListen()
will be called when the user hits the "Listen" button.
As it turns out, all the sockets processing happens in this function.
CTCPServerDlg::CTCPServerDlg(CWnd* pParent /*=NULL*/) : CDialog(CTCPServerDlg::IDD, pParent) { ... } This is the a standard C++ constructer, no changes are needed.
CTCPServerDlg::~CTCPServerDlg() { RemoveSockets(); } This is a standard C++ destructer. I felt that it was a good place
to call our sockets cleanup routine RemoveSockets().
This will be explained more below when socket initialization is discussed.
void CTCPServerDlg::DoDataExchange(CDataExchange* pDX) { ... } This function, DoDataExchange(), is where MFC associates
C++ class members with their corresponding user interface control.
All of this generated by Class Wizard, so no changes were necessary.
BOOL CTCPServerDlg::OnInitDialog() { char buf[255]; ... //-- Initialization InitSockets_1_1(); m_edLocalName.SetWindowText(GetHostName(buf,
255)); m_edLocalIP.SetWindowText(IPString(uGetLocalIP(),
buf, 255)); m_edPort.SetWindowText("4000"); return TRUE; } This function, OnInitDialog(), is called exactly one time,
precisely when the dialog starts up. It is a handy place to do all
of our initialization. We can not do this initialization in the constructer
because the constructor is called before some of our members are created
and attached to their corresponding user-interface controls. Most
of the code in this function is standard MFC fare. The lines that we are
interested in for this tutorial are at the end.
The first call we make is InitSockets_1_1(). This
function determines if the host is capable of supporting Winsock 1.1 functionality.
If it is, this function enables the host to use the Winsock APIs.
Otherwise, the function fails. If this function succeeds, you must
call RemoveSockets() when you are done with the Winsock
APIs. This function is one of our general
winsock utility functions. Refer to Quinn & Shute pp. 320
- 322.
After InitSockets_1_1(), we simply set up a few of the
user-interface controls with some desired values. The functions GetHostName(),
IPString() and uGetLocalIP() are all general
winsock utility functions.
HCURSOR CTCPServerDlg::OnQueryDragIcon() { ... } These function are more standard MFC fare. They were created by App
Wizard.
void CTCPServerDlg::OnBnQuit() { EndDialog(0); } This function was added manually. It's sole purpose is to end the
application when the user hits the "Quit" button.
Finally, this function is where the core functionality lies.
void CTCPServerDlg::OnBnListen() { We need to declare some variables. We need a client address to fill
in, addr_Cli. Also, because this is a server application,
we need to bind the socket to the local host. For this we need an
additional address structure addr_Srv. We need two
sockets, sock_Listen and sock_Accept.
sock_Listen is used as the socket that will be listening
for the client's message. Because this is a TCP socket and not a
UDP socket, in order to get any data through we need to accept the connection
with another socket, so that sock_Listen can continue to
be used for listening for clients. sock_Accept will
be the socket we use to accept data from the client. We also need
two string buffers for the message we are receiving and a worksapce for
formatting the message, szMessage[256] and buf[255].
We need a port number to listen on, listenport. We
need a temporary string to hold some values for us, csTemp.
And finally, we need an integer to hold the length of the addr_Cli
structure, clilen.
SOCKADDR_IN addr_Srv, addr_Cli; SOCKET sock_Listen,
sock_Accept; char
szMessage[256], buf[255]; int
clilen; short
listenport; CString csTemp;
Get the port number to listen on and put it in listenport.
//-- Get the port to listen on m_edPort.GetWindowText(csTemp); listenport = atoi(csTemp.GetBuffer(0)); csTemp.ReleaseBuffer(-1); if (listenport < 1) { AfxMessageBox("Please
enter a valid listen port"); return; }
Now, create a TCP socket with the socket() function.
In winsock using TCP/IP, the first and last parameters will always be AF_INET
and 0. The middle parameter determines whether a
TCP or UDP socket gets created. Using SOCK_STREAM
will create a TCP socket. Refer to Quinn & Shute pp. 50 - 53.
//-- Open a TCP socket to listen on if ((sock_Listen = socket(AF_INET, SOCK_STREAM,
0)) < 0) AfxMessageBox("Unable
to create socket");
Now fill in the local host address structure. For this purpose, we
don't need to know the local host's actual IP address. We can simply
pass the parameter INADDR_ANY and the system will automatically
assign an IP address. Of course, we first need to convert INADDR_ANY
to network-byte order with htonl().The function htons()
makes sure that the port number is stored in the proper network byte order.
Refer to Quinn & Shute pp. 54 - 55.
//-- 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);
Now bind the listening socket to the local host. Refer to Quinn &
Shute pp. 57 - 58.
//-- Bind it if (bind(sock_Listen, (sockaddr*)&addr_Srv,
sizeof(addr_Srv)) < 0) AfxMessageBox("Error:
bind() failed.");
Now put the listening socket in listen mode. Refer to Quinn &
Shute pp. 60 - 61.
//-- Put the socket in listen mode if (listen(sock_Listen, SOMAXCONN) < 0) AfxMessageBox("Error:
listen() failed.");
Standard maintenance to prepare the client address structure to recieve
new data.
//-- Prepare Client address to receive new data memset(&addr_Cli, 0, sizeof(addr_Cli)); clilen = sizeof(addr_Cli);
Let the user know that this thread is about to begin a blocking operation.
//-- Put up a message to let them knowthis thread will be blocking CDGModelessMessage dg("Waiting for TCP message
from client...", "Winsock1 - TCP Server"); dg.Show();
Calling accept() will put this thread in blocking mode
until it recieves a request for a TCP connection from a client. When
a connection is found, a new socket, sock_Accept, will
be created for communication with the client. Refer to Quinn &
Shute pp. 63 - 64.
//-- Wait for an incoming message (Note: This blocks!) if ((sock_Accept = accept(sock_Listen, (sockaddr*)&addr_Cli,
&clilen)) < 0) AfxMessageBox("Error:
accept() failed.");
Now recieve the message from the client. Refer to Quinn & Shute
pp. 68 - 69.
if (recv(sock_Accept, szMessage, 256, 0) == SOCKET_ERROR) { int res = WSAGetLastError(); AfxMessageBox("Error:
recv() failed."); } else { If the program got to this point we have recieved a valid message from
the client. It is convenient to pull out the IP address of the client
and show it to the user along with the message.
//-- We recieved a message. Show it, along with
the source IP address csTemp = "From:
"; csTemp += IPString(uGetIPFromSockAddr_In(&addr_Cli),
buf, 255); csTemp += "\n"; csTemp += szMessage; AfxMessageBox(csTemp); } Now kill the modeless message dialog because our blocking operation is
complete.
dg.Kill(); Clean up the two sockets that we have created before we exit.
closesocket(sock_Accept); closesocket(sock_Listen); }