Programming with Microsoft Visual C++.NET 6ed [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Programming with Microsoft Visual C++.NET 6ed [Electronic resources] - نسخه متنی

George Shepherd, David Kruglinski

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید








Winsock Programming


Winsock is the lowest-level Windows API for TCP/IP programming. Part of the code (the exported functions that your program calls) is located in Wsock32.dll, 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, was included for the first time with Windows NT 4.0, but we'll stick with the old version because it's available in all versions of Windows.


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 alive. Asynchronous Winsock programs were complex. They often implemented "state machines" that processed callback functions to try 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 Chapter 11. Once you get used to multithreaded programming, you'll love it.

In this chapter, we'll make the most of our Winsock calls from worker threads so the program's main thread can carry on with the user interface. The worker threads contain nice, sequential logic consisting of blocking Winsock calls.



The MFC Winsock Classes


We've tried to use MFC classes where it makes sense to use them, but the MFC developers have informed us that the CAsyncSocket and CSocket classes are not appropriate for 32-bit synchronous programming. The Visual C++ .NET 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 timeouts 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 timeouts (which are 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 \vcppnet\Ex28a\

Blocksock.h on the companion CD 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


The following code 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; }
};











Here are the CBlockingSocket member functions, starting with the constructor:



    Constructor The CBlockingSocket constructor makes an uninitialized object. You must call the Create member function to create a Windows socket and connect it to the C++ object.



    Create This function calls the Winsock socket function and then sets the m_hSocket data member to the returned 32-bit SOCKET handle.













    Parameter


    Description


    nType


    A type of socket; should be SOCK_STREAM (the default value) or SOCK_DGRAM




    Close This function closes an open socket by calling the Winsock closesocket function. The Create function must have been called previously. The destructor does not call this function because it would be impossible to catch an exception for a global object. Your server program can call Close anytime for a socket that is listening.



    Bind This function calls the Winsock bind function to bind a previously created socket to a specified socket address. Before calling Listen, your server program calls Bind with a socket address containing the listening port number and server's IP address. If you supply INADDR_ANY as the IP address, Winsock will decipher your computer's IP address.













    Parameter


    Description


    psa


    A CSockAddr object or a pointer to a variable of type ockaddr




    Listen This TCP function calls the Winsock listen function. Your server program calls Listen to begin listening on the port specified by the previous Bind call. The function returns immediately.



    Accept This TCP function calls the Winsock accept function. Your server program calls Accept immediately after calling Listen. Accept returns when a client connects to the socket, sending back a new socket (in a CBlockingSocket object that you provide) that corresponds to the new connection.



















    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




    Connect This TCP function calls the Winsock connect function. Your client program calls Connect after calling Create. Connect returns when the connection has been made.













    Parameter


    Description


    psa


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




    Send This TCP function calls the Winsock send function after calling select to activate the timeout. The number of bytes actually transmitted by each Send call depends on how quickly the program at the other end of the connection can receive the bytes. Send will throw an exception if the program at the other end closes the socket before it reads all the bytes.






















    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


    Timeout value in seconds


    Return value


    The actual number of bytes sent




    Write This TCP function calls Send repeatedly until all the bytes are sent or until the receiver closes the socket.






















    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


    Timeout value in seconds


    Return value


    The actual number of bytes sent




    Receive This TCP function calls the Winsock recv function after calling select to activate the timeout. This function returns only the bytes that have been received. For more information, see the description of the CHttpBlockingSocket class in the next section.






















    Parameter


    Description


    pch


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


    nSize


    The maximum number of bytes to receive


    nSecs


    Timeout value in seconds


    Return value


    The actual number of bytes received




    Send Datagram This UDP function calls the Winsock sendto function. The program on the other end needs to call ReceiveDatagram. There is no need to call Listen, Accept, or Connect for datagrams. You must have previously called Create with the parameter set to SOCK_DGRAM.

























    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


    Timeout value in seconds


    Return value


    The actual number of bytes sent




    Receive Datagram This UDP function calls the Winsock recvfrom function. The function returns when the program at the other end of the connection calls SendDatagram. You must have previously called Create with the parameter set to SOCK_DGRAM.

























    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


    Timeout value in seconds


    Return value


    The actual number of bytes received




    GetPeerAddr This function calls the Winsock getpeername function. It returns the port and IP address of the socket on the other end of the connection. If you're connected to the Internet through a Web proxy server, the IP address will be the proxy server's IP address.













    Parameter


    Description


    psa


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




    GetSockAddr This function calls the Winsock getsockname function. It returns the socket address that Winsock assigns to this end of the connection. If the other program is a server on a LAN, the IP address will be the address assigned to this computer's network board. If the other program is a server on the Internet, your service provider will assign the IP address when you dial in. In both cases, Winsock will assign the port number, which is different for each connection.













    Parameter


    Description


    psa


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




    GetHostByName This static function calls the Winsock function gethostbyname. It queries a name server and then returns the socket address corresponding to the host name. The function times out by itself.



















    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





    GetHostByAddr This static function calls the Winsock gethostbyaddr function. It queries a name server and then returns the host name that corresponds to the socket address. The function times out by itself.
















    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




    Cleanup This function closes the socket if it is open. It doesn't throw an exception, so you can call it inside an exception catch block.



    operator SOCKET This overloaded operator lets you use a CBlockingSocket object in place of a SOCKET parameter.





The CHttpBlockingSocket Class


If you call CBlockingSocket::Receive, you'll have a difficult time knowing when to stop receiving bytes. Each call will return the bytes that are stacked up at your end of the connection at that instant. If there are no bytes, the call will block, but if the sender closed the socket, the call will return zero bytes.

In the earlier section on HTTP, 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 must 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's part of the \vcppnet\Ex28a\

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:



    ReadHttpHeaderLine This function returns a single header line, terminated with a <cr><lf> pair. ReadHttpHeaderLine inserts a terminating zero at the end of the line. If the line buffer is full, the terminating zero is stored in the last position.






















    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


    Timeout value in seconds


    Return value


    The actual number of bytes received, excluding the terminating zero





    ReadHttpResponse This function returns the remainder of the server's response that's received when the socket is closed or when the buffer is full. Don't assume that the buffer will contain a terminating zero.






















    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







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 Ex28a program later in this chapter 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 will be 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++ .NET SOCK01\r\n"
"Date: %s\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 will block until a client connects to the server's port 80, and then Accept will return with a new socket, sConnect. The current thread will immediately start another thread.

In the meantime, the current thread must process the client's request, which 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 will be 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 is in the process of fulfilling a client request. In this 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 were to type http://www.consolidatedmessenger.com in a browser, the browser would send 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.



/ 319