| Tenouk C & C++ | MFC Home | C++, MFC, Winsock & WinInet 1 | C++, MFC, Winsock & WinInet 3 | Download | Site Index |


 

 

 

 

 

 

C++, MFC, Winsock and WinInet Part 2

 

 

 

 

 

 

 

Program examples compiled using Visual C++ 6.0 compiler on Windows XP Pro machine with Service Pack 2 and some Figure screen snapshots have been taken on Windows 2000 server. Topics and sub topics for this tutorial are listed below. Don’t forget to read Tenouk’s small disclaimer.  Supplementary item is WEBSITE.

  1. Winsock

  2. Synchronous vs. Asynchronous Winsock Programming

  3. The MFC Winsock Classes

  4. The Blocking Socket Classes

  5. The CSockAddr Helper Class

  6. The CBlockingSocketException Class

  7. The CBlockingSocket Class

  8. The CHttpBlockingSocket Class

  9. A Simplified HTTP Server Program

  10. Initializing Winsock

  11. Starting the Server

  12. The Server Thread

  13. Cleaning Up

  14. A Simplified HTTP Client Program

 

 

 

Winsock (Windows Socket)

 

Winsock is the lowest level Windows API for TCP/IP programming. Part of the code is located in wsock32.dll (the exported functions that your program calls), and part is inside the Windows kernel. You can write both internet server programs and internet client programs using the Winsock API. This API is based on the original Berkeley Sockets API for UNIX. A new and much more complex version, Winsock 2, is included for the first time with Windows NT 4.0, but we'll stick with the old version because it's simplicity. The Winsock 2 has been discussed in Winsock2. Keep in mind that Winsock/Winsock2 uses C code. The latest Winsock programming in the .NET can be found in .NET network programming.

 

Synchronous vs. Asynchronous Winsock Programming

 

Winsock was introduced first for Win16, which did not support multithreading. Consequently, most developers used Winsock in the asynchronous mode. In that mode, all sorts of hidden windows and PeekMessage() calls enabled single-threaded programs to make Winsock send and receive calls without blocking, thus keeping the user interface (UI) alive. Asynchronous Winsock programs were complex, often implementing "state machines" that processed callback functions, trying to figure out what to do next based on what had just happened. Well, we're not in 16-bit land anymore, so we can do modern multithreaded programming. If this scares you, go back and review Module 22. Once you get used to multithreaded programming, you'll love it.

In this module, we will make the most of our Winsock calls from worker threads so that the program's main thread is able to carry on with the UI. The worker threads contain nice, sequential logic consisting of blocking Winsock calls.

 

The MFC Winsock Classes

 

We try to use MFC classes where it makes sense to use them, but the MFC developers informed us that the CAsyncSocket and CSocket classes were not appropriate for 32-bit synchronous programming. The Visual C++ online help says you can use CSocket for synchronous programming, but if you look at the source code you'll see some ugly message-based code left over from Win16.

 

The Blocking Socket Classes

 

Since we couldn't use MFC, we had to write our own Winsock classes. CBlockingSocket is a thin wrapping of the Winsock API, designed only for synchronous use in a worker thread. The only fancy features are exception-throwing on errors and time-outs for sending and receiving data. The exceptions help you write cleaner code because you don't need to have error tests after every Winsock call. The time-outs (implemented with the Winsock select function) prevent a communication fault from blocking a thread indefinitely.

CHttpBlockingSocket is derived from CBlockingSocket and provides functions for reading HTTP data. CSockAddr and CBlockingSocketException are helper classes.

 

The CSockAddr Helper Class

 

Many Winsock functions take socket address parameters. As you might remember, a socket address consists of a 32-bit IP address plus a 16-bit port number. The actual Winsock type is a 16-byte sockaddr_in structure, which looks like this:

 

struct sockaddr_in {

    short   sin_family;

    u_short sin_port;

    struct  in_addr sin_addr;

    char    sin_zero[8];

};

 

The IP address is stored as type in_addr, which looks like this:

 

struct in_addr {

    union {

        struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;

        struct { u_short s_w1,s_w2; } S_un_w;

        u_long S_addr;

    } S_un;

}

 

These are ugly structures, so we'll derive a programmer-friendly C++ class from sockaddr_in. The file Blocksock.h contains the following code for doing this, with inline functions included:

 

class CSockAddr : public sockaddr_in {

public:

    // constructors

    CSockAddr()

    {

        sin_family = AF_INET;

        sin_port = 0;

        sin_addr.s_addr = 0;

    } // Default

    CSockAddr(const SOCKADDR& sa) { memcpy(this, &sa, sizeof(SOCKADDR)); }

    CSockAddr(const SOCKADDR_IN& sin) { memcpy(this, &sin, sizeof(SOCKADDR_IN)); }

    CSockAddr(const ULONG ulAddr, const USHORT ushPort = 0)

    // parms are host byte ordered

    {

        sin_family = AF_INET;

        sin_port = htons(ushPort);

        sin_addr.s_addr = htonl(ulAddr);

    }

    CSockAddr(const char* pchIP, const USHORT ushPort = 0)

    // dotted IP addr string

    {

        sin_family = AF_INET;

        sin_port = htons(ushPort);

        sin_addr.s_addr = inet_addr(pchIP);

    } // already network byte ordered

    // Return the address in dotted-decimal format

    CString DottedDecimal()

        { return inet_ntoa(sin_addr); }

    // constructs a new CString object

    // Get port and address (even though they're public)

    USHORT Port() const

        { return ntohs(sin_port); }

    ULONG IPAddr() const

        { return ntohl(sin_addr.s_addr); }

    // operators added for efficiency

    const CSockAddr& operator=(const SOCKADDR& sa)

    {

        memcpy(this, &sa, sizeof(SOCKADDR));

        return *this;

    }

    const CSockAddr& operator=(const SOCKADDR_IN& sin)

    {

        memcpy(this, &sin, sizeof(SOCKADDR_IN));

        return *this;

    }

    operator SOCKADDR()

        { return *((LPSOCKADDR) this); }

    operator LPSOCKADDR()

        { return (LPSOCKADDR) this; }

    operator LPSOCKADDR_IN()

        { return (LPSOCKADDR_IN) this; }

};

 

As you can see, this class has some useful constructors and conversion operators, which make the CSockAddr object interchangeable with the type sockaddr_in and the equivalent types SOCKADDR_IN, sockaddr, and SOCKADDR. There's a constructor and a member function for IP addresses in dotted-decimal format. The internal socket address is in network byte order, but the member functions all use host byte order parameters and return values. The Winsock functions htonl, htons, ntohs, and ntohl take care of the conversions between network and host byte order.

 

The CBlockingSocketException Class

 

All the CBlockingSocket functions throw a CBlockingSocketException object when Winsock returns an error. This class is derived from the MFC CException class and thus overrides the GetErrorMessage() function. This function gives the Winsock error number and a character string that CBlockingSocket provided when it threw the exception.

 

The CBlockingSocket Class

 

Listing 1 shows an excerpt from the header file for the CBlockingSocket class.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BLOCKSOCK.H

 

class CBlockingSocket : public CObject

{

    DECLARE_DYNAMIC(CBlockingSocket)

public:

    SOCKET m_hSocket;

    CBlockingSocket();  { m_hSocket = NULL; }

    void Cleanup();

    void Create(int nType = SOCK_STREAM);

    void Close();

    void Bind(LPCSOCKADDR psa);

    void Listen();

    void Connect(LPCSOCKADDR psa);

    BOOL Accept(CBlockingSocket& s, LPCSOCKADDR psa);

    int Send(const char* pch, const int nSize, const int nSecs);

    int Write(const char* pch, const int nSize, const int nSecs);

    int Receive(char* pch, const int nSize, const int nSecs);

    int SendDatagram(const char* pch, const int nSize, LPCSOCKADDR psa, const int nSecs);

    int ReceiveDatagram(char* pch, const int nSize, LPCSOCKADDR psa, const int nSecs);

    void GetPeerAddr(LPCSOCKADDR psa);

    void GetSockAddr(LPCSOCKADDR psa);

    static CSockAddr GetHostByName(const char* pchName, const USHORT ushPort = 0);

    static const char* GetHostByAddr(LPCSOCKADDR psa);

    operator SOCKET();

        { return m_hSocket; }

};

 

Listing 1:  Excerpt from the header file for the CBlockingSocketclass.

 

Following is a list of the CBlockingSocket member functions, starting with the constructor:

Parameter

Description

nType

Type of socket; should be SOCK_STREAM (the default value) or SOCK_DGRAM.

 

Table 1.

Parameter

Description

psa

A CSockAddr object or a pointer to a variable of type sockaddr.

 

Table 2.

Parameter

Description

s

A reference to an existing CBlockingSocket object for which Create() has not been called.

psa

A CSockAddr object or a pointer to a variable of type sockaddr for the connecting socket's address.

Return value

TRUE if successful.

 

Table 3.

Parameter

Description

psa

A CSockAddr object or a pointer to a variable of type sockaddr.

 

Table 4.

Parameter

Description

pch

A pointer to a buffer that contains the bytes to send.

nSize

The size (in bytes) of the block to send.

nSecs

Time-out value in seconds.

Return value

The actual number of bytes sent.

 

Table 5.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Parameter

Description

pch

A pointer to a buffer that contains the bytes to send.

nSize

The size (in bytes) of the block to send.

nSecs

Time-out value in seconds.

Return value

The actual number of bytes sent.

 

Table 6.

Parameter

Description

pch

A pointer to an existing buffer that will receive the incoming bytes.

nSize

The maximum number of bytes to receive.

nSecs

Time-out value in seconds.

Return value

The actual number of bytes received.

 

Table 7.

Parameter

Description

pch

A pointer to a buffer that contains the bytes to send.

nSize

The size (in bytes) of the block to send.

psa

The datagram's destination address; a CSockAddr object or a pointer to a variable of type sockaddr.

nSecs

Time-out value in seconds.

Return value

The actual number of bytes sent.

 

Table 8.

Parameter

Description

pch

A pointer to an existing buffer that will receive the incoming bytes.

nSize

The size (in bytes) of the block to send.

psa

The datagram's destination address; a CSockAddr object or a pointer to a variable of type sockaddr.

nSecs

Time-out value in seconds.

Return value

The actual number of bytes received.

 

Table 9.

Parameter

Description

psa

A CSockAddr object or a pointer to a variable of type sockaddr.

 

Table 10.

Parameter

Description

psa

A CSockAddr object or a pointer to a variable of type sockaddr.

 

Table 11.

Parameter

Description

pchName

A pointer to a character array containing the host name to resolve.

ushPort

The port number (default value 0) that will become part of the returned socket address.

Return value

The socket address containing the IP address from the DNS plus the port number ushPort.

 

Table 12.

Parameter

Description

psa

A CSockAddr object or a pointer to a variable of type sockaddr.

Return value

A pointer to a character array containing the host name; the caller should not delete this memory.

 

Table 13.

 

The CHttpBlockingSocket Class

 

If you call CBlockingSocket::Receive, you'll have a difficult time knowing when to stop receiving bytes. Each call returns the bytes that are stacked up at your end of the connection at that instant. If there are no bytes, the call blocks, but if the sender closed the socket, the call returns zero bytes. In the HTTP section, you learned that the client sends a request terminated by a blank line. The server is supposed to send the response headers and data as soon as it detects the blank line, but the client needs to analyze the response headers before it reads the data. This means that as long as a TCP connection remains open, the receiving program must process the received data as it comes in. A simple but inefficient technique would be to call Receive() for 1 byte at a time. A better way is to use a buffer. The CHttpBlockingSocket class adds buffering to CBlockingSocket, and it provides two new member functions. Here is part of the Blocksock.h file:

 

class CHttpBlockingSocket : public CBlockingSocket

{

public:

    DECLARE_DYNAMIC(CHttpBlockingSocket)

    enum {nSizeRecv = 1000}; // max receive buffer size (> hdr line length)

    CHttpBlockingSocket();

    ~CHttpBlockingSocket();

    int ReadHttpHeaderLine(char* pch, const int nSize, const int nSecs);

    int ReadHttpResponse(char* pch, const int nSize, const int nSecs);

private:

    char* m_pReadBuf; // read buffer

    int m_nReadBuf; // number of bytes in the read buffer

};

 

The constructor and destructor take care of allocating and freeing a 1000-character buffer. The two new member functions are as follows:

Parameter

Description

pch

A pointer to an existing buffer that will receive the incoming line (zero-terminated).

nSize

The size of the pch buffer.

nSecs

Time-out value in seconds.

Return value

The actual number of bytes received, excluding the terminating zero.

 

Table 14.

Parameter

Description

pch

A pointer to an existing buffer that will receive the incoming data.

nSize

The maximum number of bytes to receive.

nSecs

Time-out value in seconds.

Return value

The actual number of bytes received.

 

Table 15.

 

A Simplified HTTP Server Program

 

Now it's time to use the blocking socket classes to write an HTTP server program. All the frills have been eliminated, but the code actually works with a browser. This server doesn't do much except return some hard-coded headers and HTML statements in response to any GET request. (See the MYEX33A program later in this module for a more complete HTTP server.)

 

Initializing Winsock

 

Before making any Winsock calls, the program must initialize the Winsock library. The following statements in the application's InitInstance() member function do the job:

 

WSADATA wsd;

WSAStartup(0x0101, &wsd);

 

Starting the Server

 

The server starts in response to some user action, such as a menu choice. Here's the command handler:

 

CBlockingSocket g_sListen; // one-and-only global socket for listening

void CSocketView::OnInternetStartServer()

{

    try {

        CSockAddr saServer(INADDR_ANY, 80);

        g_sListen.Create();

        g_sListen.Bind(saServer);

        g_sListen.Listen();

        AfxBeginThread(ServerThreadProc, GetSafeHwnd());

    }

    catch(CBlockingSocketException* e) {

        g_sListen.Cleanup();

        // Do something about the exception

        e->Delete();

    }

}

 

Pretty simple, really. The handler creates a socket, starts listening on it, and then starts a worker thread that waits for some client to connect to port 80. If something goes wrong, an exception is thrown. The global g_sListen object lasts for the life of the program and is capable of accepting multiple simultaneous connections, each managed by a separate thread.

 

The Server Thread

 

Now let's look at the ServerThreadProc() function:

 

UINT ServerThreadProc(LPVOID pParam)

{

    CSockAddr saClient;

    CHttpBlockingSocket sConnect;

    char request[100];

    char headers[] = "HTTP/1.0 200 OK\r\n"

        "Server: Inside Visual C++ SOCK01\r\n"

        "Date: Thu, 05 Sep 2005 17:33:12 GMT\r\n"

        "Content-Type: text/html\r\n"

        "Accept-Ranges: bytes\r\n"

        "Content-Length: 187\r\n"

        "\r\n"; // the important blank line

    char html[ ] =

        "<html><head><title>Inside Visual C++ Server</title></head>\r\n"

        "<body><body background=\"/samples/images/usa1.jpg\">\r\n"

        "<h1><center>This is a custom home page</center></h1><p>\r\n"

        "</body></html>\r\n\r\n";

    try {

        if(!g_sListen.Accept(sConnect, saClient)) {

            // Handler in view class closed the listening socket

            return 0;

        }

        AfxBeginThread(ServerThreadProc, pParam);

        // read request from client

        sConnect.ReadHttpHeaderLine(request, 100, 10);

        TRACE("SERVER: %s", request); // Print the first header

        if(strnicmp(request, "GET", 3) == 0) {

            do { // Process the remaining request headers

                sConnect.ReadHttpHeaderLine(request, 100, 10);

                TRACE("SERVER: %s", request); // Print the other headers

            } while(strcmp(request, "\r\n"));

            sConnect.Write(headers, strlen(headers), 10); // response hdrs

            sConnect.Write(html, strlen(html), 10); // HTML code

        }

        else {

            TRACE("SERVER: not a GET\n");

            // don't know what to do

        }

        sConnect.Close(); // Destructor doesn't close it

    }

    catch(CBlockingSocketException* e) {

        // Do something about the exception

        e->Delete();

    }

    return 0;

}

 

The most important function call is the Accept() call. The thread blocks until a client connects to the server's port 80, and then Accept() returns with a new socket, sConnect. The current thread immediately starts another thread. In the meantime, the current thread must process the client's request that just came in on sConnect. It first reads all the request headers by calling ReadHttpHeaderLine() until it detects a blank line. Then it calls Write() to send the response headers and the HTML statements. Finally, the current thread calls Close() to close the connection socket. End of story for this connection. The next thread is sitting, blocked at the Accept() call, waiting for the next connection.

 

Cleaning Up

 

To avoid a memory leak on exit, the program must ensure that all worker threads have been terminated. The simplest way to do this is to close the listening socket. This forces any thread's pending Accept() to return FALSE, causing the thread to exit.

 

try {

    g_sListen.Close();

    Sleep(340); // Wait for thread to exit

    WSACleanup(); // Terminate Winsock

}

catch(CUserException* e) {

    e->Delete();

}

 

A problem might arise if a thread were in the process of fulfilling a client request. In that case, the main thread should positively ensure that all threads have terminated before exiting

 

A Simplified HTTP Client Program

 

Now for the client side of the story, a simple working program that does a blind GET request. When a server receives a GET request with a slash, as shown below, it's supposed to deliver its default HTML file:

 

GET / HTTP/1.0

 

If you typed http://www.slowsoft.com in a browser, the browser sends the blind GET request. This client program can use the same CHttpBlockingSocket class you've already seen, and it must initialize Winsock the same way the server did. A command handler simply starts a client thread with a call like this:

 

AfxBeginThread(ClientSocketThreadProc, GetSafeHwnd());

 

Here's the client thread code:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

CString g_strServerName = "localhost"; // or some other host name

UINT ClientSocketThreadProc(LPVOID pParam)

{

    CHttpBlockingSocket sClient;

    char* buffer = new char[MAXBUF];

    int nBytesReceived = 0;

    char request[] = "GET / HTTP/1.0\r\n";

    char headers[] = // Request headers

        "User-Agent: Mozilla/1.22 (Windows; U; 32bit)\r\n"

        "Accept: */*\r\n"

        "Accept: image/gif\r\n"

        "Accept: image/x-xbitmap\r\n"

        "Accept: image/jpeg\r\n"

        "\r\n"; // need this

    CSockAddr saServer, saClient;

    try {

        sClient.Create();

        saServer = CBlockingSocket::GetHostByName(g_strServerName, 80);

        sClient.Connect(saServer);

        sClient.Write(request, strlen(request), 10);

        sClient.Write(headers, strlen(headers), 10);

        do { // Read all the server's response headers

            nBytesReceived = sClient.ReadHttpHeaderLine(buffer, 100, 10);

        } while(strcmp(buffer, "\r\n")); // through the first blank line

        nBytesReceived = sClient.ReadHttpResponse(buffer, 100, 10);

        if(nBytesReceived == 0) {

            AfxMessageBox("No response received");

        }

        else {

            buffer[nBytesReceived] = `\0';

            AfxMessageBox(buffer);

        }

    }

    catch(CBlockingSocketException* e) {

        // Log the exception

        e->Delete();

    }

    sClient.Close();

    delete [ ] buffer;

    return 0; // The thread exits

}

 

This thread first calls CBlockingSocket::GetHostByName to get the server computer's IP address. Then it creates a socket and calls Connect() on that socket. Now there's a two-way communication channel to the server. The thread sends its GET request followed by some request headers, reads the server's response headers, and then reads the response file itself, which it assumes is a text file. After the thread displays the text in a message box, it exits.

 

Continue on next Module...

 

 

Further reading and digging:

  1. DCOM at MSDN.

  2. COM+ at MSDN.

  3. COM at MSDN.

  4. Win32 process, thread and synchronization story can be found starting from Module R.

  5. MSDN MFC 7.0 class library online documentation.

  6. MSDN MFC 9.0 class library online documentation - latest version.

  7. MSDN Library

  8. Windows data type.

  9. Win32 programming Tutorial.

  10. The best of C/C++, MFC, Windows and other related books.

  11. Unicode and Multibyte character set: Story and program examples.

 

 


 

| Tenouk C & C++ | MFC Home | C++, MFC, Winsock & WinInet 1 | C++, MFC, Winsock & WinInet 3 | Download | Site Index |