Tips for domain name registration and picking the right domain. Web hosting basics.
Godaddy Website Builder & Domains Promo Codes: Save money at Godaddy using this special
p (more)
Tips for domain name registration and picking the right domain. Web hosting basics.
Godaddy Website Builder & Domains Promo Codes: Save money at Godaddy using this special
promo link for Discount Domains and Hosting coupon codes. Create your own professionallooking
website with a few clicks. No programming required. Get started now:
http://www.godaddy.com-specialmessage.info/ezdiscount (less)
Slide 1: Introduction to Introduction to Networking
1 Networking and Network Programming 2 TCP/IP Overview 3 WinSock Overview 4 Visual C++
I
Slide 2: Chapter 1■ Networking and Network Programming 3
1
Networking and Network and Network Programming
Slide 3: 4
Part I ■ Introduction to Networking
The purpose of this book is to show you how to make network-aware applications that run on the Microsoft Windows and Windows NT operating systems using the Windows Sockets (WinSock) Application Programming Interface (API). To that end, several practical examples are examined that utilize the basic functionality of WinSock. Network operating systems, such as Windows for Workgroups and Windows NT, provide basic file and printer sharing services. This most basic level of functionality is provided “out of the box.” Network-aware applications are programs that use the capabilities of a collection of connected computers. Network-aware programs range from custom applications that transfer data among computers on a network to mainstream applications that enable electronic mail and remote database access. The WinSock API is a library of functions that a programmer can use to build these network-aware applications. WinSock has its roots in Berkeley sockets as introduced in the Berkeley Software Distribution of UNIX. WinSock uses the TCP/IP (Transmission Control Protocol/Internet Protocol) suite, which provides the formal rules of behavior that govern network communications between all computers running this particular computer networking protocol. Before I begin the examination of network programming, look at the basics of computer networking in general. A network can be loosely defined as a collection of two or more computers that have some sort of communication path between them. A network can be loosely classified as either a local area network (LAN) or wide-area network (WAN). The use of the terms LAN and WAN is somewhat misleading because which term you use is relative to the particular network installation you’re describing. Generally speaking, a LAN covers a much more geographically restricted area than does a WAN. Whereas a LAN may connect computers within an office building, a WAN may connect computers spread across the country. With the advances in networking hardware and software, many widely dispersed LANs can now be connected to form a much larger homogeneous WAN. Devices known as bridges and routers allow for this connection of disparate LANs. Computer networks aren’t new, but they weren’t accepted in the personal computer realm until perhaps the late 1980s, when computer firms began offering cost-effective and reliable networking for the desktop PC. At that time, the primary goal of the PC network was to provide a central repository for files and to allow printers to be shared among many users. It hasn’t been until relatively recently that businesses have realized the true potential of a PC network.
Slide 4: Chapter 1■ Networking and Network Programming5
Goals of Networking
The goals of PC networking have been expanding over the last few years—from simple file and printer sharing to access of fax machines, modems, and enterprise-wide electronic mail systems. All the while, the essential goals of networking have always been to share resources and to provide a medium for communications.
Resource Sharing
For the sake of this discussion, a network resource is either a device or a capability on the network that’s available for use by network users. The computer that the network resources are attached to is called the server. The other computers that access those resources over the network are called clients. The typical PC network user today takes shared file and printer access for granted. But there are now other resources that also can be made available to the user. Among them are fax machines, modems, compute servers, and database servers.
Files
The traditional use of PC networks has been and probably always will be to act as a repository for files. By storing files in a common location accessible to coworkers, for example, much productivity can be gained. Several products exist from Microsoft and other vendors that provide this capability. Windows for Workgroups is one such product. It’s classified as a peer-to-peer network, which means that there’s no dedicated, centralfile server. Instead, any computer on the network can share files with any other; any computer on the network can act as either a client, server, or both. Windows NT and Windows NT Advanced Server expand on this idea by providing a much more robust filesharing capability and better file system security. Figure 1.1 shows two computers labeled Computer A and Computer B. Each has access to files on the File and Print Server and stored on the computer server. To illustrate the difference between the central-file server model and the peer-to-peer model, examine the following scenario, where Computer A has a file on its hard disk that it would like to make available to Computer B. In the central-file server model, Computer A must connect to the File and Print Server, place the file on the server’s hard disk, and then inform Computer B that the file is available. Computer B then connects to the File and Print Server and accesses the file deposited there by Computer A. Using a peer-to-peer network, Computer A could simply give Computer B permission to access the file on Computer A’s hard disk.
Slide 5: 6
Part I ■ Introduction to Networking
FIGURE 1.1. File and printer resource sharing.
Computer A Computer B
File and Print Server
Laser Printer
At a minimum, a computer network with a file server, whether it be centralized or peertopeer, prevents the use of the infamous “sneaker net” whereby files are transferred between computer users by first putting the desired files onto a floppy disk and then walking that floppy over to the designated recipient.
Printers
Another popular use of PC networks is to make printers available to all network users. This obviously provides a great cost savings by reducing the number of expensive printers and the cost of the maintenance and management of those devices. Windows for Workgroups and Windows NT provide printer-sharing capabilities. As Figure 1.1 shows, the two computers labeled Computer A and Computer B, as well as the File and Print Server computer, have access to the laser printer attached to the File and Print Server. When the user seated at Computer A prints a document, it’s sent to the File and Print Server where it’s printed. If a user at Computer B tries to print a document while Computer A’s document is still printing, Computer B’s document is stored in a temporary location on the File and Print Server. This process is called printer spooling. As soon as the first submitted print job belonging to Computer A’s user is complete, the next job, belonging to Computer B’s user, is begun. A Windows NT-based network fully supports the remote management of networked printers. An administrator of a Windows NT network can monitor the status (to see if the paper is out, for example) of a remotely located printer and also manipulate the queue
Slide 6: Chapter 1■ Networking and Network Programming7
of jobs waiting to be printed. He could, for instance, pause a print job sent by a user at 1:00 to make a print job submitted at 1:05 print first. The administrator can control other printer options such as which hours of the day the printer is available, who on the network can access the printer, and which users’ print jobs have higher priority than others.
Fax Machines
The fax “machines” that are now being attached to computer networks are really just specialized modems that can communicate with other fax “modems” and more traditional stand-alone fax machines. There are several benefits to networked fax modems. Most documents that are eventually faxed are created with a computer’s word processing program, printed on a traditional laser or dot-matrix printer, and then fed into a traditional fax machine. The networked fax modem saves these last two time-consuming steps by allowing the word processing program to “print” directly to the fax device. Most fax modems appear as just another printer to the word processing program. When the user selects the fax modem as the “printer” to print to, the user is asked to fill out a fax cover sheet which includes the recipient’s name and the telephone number of the recipient’s fax machine. A fax modem also allows a fax to be easily distributed to multiple recipients at different telephone numbers. Microsoft at Work fax software is a component of the Microsoft at Work architecture (to be discussed shortly). This software allows networked computers running Windows to share a fax modem attached to one of the networked computers.
Modems
Standard modems, as opposed to fax modems, are also being used now as shared devices on PC networks. In the past, users who had a regular need for modem communications had to have their own personal modems. More often than not, that modem was very underutilized. By arranging several modems in a modem pool reachable by a network, many more users can have access to modem communications without the expense of personal modems. When users need to access a modem, they request one from the modem pool and then proceed as if that modem were theirs alone. When they’re finished with the modem resources, those resources are freed and made available to the next user who requests them. Figure 1.2 shows four computers and a two-modem pool. The Modem Pool Server is the computer that manages the modem pool. Computers A, B, and C have access to these modems. When Computer A requests the use of a modem, the Server removes that modem from its availability list. When Computer B requests a modem, the Server assigns the second modem to Computer B. If Computer C then requests a modem, the request is placed in a queue. As soon as Computer A or B relinquishes its “borrowed” resource, Computer C gets use of that freed modem.
Slide 7: 8
Part I ■ Introduction to Networking
FIGURE 1.2. Modem pool.
Computer A
Computer B
Computer C
Modem Pool Server
Modem 1
Modem 2
Modem access can also operate in the opposite direction. It’s possible to have remote users dial into the computer network. Once they’re logged to the network, the remote users have the same access to network resources as users whose computers are physically attached to the network. Microsoft’s Remote Access Service (RAS) includes this capability for Windows for Workgroups and Windows NT.
Compute Servers
Another shared resource that can be found attached to some PC networks is the compute server. This device is usually a very powerful computer that’s geared toward performing specialized tasks. For example, compute servers can be tuned for exceptional floating-point calculation performance. A compute server isn’t necessarily practical for use as a general-purpose computer, so users access this resource from a workstation or desktop PC. The results of the compute server’s work can be displayed on the local desktop PC using a remote graphical user interface. This graphical interface is based on the X Windows system developed at the Massachusetts Institute of Technology. Another way to take advantage of a compute server is to use Remote Procedure Calls (RPC) in your desktop application. Using RPCs, the application on the desktop computer makes calls to functions that happen to execute on the remote compute server. When the function has completed, the results are returned to the desktop computer as if the function call took place locally. Figure 1.3 shows one possible scenario with a powerful mainframe compute server serving the needs of both an X Windows workstation and a PC using RPCs.
Slide 8: Chapter 1■ Networking and Network Programming9
FIGURE 1.3. Compute server.
Computer A X Windows
Computer B Remote Procedure Calls
Mainframe Compute Server
Database Servers
The networked database server is similar to the compute server but is more common in today’s business environment. With this network resource, desktop PCs can query and modify a database that’s made available to the entire enterprise. Figure 1.4A shows one possible configuration. The user seated at the client computer sends a query to the database server. The database server receives the query parameters and processes the request. When the database server has completed the necessary processing, a response is returned to the client computer. This configuration is known as client/server architecture. The client/server programming model is explained later in this chapter. Several vendors, including Microsoft, Oracle, and Sybase, provide database servers that are geared toward the high-end client/server market. Many companies, such as Borland, Gupta, Microsoft, PowerSoft, and Oracle, provide tools necessary to build the client portion of the client/server solution. A client/server database is especially useful when several people need access to the same information. This architecture is desirable because it allows people in different locations, possibly even on opposite sides of the globe, to share and modify common information. The client/server database architecture not only makes it easy to locate users at disparate locations but also allows freedom in the location of the databases. With this
Slide 9: 10
Part I ■ Introduction to Networking
scenario, as shown in Figure 1.4B, the database can be maintained where it makes the most sense. In an order entry system, for example, it might make sense to have the billing department maintain the customer records in one database while the inventory records are maintained by the parts department in a different database stored on another database server. During order entry time, the order entry clerk can access both databases and get the most up-to-date information.
FIGURE 1.4A.
Database server.
Query Client Computer Database Server
Step 1: The client sends a query to the database server
Processing
Client Computer
Database Server
Step 2: The database server processes the query
Response Client Computer Database Server
Step 3: The database server returns a response to the client
Communications Medium The complementary, and somewhat overlapping, goal for a computer network is to act as a communications medium. In a basic sense, if this communications medium didn’t exist, neither would the ability to share any network resources, as described earlier. In this context, however, the communications medium allows network users to communicate better with each other. To facilitate this human-to-human communication, many networked software tools have been built. Among them are electronic mail systems, workgroup scheduling programs, and electronic forms processing systems.
Slide 10: Chapter 1■ Networking and Network Programming 11
FIGURE 1.4B.
Departmental database servers.
Billing Department
Parts Department
Customer Records
Inventory Records
Order Entry Computer
Electronic Mail
Electronic mail, known as e-mail, has had wide acceptance in the arena of larger computer systems such as those that run a UNIX operating system derivative. PC networks now have e-mail capability, too. Not only can e-mail be shared between PC network users, but it can be routed to users on other networks that are based on high-end workstations or multiuser computers. This capability means that all computer users in a business setting can use electronic communications among themselves and can still use the best computer and operating system combination to meet their primary job responsibilities. Modern e-mail systems have been extended further so that embedded or attached objects can be sent. These objects can be as simple as additional textual information or as diverse as a computer-playable video presentation or a financial spreadsheet. When recipients read their e-mail, the attachments are immediately available. Windows for Workgroups and Windows NT are bundled with e-mail support.
Slide 11: 12
Part I ■ Introduction to Networking
Electronic Forms
With electronic forms an organization can easily exchange structured information. Eforms are the modern-day equivalent of the printed paper form. A simple example of a form is the popular “While You Were Out” message your secretary might fill out. By combining e-forms with an e-mail system, your messages will appear as nicely formatted, standardized e-mail messages instead of pieces of paper scattered about your desk. Another use of an e-form might be by a salesman who enters a customer’s order on his laptop computer while on site. The e-form is then either immediately transferred to a host computer using a modem or transferred at the end of the business day in a batchtype mode where all the day’s orders are sent.
Workgroup Scheduling
Workgroup scheduling helps coworkers manage their time and communicate with each other more effectively. A network scheduling program provides network computer users with the ability to view and modify each other’s day planners. This ability makes it easier to schedule group meetings, make personal appointments, and assign tasks to an available individual. Microsoft’s Windows for Workgroups and Windows NT include a scheduling program that has this capability.
Chat
A chat utility can replace a phone call for simple one-to-one or one-to-many communications. Under a UNIX-based operating system, the chat utility is called talk. In the Windows and Windows NT environment, the chat utility is called Chat and is executed by running the WINCHAT.EXE program. With both programs you can connect to at least one other network user and then type messages back and forth.
World Wide Web and Mosaic
On a more global scale, the World Wide Web (WWW) is gaining in popularity. The WWW was started at CERN, the European Laboratory for Particle Physics. Its original purpose was to facilitate the communication between fellow particle physicists. WWW is a distributed system with which users can access documents of varying types, from simple text files to graphical images stored in the GIF or JPEG format. The data may
Slide 12: Chapter 1■ Networking and Network Programming 13
contain links to other related data. By traversing these links, information on a particular topic can be found. This world-wide connection is made possible by the Internet, a collection of an estimated 2,000,000 interconnected computer systems spread the world over. To traverse the Web, as it is known, the user needs a sophisticated Web client or browser. The browser’s duties involve traversing the links, retrieving data of miscellaneous types, and providing viewers for that data. One of the best Web browsers available is Mosaic, developed at the National Center for Supercomputing Applications. Figure 1.5 shows Mosaic for Windows connected to a computer that Microsoft has made available for support of its products. Pointing the mouse cursor at the folder and clicking the mouse button causes Mosaic to redraw the screen with the newly selected folder’s information. When users see data files they would like, another mouse click causes files to be retrieved to the users’ machines where they’re displayed, if there are appropriate viewers available, or saved to disk for later examination. Webs are even finding a place for internal use in the corporation. The data stored at a Web site is inherently cross-platform. This means a company can produce a document once, possibly including multimedia-type extensions, and have that document available for viewing by users of Windows, Macintosh, or UNIX computers. All that is needed is a Web browser for each platform.
FIGURE 1.5.
Mosaic.
Slide 13: 14
Part I ■ Introduction to Networking
Coming Soon The future promises more networking options. One exciting area of development involves wireless communications. Usually associated with the notion of a Personal Digital Assistant, wireless communications will allow access to an enterprise’s network from anywhere on the planet. Imagine being able to access your e-mail messages from the beach.
Another area of interest, particularly to Microsoft, is to make network resources more easily shared and utilized. To meet this goal, Microsoft initiated the Microsoft at Work program in 1993. One component of Microsoft at Work discussed earlier was Microsoft at Work fax software, with which network users can send faxes and computer files directly from their desktop computers. Soon we will see other Microsoft at Work-enabled devices, such as telephones and photocopiers. These devices will have a touch screen surface with a simplified Windows interface and will also be available as network resources. With an “At Work” enabled photocopier, for example, a network user will be able to send a print job directly from his favorite word processor program and have the desired number of copies printed, collated, and stapled. As networks get more sophisticated and the amount of network traffic they can handle increases, video conferencing becomes more viable. Video conferencing usually involves the use of a multimedia-enabled computer that includes a video camera and microphone. With such a configuration, two or more users can see and hear each other, as well as type messages back and forth as the outdated Chat type utility allows. By combining video conferencing with a networked “white-board” utility, on which networked users can see and manipulate visual computer data, coworkers are able to collaborate on work even though they may be located in different offices, different states, or even different countries. It might be a while before that kind of network bandwidth exists though.
Network Topologies
The previous sections described the capabilities of a computer network. But how are all of these file, print, fax, and compute resources connected so as to allow the typical desktop computer to access them? Network topology refers to the way networked computers and network resources are connected. The three most widely used topologies are bus, ring, and star. Note that the following network topology diagrams are logical views of the topologies they represent and don’t necessarily match the physical (electrical) interconnections on the networks.
Slide 14: Chapter 1■ Networking and Network Programming 15
Bus Network The bus network topology, shown in Figure 1.6, connects each computer to a single cable. At each end of the cable is a terminating resistor or a terminator. An electrical signal is passed back and forth along the cable past the computers and between the two terminators. The bus carries a message from one end of the network to the other. As the bus passes each computer, the computer checks the destination address on the message. If the address in the message matches the computer’s address, the computer receives the message. If the address doesn’t match, the bus carries the message to the next computer, and so on.
Bus topology is passive, meaning that computers only listen for data being sent on the network and aren’t responsible for moving data from one computer to the next. If one computer fails, it doesn’t affect the entire LAN. On the other hand, if a cable breaks, the entire cable segment (the length between the two terminators) loses its connectivity, so that the entire segment isn’t functional until the cable can be repaired. Each computer attached to a bus network can transmit data whenever it “wants.” This capability means that two computers may try to transmit simultaneously. This occurrence is called a collision. A collision is detected by the network hardware of the sending computers. When a collision is detected, the packets of data that generated the collision are retransmitted. The limitation of bus networks is the speed of data transmission relative to the number of computers on the network. As more computers are added to the network, more collisions are bound to happen. As more collisions occur, more retransmissions take place and the overall network performance degrades. Ethernet is one example of a common bus network found on many local area networks. Ethernet is also the most popular LAN architecture in use today.
Note
Ethernet was developed by the Xerox Corporation in 1972 as the follow-up to some research done at the University of Hawaii. Ethernet first became commercially available in 1975 as a 2.94 Mbps network able to connect up to 100 computers spread over a 1-kilometer cable. Xerox Ethernet soon became popular, and work was done with the Intel Corporation and Digital Equipment Corporation to extend Ethernet’s capability to 10 Mbps. Today, 100 Mbps Ethernet is gaining in popularity.
Slide 15: 16
Part I ■ Introduction to Networking
Ethernet networks can be wired with different types of cable, each with its own benefits and drawbacks. Three popular specifications for Ethernet topologies are 10BASE2, which uses thin coaxial cable (Thinnet) that can carry a signal up to approximately 607 feet; 10BASE5, which uses Thicknet cabling that can carry a signal for about 1,640 feet; and 10BASET, which uses unshielded twisted-pair cable that can carry a message for about 328 feet between a computer and the hub to which the computer is connected.
FIGURE 1.6.
Bus network.
Ring Network Figure 1.7 shows a ring network. In a ring network, a packet of data (often called a token) is continually moving around the ring from one computer to the next. To send data, a computer on the network must wait for the circulating token to pass by. When the token arrives, it’s examined to see whether it’s empty. If it’s empty, the computer that wishes to transmit adds its data to the token packet and addresses the packet to a destination. As the token passes by the destination computer, the computer looks at the address and because the message is addressed to itself, extracts the data, and replaces the token packet’s data with a delivery acknowledgment message. The token then continues to circle the ring and eventually returns to the sending computer. The sending computer examines the token packet to see if it contains the data it sent or an acknowledgment message. If it doesn’t find an acknowledgment message, the sender knows that the data wasn’t received, possibly because the destination computer wasn’t operating. The sender then clears the token packet and passes it along the ring to allow subsequent computers their chance to use the network’s communication resources. The token passing scheme is in contrast to the bus topology whereby any computer can send at any moment and the protocol must detect collisions. Collisions of this nature can’t occur on a ring network.
Slide 16: Chapter 1■ Networking and Network Programming 17
Note
The first design of a network passing a token ring is attributed to E. E. Newhall in 1969. IBM first publicly supported a token-ring topology in March 1982, and announced its first token-ring network product in 1984.
Data on the IBM token-ring network is transmitted at either 4 or 16 Mbps, depending on the actual implementation. For computers to communicate with each other, all network cards must be configured similarly to communicate at either 4 or 16 Mbps on the network. Networked computers are connected by shielded and/or unshielded twistedpair cable to a wiring concentrator called a Media Access Unit or MAU (rhymes with cow). Each MAU can support as many as 72 computers that use unshielded wire or up to 260 computers using shielded wire. Each ring can have as many as 33 MAUs allowing for a theoretical maximum of 8,580 computers on the network.
FIGURE 1.7.
Ring network.
Star Network To transmit data between any two computers in a star network, shown in Figure 1.8, requires that data be sent via the centrally located computer, called a hub. The hub provides a common connection so that all the computers can communicate with one another. To extend the star network, hubs can be connected to one another. The major problem with star networks is that if the centrally located hub isn’t operating, the entire network becomes unusable. A benefit of a star network is that no computer, other than the centrally located hub, can interrupt network traffic.
Slide 17: 18
Part I ■ Introduction to Networking
FIGURE 1.8.
Star network.
Internetworking The previous section detailed different network topologies. This section will show that these disparate networks can be interconnected and may even be separated by thousands of miles. This scenario is called internetworking. Figure 1.9 shows a well connected network composed of a bus network, a ring network, a satellite connection to a remote server, and a dial-up modem connection. Notice the device called the Gateway. This device is used to connect the bus network to the ring network. Its job isn’t only to bridge the two networks hardware-wise but also to route data between the two when the destination of a data packet isn’t local to either the bus or ring network. Routing and gateways are described more fully in Chapter 2, “TCP/IP Overview.” In this network, the laptop computer has the same access to resources connected to the bus network’s Workstation computer as does the bus network’s Macintosh computer. Of course the access times may not be the same for the laptop computer and the Macintosh.
Does Network Topology Matter?
Fortunately, the average application programmer has little need to know the topology details of the network his software will run on. Most of these details are hidden from
Slide 18: Chapter 1■ Networking and Network Programming 19
the application program by a networking application programming interface (such as WinSock). The application programmer will need to be concerned with these nittygritty details if the application being developed has any special requirements, such as fault tolerance or guaranteed response times. The programmer may also be concerned about the underlying network hardware. Ethernet was listed earlier as an example of a bus network. Some networks may not be as simple as Figure 1.6 appears. As several local area networks are interconnected, and as wide area network links, such as those provided by satellites, are added, network topology becomes an issue with the network application programmer. You can’t assume that data will reach its destination in less than 100 milliseconds, for example. The best advice is to make your network programs as configurable and robust as possible, especially with regard to time-out values.
FIGURE 1.9.
Internetworking.
Desktop PC Macintosh Bus Network Desktop PC Desktop PC Ring Network Gateway Macintosh
Desktop PC
Workstation
Satellite
Satellite Dish
Satellite Dish
Telephone Line Modem Server Modem Laptop Computer
Look at Figure 1.9 as an example of a network configuration that requires flexible network applications. If the server is acting as a database server, it must serve the client computers on the bus network, the ring network, and the telephone line. The network access times are different for the Macintosh on the bus network than they are for the laptop computer dialed with a 9,600-baud modem, for example.
Slide 19: 20
Part I ■ Introduction to Networking
The application programmer should make the server aware of the disparity in performance when it communicates with the many other computers it serves. Those other computers must likewise be knowledgeable about their connectivity to the server. The client software running on the laptop computer may have a five-second time-out for database access while the bus network’s Macintosh may need a 10-second time-out to make up for the delays introduced by the satellite link. Attention to details such as this early in a network application’s development cycle may save a lot of aggravation later.
Network Programming Models
The previous section discussed ways that computers and other resources can be attached to a network. But what do we do now that we have networked computers that can communicate with one another and share common resources? We need software that can take advantage of the network. This section begins a discussion of network programming. Network programming can be thought of in two primary contexts: client/server and distributed.
Client/Server Computing In the client/server computing model, an application is split into two parts: a front-end client that presents information to the user and collects information from the user, and a back-end server that stores, retrieves, and manipulates data and generally handles the bulk of the computing tasks for the client. In this model the server is usually a more powerful computer than the client, oftentimes a minicomputer or mainframe, and serves as a central data store for many client computers, making the system easy to administer. Client/server architecture increases workgroup productivity by combining the best features of stand-alone PCs with the best features of minicomputers and mainframes. Client/server architecture makes the best use of high-end server hardware and reduces the load on client PCs. Load reduction, in turn, provides superior performance and minimizes network traffic. Figure 1.4A shows one example of a client/server interaction with a client accessing a database server.
A server is any program that runs on a networked computer and can provide a service. A server receives a request over the network, performs the necessary processing to service that request, and returns the result to the requester. The client is the program that sends a request to a server and waits for a response. For a client and server to communicate and coordinate their work, an interprocess communication (IPC) facility is needed. The subject of this book, WinSock, can be used to satisfy this requirement. Chapter 15, “Practical Client/Server Database Application,” introduces an example that will demonstrate client and server database implementations.
Slide 20: Chapter 1■ Networking and Network Programming 21
One server program can service several client requests at the same time. For this reason, implementing servers tends to be more difficult than implementing clients. To provide the capability of supporting several client requests simultaneously, servers are usually built in two parts: a single master that accepts requests and one or more slaves that actually process and respond to the individual requests. Client/server architecture contrasts with the classical centralized architecture popularized by typical mainframe installations. In a centralized environment, the “clients” are little more than dumb terminals that act as simple data entry/display devices. There’s a minimum of work done at the terminal. The user typically fills in the fields of a form before sending the field data to the central computer. All processing and screen formatting is done on the central computer, and the dumb terminal simply displays the preformatted data. In a client/server environment, the client has much greater intelligence and more freedom with the final visual presentation of the data to the user. Instead of the data being preformatted to match the way it will be viewed, it’s sent back in its “raw” format, and the application running on the client computer “decides” how to display that data. Thus the “front end” that the user sees can be customized while the “back end” remains unchanged.
Distributed Computing The distributed architecture can be thought of in two different ways: precollection and parallel processing.
Precollection is the act by which background processes on networked computers con-
currently collect and propagate information before that information is requested. An example would be a program that requests the status of every other computer on the local network. In the client/server environment, the client program would have to send a request to each computer on the network and wait for a response. This procedure is potentially very time-consuming. In a distributed implementation, each computer on the local network would have a process that runs continually in the background and that reports status information to every other computer on a regular interval. When the program is run to request the status information of every other computer on the local network, the response comes back immediately because the information was precollected in each local computer. Of course this solution wouldn’t work well if the information being requested was time sensitive, because the delay in the updates would make the response outdated. Shortening the time between the updates sent by the networked computers wouldn’t work well either because of the possibility of saturating the network’s data-handling capabilities. When most people think of parallel processing, they think of a computer that has more than one processing unit. Parallel computing in a distributed system environment means
Slide 21: 22
Part I ■ Introduction to Networking
taking advantage of more than one computer on the network to perform a specific task. Suppose that you, as a software developer, want to do a large project build late at night when few other programmers are at work. Wouldn’t it be nice to take advantage of all that idle processing power lying in each programmer’s workstation or desktop PC? With a build utility that was designed for a distributed environment, your build could execute in a parallel fashion with certain modules being compiled on certain computers and other modules being compiled on other computers. The project build could be completed in a greatly reduced time. This assumes that the source code going into the build is located somewhere on the network and is as easily accessible by all other computers as it is to your own. If the source code is located on your local computer, the overhead in shipping it across the network to the other computers might overshadow the benefit of multiple modules being built simultaneously.
Summary
This chapter has discussed the basics of computer networking and network programming. The primary purpose of most computer networks is to allow for the sharing of resources such as files, printers, modems, and fax machines, and to facilitate the communication of the people using the networked computers. Network topology refers to the way networked computers and network resources are connected to each other, with the most popular topologies being bus, ring, and star. To take advantage of the network’s connectivity, network-aware application software is used. The client/server and distributed models are two popular methodologies followed when writing networked software. To write this networked software, a network application programming interface (API) is used by the programmer. One such network API, and the focus of this book, is WinSock. The following two chapters will discuss TCP/IP, the underpinnings of WinSock, and WinSock itself.
Slide 22: Chapter 2 ■ TCP/IP Overview
2
23
TCP/IP Overview verview
Slide 23: 24
Part I ■ Introduction to Networking
Computer network protocols are formal rules of behavior that govern network communications. The Transmission Control Protocol (TCP) and Internet Protocol (IP) are just two of the data communication protocols encompassed by the Internet Protocol Suite. This protocol suite is usually referred to as TCP/IP partly because TCP and IP are two of the most important protocols of the collection. TCP/IP includes a set of standards that specify how networked computers communicate and how data is routed through the interconnected computers. TCP/IP provides the application programmer with two primary services: connectionless packet delivery and reliable stream transport. These will be discussed in detail later in this chapter. TCP/IP has several distinguishing features that have led to its popularity, including Network Topology Independence. TCP/IP is used on bus, ring, and star networks. It’s used in local-area networks as well as wide-area networks. Physical Network Hardware Independence. TCP/IP can utilize Ethernet, token ring, or any number of physical hardware variations. Open Protocol Standard. The TCP/IP protocol suite standard is freely available for independent implementation on any computer hardware platform or operating system. TCP/IP’s wide acceptance and the fact that TCP/IP is available on platforms ranging from supercomputers to desktop personal computers makes it an ideal set of protocols to unite different hardware and software. Universal Addressing Scheme. Each computer on a TCP/IP network has an address that uniquely identifies it so that any TCP/IP enabled device can communicate with any other on the network. Each packet of data sent across a TCP/IP network has a header that contains the address of the destination computer as well as the address of the source computer. Powerful Client-Server Framework. TCP/IP is the framework for powerful and robust client-server applications that operate in local-area networks and wide-area networks. Application Protocol Standards. TCP/IP doesn’t just provide the programmer with a method for moving data around a network among custom applications. It also provides the underpinnings of many application-level protocols that implement such common functionality as e-mail and file-transfer capabilities. The current incarnation of the Windows Sockets library is built on TCP/IP, although there’s nothing inherent in WinSock precluding it from utilizing an alternate protocol
Slide 24: Chapter 2 ■ TCP/IP Overview
25
stack. In fact, work is in progress on the next version of WinSock, which will support the use of Novell’s IPX/SPX, Apple’s Appletalk, and other popular network protocols.
NOTE
The term protocol stack has been mentioned a few times now. It refers to the way some network communication protocols, including TCP/IP, are composed of several logical layers of software where each layer communicates with the layer directly above and below itself. At the top of this stack is the application layer that you, as the applications programmer, provide. The bottom of the stack is generally thought to be the layer that communicates with the network hardware (that is, the Ethernet or Token Ring). The upcoming section titled “ISO OSI Protocol Stack” provides a more complete definition of a protocol stack.
TCP/IP History
The history of the TCP/IP protocol suite can be traced back to one of the first widearea networks consisting of computers from different manufacturers running different operating systems.
ARPANET This experimental network was called ARPANET, and its development was sponsored by the Defense Advanced Research Projects Agency (DARPA) in 1969 with the goal of creating a network to provide robust data communications among computers from different vendors. Before ARPANET, most computer networks were homogeneous, consisting of computers from the same hardware manufacturer running the same operating system.
The ARPANET’s popularity became apparent, and in 1975, it was converted from an experimental network into a fully operational network that was used for daily communications among researchers at the connected sites. But research into network protocols continued and the Internet Protocol Suite resulted. The TCP/IP protocols were adopted as Military Standards in 1983, and all computers connected to the ARPANET were required to adopt the new protocol. The ARPANET was then split into two networks: the MILNET, used for unclassified military communications, and the new, much smaller ARPANET, used for further research. MILNET and ARPANET together became known
as the Internet.
Slide 25: 26
Part I ■ Introduction to Networking
Berkeley Software Distribution DARPA was also interested in expanding the Internet by attaching university computing sites. At that time, most university computer science departments were running a version of the UNIX operating system developed at the University of California at Berkeley. This implementation of UNIX is known as the Berkeley Software Distribution or BSD UNIX. DARPA funded Bolt Beranek and Newman, Inc. to implement TCP/ IP for UNIX and funded Berkeley to incorporate the protocols into its software distribution. This funding, combined with the fact that many university computer science departments were adding more computing resources that needed to be interconnected, all but guaranteed a wide audience for TCP/IP.
The programmers for BSD UNIX didn’t simply take TCP/IP as it came from Bolt Beranek and Newman. They added an abstracted layer for the use of application developers called sockets. Sockets, which WinSock is based on, make it easy for the application programmer to write networked code. The BSD programmers also added several utilities to their UNIX that were built upon pre-existing commands used in the standalone computing environment. For example, the new remote copy command rcp was introduced. This command extended the standard copy command called cp to the network. Network-aware utilities such as these, and the power that can be wielded from them, helped BSD UNIX and its TCP/IP gain wide acceptance. With the popularity of BSD UNIX at universities, the size of the connected Internet grew. The even wider acceptance of TCP/IP soon after BSD’s inclusion of the protocols led to an astronomical Internet growth rate that has yet to peak. In 1983, the Internet connected a handful of computing sites. Today, the Internet connects two million computers and forty million people spread all over the world.
NOTE The use of the term internet is sometimes confusing because it’s used haphazardly to mean so many different things. TCP/IP is another name for the Internet Protocol Suite. TCP/IP and Internet Protocol are used interchangeably. Originally, if someone said their computing site had an internet, it meant there were two or more networked computers that used the Internet Protocol between them. The term internet (notice the lower case i ) is now used to refer to any collection of physically separate networks that share the same communication protocols (not necessarily TCP/IP) to appear as a single logical network. The term Internet (notice the upper case I ) is used to refer to the worldwide collection of interconnected computer networks that run the TCP/IP protocols.
Slide 26: Chapter 2 ■ TCP/IP Overview
27
Request for Comments No single company or group “owns” TCP/IP. The protocols are freely distributable, and anyone is allowed to incorporate them into his or her operating system or computer network. You may be wondering how TCP/IP is expanded and how the application protocols mentioned earlier are developed. The answer lies in Internet Request for Comments (RFC).
RFCs provide for an informal method of establishing new TCP/IP protocol standards. RFCs usually begin as Internet drafts. After the Internet community has had a chance to critique the draft it becomes an RFC. RFCs are numbered sequentially, so later RFCs have higher numbers than those created earlier. Some RFCs supersede earlier ones, but the new RFC will always make note of that. The number an RFC receives is distributed by an organization at SRI International called the Network Information Center or the NIC (pronounced nick). The NIC is funded by the Defense Communication Agency (DCA), which manages the military’s computer network infrastructure. The NIC isn’t only responsible for an RFC’s number, it’s also responsible for the storage and distribution of TCP/IP protocols, allotment of Internet addresses, and the registration of the names of Internet-connected computers. One example of an RFC is RFC 742, which defines the finger protocol. The finger protocol is usually accessed by the user using a finger program. Finger allows a user to find out certain information about a specific user on a specific networked computer or all users on a specific networked computer. The finger protocol describes the communications that must take place between the finger client program that a user runs and a finger server that continuously runs on the computer that’s being “fingered.” You’ll build a finger client program for Windows in a later chapter.
ISO OSI Reference Model
A group called the International Standards Organization (ISO) introduced the Open Systems Interconnection (OSI) Reference Mode, a layered network architecture, with the goal of international standardization of computer network protocols. The OSI model is said to be an open systems architecture because it connects computer systems that are open for communications with other systems. The connected computer systems don’t have to be from the same manufacturer and don’t have to run the same operating system. TCP/IP and the OSI model share a similarity in this regard.
Slide 27: 28
Part I ■ Introduction to Networking
The Seven-Layer OSI Model The OSI model is composed of seven layers as shown in Figure 2.1. Taken together, these layers define the functions of data communication protocols. Each layer of the OSI model represents a function performed when data is transferred between cooperating applications across a connecting network. A layer doesn’t have to define a single protocol; it defines a function that’s performed by any number of protocols. For example, the finger protocol and a file transfer protocol both fit into the Application Layer. According to Andrew Tanenbaum’s Computer Networks book, the layers were defined with the following ideals in mind: A layer should be created where a different level of abstraction is needed. Each layer should perform a well-defined function. The function of each layer should be chosen with an eye toward defining internationally standardized protocols. The layer boundaries should be chosen to minimize the information flow across the interfaces. The number of layers should be large enough that distinct functions don’t have to be thrown together in the same layer out of necessity, and small enough that the architecture doesn’t become unwieldy.
FIGURE 2.1.
The ISO OSI reference model.
Sending Application
Receiving Application
Application Layer Presentation Layer Session Layer Transport Layer Network Layer Datalink Layer Physical Layer
Application Layer Presentation Layer Session Layer Transport Layer Network Layer Datalink Layer Physical Layer
Slide 28: Chapter 2 ■ TCP/IP Overview
29
Application Layer
The application layer provides end-user services such as file transfer and e-mail. This is the layer closest to what the user of the computer sees and manipulates. The finger protocol described earlier fits into this layer because it’s the protocol that defines the client application’s interaction with the server application providing the finger service. The finger client communicates with its peer finger server. A peer is a protocol implementation that resides in the equivalent layer on a remote system.
Presentation Layer
The presentation layer controls how data is represented. This is the layer in which data compression might take place, for example. Using data compression as an example, when data passes from the application layer to the presentation layer, the presentation layer compresses the data before passing it on to the session layer. When data arrives, it’s passed from the session layer to the presentation layer where it’s uncompressed and passed on to the application layer. Hence, the presentation layer really performs a data manipulation function, not a communication function.
Session Layer
The session layer manages the process-to-process communication sessions between hosts. It’s responsible for establishing and terminating connections between cooperating applications.
Transport Layer
The transport layer performs end-to-end error detection and correction. This layer guarantees that the receiving application receives the data exactly as it was sent.
Network Layer
The network layer manages network connections. It takes care of data packet routing between source and destination computers as well as network congestion.
Datalink Layer
The datalink layer provides reliable data delivery across the physical network. It doesn’t assume that the underlying physical network is necessarily reliable.
Physical Layer
The physical layer is concerned with transmitting and receiving raw bits over a physical communication channel. Ethernet is one example of such a channel. This layer has knowledge of voltage levels and of the pin connections to the physical hardware media.
Slide 29: 30
Part I ■ Introduction to Networking
These seven layers and the way they’re represented as building blocks stacked one on top of the other has led to the term protocol stack or simply stack.
TCP/IP and the OSI Model TCP/IP doesn’t directly follow the OSI model. Although each network model has the goal of facilitating communication among different makes and models of computers, even when those computers are running dissimilar operating systems, each network model has resulted in different implementations. Whereas the OSI model is driven by a large standards organization, which takes a long time to formulate and adopt a standard, TCP/ IP was driven by the immediate need of the United States government. TCP/IP development isn’t burdened with the same stringent requirements as OSI. Most of TCP/IP’s advances have been made by individuals and small groups through the issuance of RFCs. The process of creating and adopting an RFC is more expeditious than the equivalent procedure in the ISO. This has led some to say TCP/IP isn’t a very “pure” architecture. Pure or not, TCP/IP is the set of protocols used to connect more computers in the world today than any other.
Although OSI and TCP/IP differ, it’s still useful to use the seven-layer model as a frame of reference when discussing data communications. Figure 2.2 shows the layered architecture of TCP/IP.
FIGURE 2.2.
Application Layer Transport Layer Internet Layer Physical Layer
TCP/IP layered architecture.
Application Layer
The application layer consists of applications that make use of the network. A file-transfer utility and the finger program discussed earlier are examples of programs that fit into the application layer. The application and presentation layers of the OSI model fit into this layer of the TCP/IP architecture. For example, if data transferred between two peer
Slide 30: Chapter 2 ■ TCP/IP Overview
31
programs is going to be compressed, the application “is responsible” for the compression and decompression. In effect, the transport layer is absorbed into the application instead of being a separate entity as it is in the OSI model. For the sake of discussion, however, you can still think of the transport function as a logical layer.
Transport Layer
The transport layer provides end-to-end data delivery. The OSI model’s session and transport layers fit into this layer of the TCP/IP architecture. The notion of OSI’s session connection is comparable to TCP/IP’s socket mechanism. A TCP/IP socket is an end-point of communications composed of a computer’s address and a specific port on that computer. OSI’s transport layer has an equivalent in TCP/IP’s TCP. TCP provides for reliable data delivery and guarantees that packets of data will arrive in the order they were sent, with no duplicates and with no data corruption.
Internet Layer
The internet layer defines the datagram and handles the routing of datagrams. The datagram is the packet of data manipulated by the IP protocol. A datagram contains the source address, destination address, and data, as well as other control fields. This layer’s function is equivalent to that of the OSI’s network and datalink layers. The IP (as in the right-hand side of TCP/IP) is analogous to the network layer. It’s responsible for encapsulating the underlying network from the upper layers. It also handles the addressing and delivery of datagrams. The datalink layer isn’t usually represented in the TCP/IP architecture, but IP could be used to support this function.
Physical Layer
TCP/IP makes no effort to define the underlying network physical connectivity. Instead, it makes use of existing standards provided by such organizations as the Institute of Electrical and Electronic Engineers (IEEE), which defines RS232, Ethernet, and other electronic interfaces used in data communications. The movement of a packet of data through the layers in a TCP/IP network is shown in Figure 2.3. When a packet of data is sent, it travels to the transport layer where the transport header is added. Next the internet layer adds its header. Finally, the physical layer attaches its header. When a packet of data is received, the process is reversed, resulting in the application’s reception of the intended data.
Slide 31: 32
Part I ■ Introduction to Networking
FIGURE 2.3.
Data movement through the TCP/IP layers.
Sending Application
Receiving Application
Data
Data
Transport Header Internet Header Physical Header Internet Header Transport Header Transport Header
Data
Transport Header Internet Header Transport Header Transport Header
Data
Data
Data
Data
Physical Header
Internet Header
Data
TCP/IP Addressing Scheme
One of TCP/IP’s distinguishing features described previously is its universal addressing scheme whereby each computer on a TCP/IP network has an address that uniquely identifies it. This universal addressing scheme extends even to the world-wide Internet, connecting more than two million computers that are connected to thousands of separate networks. It’s IP’s responsibility to deliver datagrams among the TCP/IP networked computers. To make such deliveries possible, each computer has a unique IP address composed of a 32bit number. The IP address contains sufficient information to uniquely identify a network and a specific computer on the network.
Network Classes Because a computer’s IP address must uniquely identify not only the computer but also the network the computer is attached to, the IP address is split between a network identifier (net id) part and a host identifier (host id) part. The split between these two identifiers isn’t the same for all IP addresses. The class of the address determines how many bits of the IP address are reserved for network identification and how many are reserved for host identification. There are five classes of IP address with only the first three relevant to the majority of users. Classes A, B, and C are for general-purpose use; classes D and E are reserved for special purposes and future use. Figure 2.4 shows the format of an IP address.
FIGURE 2.4.
31 Class ID Network ID Host ID
0
IP address format.
Slide 32: Chapter 2 ■ TCP/IP Overview
33
As far as the application programmer is concerned, no discernible difference exists among a class A, B, or C address. A computer with a class A address can communicate with a computer with a class C address just as well as a class A computer can communicate with another computer with a class A address. Table 2.1 shows the maximum number of networks and hosts that can exist for the different classes.
Table 2.1. IP address class allocation.
Network Class A B C
Networks 126 16,382 2,097,150
Hosts per Network 16,777,214 65,534 254
Not all network identifiers or host identifiers are available for use. Some addresses are reserved for special use. If Table 2.1 is compared to the following class descriptions, you’ll see that not all bit combinations are included in the counts of Table 2.1.
Class A
Class A IP addresses are identified by a high-order bit of zero. The next highest order seven bits identify the network. The remaining 24 bits identify the host.
Class A IP Address Format.
FIGURE 2.5.
31 30 0 Network ID 24 23 Host ID 0
Class B
Class B IP addresses are identified by a high-order bit of one and the next highest order bit set to zero. The next highest order fourteen bits identify the network. The remaining 16 bits identify the host.
FIGURE 2.6.
31 1 0 30 29 Network ID 24 23 Host ID 0
Class B IP address format.
Slide 33: 34
Part I ■ Introduction to Networking
Class C
Class C IP addresses are identified by a high-order bit sequence of one one zero. The next highest order twenty-one bits identify the network. The remaining eight bits identify the host.
FIGURE 2.7.
31 30 29 28 1 1 0 Network ID 87 Host ID 0
Class C IP address format.
Dotted Decimal Notation If the format of an IP address sounds confusing, don’t worry. An IP address is usually represented by the dotted decimal notation. An IP address’ dot notation is comprised of 4 decimal values in the range of 0 to 255 separated by a period or dot (.). Each value represents 8 bits of the IP address. The 4 values together are the 32 bits of the IP address.
I’ll use the IP address of my computer as an example. Its address in dotted decimal format is 166.78.4.139. Figure 2.8 shows the binary interpretation of this IP address. Bit 31 is a 1, so that indicates this computer is either a class B or C network. Bit 30 is a 0 indicating that the computer does belong to a class B network. The earlier discussion of network classes said that class B addresses allowed 14 bits for the network id part and 16 bits for the host id part. The dividing line separating the network part from the host part is between bits 15 and 16. You can read this to mean the computer resides on network 166.78 and is host 4.139.
FIGURE 2.8.
IP address decoding.
Dotted Decimal Notation 166.78.4.139 Binary Representation 10100110 01001110 00000100 10001011
NOTE
If remembering the dotted decimal notation of a computer still sounds too confusing, read on: The implementors of TCP/IP realized that humans would need an easy method of accessing this information, so they devised a method whereby a simple-to-remember textual name symbolizes an IP address. The name-to-IP-address translation is supported by several methods with the
Slide 34: Chapter 2 ■ TCP/IP Overview
35
simplest being a plain ASCII file where each line of the file has the IP address in dotted decimal notation to the left and the textual name to the right. This file is customarily named hosts and is referred to as the host file. The host file implementation is fine for a small network with relatively few computers, but the management of such a file becomes unwieldy or impossible as the network grows to thousands of hosts, as in the Internet or any large corporate network. In this environment, a name server is utilized. A name server is a computer that provides a name to IP address resolution. When a request to translate a certain name to its IP address arrives at the name server, it does a database lookup to see if it has this information. If not, the request is passed on to an authoritative server. Authoritative servers are maintained with official data provided by the group responsible for the assignment of IP addresses.
Subnetting Subnetting is a method of locally modifying the use of the network and hosts bits. By moving the dividing line that separates the host id part from the network id part, more networks can be created at the same time the maximum number of hosts on each network is reduced. A subnet mask is used to define the new dividing line. It’s represented in dotted decimal notation in much the same way as an IP address is. The bits that are set to one represent the network portion; the remaining bits that are set to zero represent the host portion.
Earlier, I determined that the IP address of my computer resided on network 166.78 and had a host id of 4.139. Officially speaking this is correct. But as it turns out, the network administrators at my site have decided to use a network mask of 255.255.255.0 to logically partition the address space into more networks. Looking at the logical view,
my network id is 166.78.4 and my host id is 139. Another way of saying this is my
computer is host 139 on subnet 166.78.4.0. Notice that the low order byte is 0. Performing a logical AND operation between my complete IP address and the subnet mask results in the subnet 166.78.4.0. The component that remains—139—is the host part.
TIP
The subnet mask doesn’t have to be partitioned on even 8-bit boundaries. As an example, suppose that my subnet mask is 255.255.255.128. Performing the logical AND operation between this subnet mask and 166.78.4.139 results in 166.78.4.1 with a remaining portion of 11. Hence my computer would be host 11 on subnet 166.78.4.1.
Slide 35: 36
Part I ■ Introduction to Networking
The NIC and Internet IP Addresses You should see now that IP address space is a limited resource. You have also learned that any computer attached to the global Internet must have a unique IP address. The Network Information Center is the group responsible for the assignment of IP addresses and domain names. To get an official IP address and have your host name officially recognized, you must register with the NIC. Depending on your needs, the NIC will most likely allocate to you either a class B or C network identifier. Class A network space is very rare—remember that there are only 126 possible class A networks—and is almost exhausted.
When you register, you’ll also need to choose a domain name. Domain names are organized into a hierarchical structure with the root-level domain at the top. The top-level domains in the United States are COM for commercial organizations EDU for educational organizations GOV for governmental organizations MIL for military organizations NET for network support organizations
ORG for organizations that don’t fit into any other category
Other top-level domains are generally reserved for country codes. For example, the United Kingdom belongs to the top-level UK domain, and Australia belongs to the top-level AU domain. My personal computer, with IP address 166.78.4.139, has a fully qualified name of GOOBER.PING.COM. It’s a member of the PING.COM domain which is in turn a member of the top-level COM domain. Figure 2.9 shows a hierarchical representation of the domains mentioned thus far.
FIGURE 2.9.
Domain hierarchy.
Root
EDU
GOV
MIL
NET
ORG
COM
UK
AU
PING
GOOBER
Slide 36: Chapter 2 ■ TCP/IP Overview
37
Routing
Routing is the method by which packets of data are sent from one computer to another in the most efficient way possible. The routing process is composed of several components as follows: Determining what paths are available between the source and destination computers Selecting the “best” path between the source and destination computers where “best” may mean different things depending on the goals
Using those paths to reach other computers
Adjusting the datagram formats to fit into the underlying physical network technology In a TCP/IP network, routing is performed by the IP layer. The network id of the destination computer’s IP address as well as the subnet mask are used by the IP layer to make routing decisions.
Default Gateway In an interconnected computer network, or internet, some method is required to deliver data to computers that reside on another connected network. By specifying a default gateway, the IP layer of the sending computer “knows” to what destination it should forward data that has a destination which isn’t on the local network. See Figure 2.10 for a simple network arrangement. When 166.78.4.139 sends data to 166.78.4.10, the IP layer takes the subnet mask, in this case 255.255.255.0, and performs a logical AND operation on both the source and destination IP addresses. The result in this case is 166.78.4.0 for both addresses, which tells the IP layer that both computers are on the same subnet. The data is sent directly to 166.78.4.10. When 166.78.4.139 sends data to 166.78.1.5, the IP layer again uses the subnet mask, and the results are 166.78.4.0 for the source and 166.78.1.0 for the destination. These numbers don’t match, which signals the IP layer that the computers reside on different subnets. The sending computer can’t send directly to 166.78.1.5. The data must be sent to the default gateway, which is a computer that has two IP addresses and resides on two distinct subnets. The data is first sent to 166.78.4.2 and then forwarded on to 166.78.1.5.
Slide 37: 38
Part I ■ Introduction to Networking
FIGURE 2.10.
Network routing.
166.78.4.139 Subnet 166.78.4.0
166.78.4.10
166.78.4.2 166.78.1.1 Gateway
Subnet 166.78.1.0
166.78.1.5
Multiple Default Gateways It’s also possible to have multiple default gateways. With this configuration, a subnet doesn’t rely on one gateway to the connected networks. Instead the data can use several paths to leave the source subnet. The IP layer uses the subnet mask, the IP addresses of the gateways, and the IP address of the destination computer to decide the most efficient route from sender to receiver.
Internet Layer
The internet layer is shown in Figure 2.2. It defines the datagram and handles the routing of those datagrams. IP is the most important protocol of the TCP/IP protocol suite, because it’s used by all other TCP/IP protocols and all data must flow through it. IP is also considered the building block of the Internet. Although the application programmer doesn’t usually see this layer, a brief overview is beneficial.
Slide 38: Chapter 2 ■ TCP/IP Overview
39
IP IP is a connectionless protocol, which means that no end-to-end association is established before data is transmitted. This is in contrast to a connection-oriented protocol that exchanges control information between hosts to establish a connection before data is transmitted. IP doesn’t guarantee reliable data delivery either. Packets of data could arrive at their destination out of order, duplicated, or not at all. IP relies on other layers, such as the TCP transport protocol, to provide the reliability feature.
The basic building block of IP is the datagram. Each datagram, or packet of data, has a source and destination address. Routing of data is done at the datagram level. As a datagram is routed from one network to another, it may be necessary to break the packet into smaller pieces. This process is called fragmentation and it’s also the responsibility of the IP layer. Fragmentation is required on some internets because the many hardware components that make up the network have different maximum packet sizes. IP must also reassemble the packets on the receiving side so that the destination host receives the packet as it was sent.
Address Resolution Protocol Unfortunately, network hardware (that is, the Ethernet card you plug into your computer) doesn’t understand IP addresses. The Address Resolution Protocol (ARP) is used to map the logical IP addresses and host names—that humans like to use—into the
physical addresses that the underlying network hardware mandates. This protocol
operates by broadcasting a message onto the local network, saying in effect, “Is the computer with IP address xxx.xxx.xxx.xxx out there?” If the computer with the designated IP address is listening, it returns a message with its physical hardware address to the source. Any other computer that receives the broadcast request message ignores it. This protocol only works on the local network because the format of the physical network address is dependent on the hardware used in the network. For example, if an Ethernet was in use, the response to the ARP request would be a 48-bit number that uniquely identifies every Ethernet device in existence.
Internet Control Message Protocol The Internet Control Message Protocol (ICMP) is another low-level protocol rarely used by the application programmer. It uses IP datagrams to send messages that perform flow control, error reporting, routing manipulation, and other informational functions for TCP/IP.
Slide 39: 40
Part I ■ Introduction to Networking
The application programmer most certainly will make use of the ping utility, one of the most common programs that uses ICMP. Ping uses ICMP’s echo function to test the response of a networked host. By getting a response from ping, you’re assured that network routing is in place between the two computers and that the remote computer is indeed running.
NOTE
You’ll develop a version of ping in a later chapter. That version of ping will use an application-level protocol from the transport layer instead of the internet layer’s ICMP.
Transport Layer
IP is responsible for getting datagrams from computer to computer. The transport layer is responsible for delivering that data to the appropriate program or process on the destination computer. The two most important protocols of the transport layer are User Datagram Protocol (UDP) and Transmission Control Protocol (TCP). UDP provides connectionless datagram delivery; TCP provides a reliable stream-oriented delivery service with end-toend error detection and correction. To facilitate the delivery of data to the appropriate program on the host computer, the notion of a port is used. A port is a 16-bit number that denotes an end-point of communication within a program. An IP address and port combination taken together uniquely identify a network connection into a process. The socket paradigm developed by the University of California at Berkeley makes more intuitive the use of IP addresses and ports.
NOTE
The application programmer is responsible for ensuring that two or more processes don’t utilize the same port.
Application programmers use UDP and TCP in the majority of their networked programs.
Slide 40: Chapter 2 ■ TCP/IP Overview
41
User Datagram Protocol The User Datagram Protocol (UDP) allows data to be transferred over the network with a minimum of overhead. UDP overhead is low because it provides only unreliable data delivery. There’s no method in the protocol to verify that the data reached the destination exactly as it was sent. The data may be lost, duplicated, or arrive out of order.
These limitations don’t make UDP useless, though. The low overhead in UDP transmission—because there’s no need to establish a connection—and the lack of reliability makes UDP very efficient. UDP can be used when the application programmer puts error-case handling into the application. For example, suppose that you had a simple client-server relationship where the client sends a small piece of data to the server and expects within two seconds a response in the form of a small piece of data. If the client doesn’t receive a response within two seconds, it can assume the data didn’t make it to the server successfully and so it may retransmit the request. If the client does receive a response from the server, that can be used as an acknowledgment that the data did reach its destination. Figure 2.11 shows the format of a UDP message. The message contains a 16-bit source and destination port.
FIGURE 2.11.
UDP message format.
Source Port Length
Destination Port Checksum Data...
Transmission Control Protocol
The Transmission Control Protocol (TCP) verifies that data is delivered in order and without corruption. Associated with this feature is extra overhead in the generation and maintenance of a connection. TCP provides for the transmission of a reliable, connection-oriented stream of bytes. TCP’s reliability comes from its inclusion of a checksum into each packet of data transmitted. On reception, a checksum is generated and compared to the checksum included in the header of the data packet. If the checksums don’t match, the receiver communicates that fact to the sender, and the data is automatically resent. Application programmers don’t have to be concerned with this function because the lower layers mask it. TCP is considered connection-oriented because the two end-points of communications exchange a handshaking dialogue before data transmission can begin. This handshake guarantees to the sender that the receiver is alive and ready to accept data.
Slide 41: 42 Part I ■ Introduction to Networking
Source Port Destination Port
FIGURE 2.12.
TCP message format.
Sequence Number Acknowledgement Number Offset Reserved Flags Checksum Options Data... Window Urgent Pointer Padding
Figure 2.12 shows the format of a TCP message. The message contains a 16-bit source and destination port as does the UDP message. But this message also includes sequencing fields as well as a data checksum field. These additional entries in the message are there to support TCP’s reliable data transport.
Well-Known Ports UDP and TCP use the IP address and the port number to uniquely identify a particular process on a TCP/IP networked computer. But your application program shouldn’t use just any port. Some ports are called reserved ports because they have been given a special meaning. Some RFCs describe application-level services that most TCP/IP networked computers run. These network-accessible services “listen” at a well-known port so that client programs need only know the IP address of the remote host. For example, consider the finger program. It takes as its parameter a host name or host IP address. The finger program connects to the host at the well-known port that has been reserved for the finger service. If you were to write a program that waited for connections on a well-known port, you might get client programs trying to attach to your service that were expecting another program on the back-end.
Summary
This chapter discussed the principal components of a TCP/IP network. TCP/IP protocols are independent of the underlying network hardware on which they reside. Each computer on a TCP/IP must have a unique IP address that universally discloses the computers identification. TCP/IP is particularly well suited for use in an internetworking environment where several disparate networks need to be connected. Gateways allow the IP data to travel between two or more interconnected networks. The application programmer will make the most use out of TCP/IP’s transport level interfaces of UDP and TCP. The socket paradigm is used to assist the application programmer with network coding. The next chapter discusses WinSock’s use of sockets and the extensions necessary to support the Microsoft Windows architecture.
Slide 42: Chapter 3■ WinSock Overview 43
3
WinSock Overview verview
Slide 43: 44
Part I ■ Introduction to Networking
The Windows Sockets Application Programming Interface (WinSock API) is a library of functions that implements the socket interface as popularized by the Berkeley Software Distribution of UNIX. WinSock augments the Berkeley socket implementation by adding Windows-specific extensions to support the message-driven nature of the Windows operating system. WinSock version 1.1 is bound to the TCP/IP protocol suite. Although future versions of WinSock are expected to support Novell’s IPX/SPX, Apple’s Appletalk, and other popular network protocols, this book concentrates on the socket interface to the TCP/ IP protocol stack. The WinSock specification allows TCP/IP stack vendors to provide a consistent interface to their stacks so that application developers can write an application to the WinSock specification and have that application run on any vendor’s WinSock-compatible TCP/ IP protocol stack. This is contrast to the days before the WinSock standard when software developers had to link their applications with libraries specific to each TCP/IP vendor’s implementation. This limited the number of stacks that most applications ran on because of the difficulty in maintaining an application that used several different
implementations of Berkeley sockets. WinSock has removed that barrier. App-
lication programmers write to the WinSock API and link their applications with the WINSOCK.LIB import library (or WSOCK32.LIB in the case of Win32). The application can then be installed on a computer that has a WinSock TCP/IP stack, from any number of vendors, and dynamically link to the WINSOCK.DLL (or WSOCK 32.DLL) provided by the vendor. Figure 3.1 is a block diagram of WSOCK32.DLL interaction in a 32-bit program on Windows NT. Although the actual WINSOCK.DLL is specific to each TCP/IP stack vendor, the interface into that dynamic link library remains consistent, hence any program linked with the WinSock import library should work.
CAUTION
I say should work because not all TCP/IP vendors’ stacks operate in exactly the same way. Some simply have bugs and others interpret the WinSock or TCP/IP protocol standards differently. In some cases, the WinSock specification itself is ambiguous. As discussed in Chapter 2, a socket is simply an end-point of communication. A TCP/ IP socket is comprised of an IP address and a port. Some ports are reserved for wellknown services and others are for use by your applications. Sockets can be set up to provide
either a reliable, connection-oriented stream service or an unreliable, connectionless datagram service.
Slide 44: Chapter 3 ■ WinSock Overview
45
FIGURE 3.1.
WinSock layering.
Win32 WinSock Application
WINSOCK32.DLL
Application Layer Operating System Layer
I/O System
Includes Disk and Network
Executive Services
Device Drivers Hardware Abstraction Layer
Micro-Kernel
The reliable stream socket is based on the TCP. It requires that a connection be established before two processes can send or receive data between themselves. The data sent between the connected processes is simply a stream of bytes. There are no record delimiters in the data stream. For example, if the sending process sends 100 bytes, the receiving process may receive that data as a single chunk of 100 bytes or two chunks of 50 bytes each. If your application depends on records being sent, you must provide application-level headers in the data stream; TCP won’t preserve the packet size for you on the receiving side. The connection-oriented stream service is well suited to the client-server architecture. In a typical client-server interaction, the server creates a socket, gives the socket a name, and waits for clients to connect to the socket. The client creates a socket and connects to the named socket on the server. When the server detects the connection to the named socket, it creates a new socket and uses that new socket for communication with the client. The server’s named socket continues waiting for connections from other clients. See Figure 3.2 for an illustration of this simple client-server interaction. The unreliable, connectionless datagram socket is based on the User Datagram Protocol. It doesn’t require that a connection be established before two processes can send data to and receive data from each other. The data sent between any two processes is contained in a single packet. The sender sends the packet and the receiver receives the
Slide 45: 46
Part I ■ Introduction to Networking
entire packet. Consequently, this type of socket can be easily used to send records; no application-level headers are required. The limitations in this socket service are that data may not be received at the destination, that data may be duplicated, and that data may arrive out of order.
FIGURE 3.2.
Client-server stream socket interaction.
Server
socket() Create the Socket bind() Give the Socket a Name listen() Listen for Connections from Clients
Client
Wait for Connections from Clients
socket() Create the Socket connect() Connect to the Server
accept() Accepting the connection causes a new socket to be created while the original socket continues to wait for new connections send() / recv() Send and Receive Data closesocket() Close the Connection send() / recv() Send and Receive Data
closesocket() Close the Connection
Berkeley Sockets Versus WinSock
Those familiar with Berkeley sockets may want to examine the following list, which describes some differences between the Berkeley socket implementation and WinSock.
WinSock Version 1.1 supports the TCP/IP domain for interprocess communication on the same computer as well as network communication. In addition to the TCP/IP domain, sockets in most UNIX implementations support the UNIX domain for interprocess communication on the same computer and the Xerox XNS domain. The return values of certain Berkeley functions are different. For example, the socket()
function returns -1 on failure in the UNIX environment; the
WinSock implementation returns INVALID_SOCKET.
Slide 46: Chapter 3■ WinSock Overview 47
Certain Berkeley functions have different names in WinSock. For example, in UNIX the close() system call is used to close the socket connection. In
WinSock, the function is called closesocket(). See the next item for the reason.
WinSock socket handles may not be UNIX-style file descriptors. In a UNIX environment, a socket handle can be operated on in much the same way as any other file handle (that is, an actual disk file). In most WinSock implementations, with the possible exception of the Win32 environment of Windows NT, socket handles can’t be operated on in the same fashion as generic file descriptors. Several new functions were added to WinSock to support the message-driven architecture of Windows. These are discussed in the following section.
WinSock Extensions to Berkeley Sockets
WinSock has several extensions to Berkeley sockets. Most of these extensions are due to the message-driven architecture of Microsoft Windows. Some extensions are also required to support the nonpreemptive nature of the 16-bit Windows operating environment. Windows NT and the 32-bit follow-up operating system to Windows 3.11 remove the nonpreemptive limitations, but the additional WinSock functions are still useful for reasons discussed later.
Windows Message-Driven Architecture Although this book is not geared toward the beginning Windows programmer, this section gives a brief overview of the Windows architecture. If you’re familiar with Windows message-driven architecture, feel free to jump ahead to the section titled WinSock Asynchronous Functions.
At the heart of every Windows program is a message loop and one or more window procedures. The message loop retrieves messages from the program’s message queue and dispatches them to the appropriate window procedure for execution. Windows is considered to be a message-driven or event-driven system because no part of the program runs unless a message or event triggers it. Messages are generated by user actions such as typing on the keyboard or moving the mouse and by internal operating system activity. The two major components of a Windows program are its message loop and its windows. As described previously, the message loop retrieves messages and calls the window procedure appropriate for the window. The message that is retrieved from the message queue contains a handle to the window to which the message should be routed.
Slide 47: 48
Part I ■ Introduction to Networking
The window procedure to call is dependent on the window class of the destination window. Some window classes are declared by the application programmer, and other predefined window classes are supplied by the Windows operating system. These predefined window classes are called controls. A few of the predefined controls are listed here:
EDIT LISTBOX STATIC Used to view and edit text Used to display a list from which the user can select one or more items Used to display static text that is often used as labels for other controls
To better explain the message-driven nature of Windows, you can examine the following contrived sample application with a sizable main window, an edit control, and a static text label to the left of the edit control. The program’s display is shown in Figure
3.3.
FIGURE 3.3.
Sample Application with Three Windows.
The sizable main window has a user-defined window class. The most important properties, or styles, of the main window are
WS_CAPTION WS_MINIMIZEBOX
WS_MAXIMIZEBOX
Gives the window its title bar Adds the minimize button to the top right of the title bar allowing the window to be minimized into an icon Adds the maximize button to the top right of the title bar allowing the window to be maximized to fill the entire
screen
WS_SYSMENU WS_THICKFRAME
Adds the system menu to the top left of the title bar Gives the window its sizing capabilities
The static text label is another window of the application. To the programmer new to Windows, it may seem strange that a static text label is a window. A lot of what the user thinks of as simple screen elements, such as buttons, list boxes, and edit boxes, are really
Slide 48: Chapter 3■ WinSock Overview 49
just specialized windows. This static-text label is known as a child window because it is anchored to the main window; the main window is the parent to the static-text label. This window has the predefined window class of STATIC and its functionality is limited. The static control can respond to messages that tell it to change its text or return its textual contents to the caller. Notice I said “respond to messages.” The static control acts just like any other window in that it lies dormant until it receives a message. When a message comes in destined for the static control, the window procedure for the static control is called with parameters that specify the action that should be taken. One such message might be WM_SETTEXT, which tells the control to change the text it’s displaying on the screen. The last window of the application is the edit box. It has the predefined window class of EDIT. The edit control designates a rectangular child window in which the user can type text from the keyboard. The user selects the control and gives it the keyboard focus by clicking in it or moving to it by pressing the Tab key. The user can type text when the control displays a flashing caret. The mouse can be used to move the cursor, select characters to be replaced, or position the cursor for inserting characters. The Backspace key can be used to delete characters. You can tell from this description of the EDIT control, taken from the Windows Software Development Kit documentation, that this type of window knows about a lot more messages than the static control. Now that you’ve read about the three windows that make up this sample application, look at the program flow once it is up and running. The heart of the program is its message loop. A
message loop commonly used looks like this:
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
This code fragment retrieves messages from the application’s message queue and dispatches the messages to the destination window by calling its window procedure. If the user positions the mouse cursor over the edit box and presses the left mouse button, the edit control receives the input focus. The edit control knows it has focus when the WM_SETFOCUS message is dispatched to it. When this happens, the edit control displays the blinking caret to show where the next key pressed will be displayed. Figure 3.4 shows the message flow for the following WM_CHAR event. When the user types a key on the keyboard, a WM_CHAR message is generated. One of the qualifiers to this message is the actual key pressed. The edit control receives this notification and paints the newly typed character in the box. If the user then positions the mouse cursor outside the edit box but still inside the main application window, the edit box will receive a WM_KILLFOCUS message telling it to remove the blinking caret, and the main window then receives a
WM_SETFOCUS
message.
Slide 49: 50
Part I ■ Introduction to Networking
FIGURE 3.4.
Message flow.
Static Text Label: Class: CustomClass Class: STATIC Handle: 1001 Handle: 1000 Class: EDIT Handle: 1002
Hardware event is translated into a Windows message
1
Hardware Event User presses the A key after the edit control has received focus
Message Queue 2
Message Window Handle: 1002 Message: WM_CHAR Message Qualifier: 'A'
Message Loop Pull the WM_CHAR message from the message queue and check its destination window handle. Find out the window class for that destination window handle and call the window procedure for that window class.
3
Dispatch the Message
CustomClass Window Procedure Remains idle until a message comes in for window handle 1000.
Static Control Window Procedure Provided by the Windows Operating System Remains idle until a message comes in for window handle 1001.
Edit Control Window Procedure Provided by the Windows Operating System Decode the message. If the message is WM_CHAR look at the message qualifier to determine the actual character being received. Print that character in the edit control window.
The 16-bit Windows environment is cooperatively multitasked. This means that the applications running must cooperate with one another so that multiple tasks or programs can run simultaneously. They do this by continuously running the message loop described previously. If an application calls the GetMessage() function and there are no messages waiting for that application, Windows switches tasks and allows another program to run. That newly running program runs through its message loop until it has no more messages to process, and the procedure continues through all the programs running on the computer. In this environment, it’s very easy for a single program to prevent all others from running. For example, if the edit box in the previously described example took 10 seconds to process the WM_CHAR message, no other tasks on the computer would run for at least 10 seconds. Not only would other tasks not run but if the user rapidly typed several keys in succession, it would take 10 seconds for each keystroke to be reflected on the screen. It is imperative in the nonpreemptive multitasking Windows environment to process a message swiftly and return to the message loop. If a program doesn’t follow this rule, the performance of the entire computer system will suffer.
Slide 50: Chapter 3■ WinSock Overview 51
Windows NT is a preemptive multitasking operating system. This means that the operating system itself determines when a new task should run. It doesn’t depend on an application checking for messages in order for a task switch to occur. If, as described previously, an application took 10 seconds to process a WM_CHAR message, other applications could still run before that 10 seconds elapsed. That particular application would experience poor performance, but it wouldn’t degrade the performance of any other program running on the computer at that time. It’s generally considered bad practice for a program to have poor response time to a user’s commands. Users won’t be happy if they have to wait 10 seconds for a character to appear on the screen. If an operation that was executed in response to a message takes a long time to run, the user interface of the program should remain responsive to the user. That is why it’s advisable for an application to check its message queue often, even in the preemptive environment of Windows NT.
WinSock Asynchronous Functions WinSock was originally designed for the nonpreemptive Windows architecture. For this reason, several extensions were added to traditional Berkeley sockets.
Blocking Versus Nonblocking
Many of the Berkeley socket functions take an indeterminate amount of time to execute. When a function exhibits this behavior, it is said to block; calling the function blocks the further execution of the calling program. In the Berkeley UNIX environment, for which sockets were originally developed, this didn’t pose a serious problem because the UNIX operating system would simply preempt the blocking program and begin running another program. Windows (unlike Windows NT) can’t preempt a task, so all other programs are put on hold until the blocking call returns. The designers of WinSock knew this posed a serious problem, so they added special code in the blocking functions to force the message loop of other applications to be checked. But this was still not the most efficient technique. Berkeley sockets already have the notion of blocking versus nonblocking for some operations. For example, the send() function used to send data to a remote host may not return immediately, so the programmer is given the option of creating the socket with blocking or nonblocking sends. If the socket is created in blocking mode, it won’t return until the data has been delivered. If it’s created in nonblocking mode, the call to the send() function returns immediately and the program must call another function called select() to determine the status of the send. Windows and Windows NT can use the select() method of nonblocking calls, but the best thing to do in a Windows program is to use the special Windows asynchronous functions.
Slide 51: 52
Part I ■ Introduction to Networking
The special Windows asynchronous functions begin with the prefix WSAAsync. These functions were added to WinSock to make Berkeley sockets better fit the message-driven paradigm of Windows. The most common events to use the asynchronous functions for are the sending and receiving of data. Sending data might not happen instantly, and receiving data most certainly will cause a program to wait unless it is receiving a constant stream of bytes. By creating a socket for nonblocking sends and receives and using the WSAAsyncSelect() function call, an application will receive event notification messages to inform it when it can send data or when data has arrived and needs to be read. In the mean time, when there is no data communications occurring, the rest of the program remains fully responsive to the user’s actions. The WSAAsyncSelect() function and its use with sending and receiving data on a nonblocking socket is discussed beginning in Chapter 7, “Socket Functions.” WinSock even extends Berkeley’s nonblocking support to functions that could still cause a Berkeley UNIX program to block. The concept of a name server was introduced in Chapter 2, “TCP/IP Overview.” The name server’s job is to take as input the plain text representation of a computer’s name and return that computer’s IP address. The name server is usually a networked computer distinct from the one running WinSock programs that you develop. The services of a name server require that a message be sent over the network from the computer using the WinSock program to the computer running the name server. This network communication could take an indeterminate amount of time. WinSock has compensated for that fact by extending the functions that utilize the services of a name server. These functions are among those grouped into a category of functions called the database functions or the getXbyY functions. GetXbyY is used to refer to these database functions because the function names take the form of get X by Y, or put another way: “Given Y, what is the corresponding X?” In the name server example, the function used is called
gethostbyname();
is its host information? In Berkeley UNIX, the getXbyY functions may block. WinSock adds asynchronous versions of the getXbyY functions called WSAAsyncGetXbyY. The gethostbyname() function is complimented by the nonblocking WinSock function called
WSAAsyncGetHostByName(),
given the computer’s name, what
immediately with an identifying handle. When the actual work performed by the function has completed, a message is sent to the application notifying completion of the function with the specified handle. The database functions, in both their blocking and asynchronous forms, are discussed in a Chapter 6, “Conversion and Database Functions.”
for example. A call to a
WSAAsyncGetXbyY
function returns
Slide 52: Chapter 3■ WinSock Overview 53
The WinSock asynchronous functions were added primarily for the benefit of the
nonpreemptive Windows environment. You may be questioning their worth in the truly preemptive multitasking Windows NT environment. The WSAAsync functions have an important use even in Windows NT. They allow your applications to remain responsive to the user. Users won’t enjoy working with your program if it forces them to wait for completion of a long event. Most users expect a way to cancel operations that take a long time. For example, suppose that you have a program that takes as input a computer’s plain-text name. The user enters the name and then presses a button labeled Look Up, which causes the gethostbyname() function to be called. Using gethostbyname() will cause the program to hang for an indeterminate amount of time until the request is carried out. Under Windows NT other programs would still run, but under Windows the performance of all programs would be degraded. This program could be modified
to use
WSAAsyncGetHostByName()
Look Up button, the WSAAsyncGetHostByName() function is called and returns an identifying handle. If users wish to cancel the search, they can press the Cancel button, which terminates the request with that identifying handle. Users would maintain full control instead of being at the mercy of the program.
instead of
gethostbyname().
As soon as users press the
Summary
This chapter discussed WinSock as compared to the original Berkeley sockets interface popularized by BSD UNIX. WinSock includes most of the Berkeley functionality with many extensions to support the event-driven nature of Windows. The use of some of these extensions is mandatory for proper program execution; the use of others is optional but desirable because they provide a user interface responsive to the user’s requests. Later chapters will fully explore the benefits of using the WinSock extended functions. The next chapter discusses the Microsoft Visual C++ development environment.
Slide 53: Chapter 4 ■ Visual C++
4
55
Visualisual C++ C++
Slide 54: 56
Part I ■ Introduction to Networking
Microsoft’s Visual C++ product has greatly simplified the development of Windows programs. The application developer is no longer required to start every project with the same hundred lines of mandatory code just to display the most basic of windows. The Microsoft Foundation Class library (MFC) encapsulates most of that drudgery, allowing the programmer to start working on the real problem instead of the plumbing. The samples presented in this book make use of MFC and Visual C++.
Visual C++ Components
The Visual C++ package includes several tools that are tightly integrated with each other and to the Microsoft Foundation Class library. The combination of these tools makes it easy to create a framework from which the application begins.
Visual Workbench Visual Workbench is the program you will spend the most time in. It includes a colorcoded text editor which highlights keywords in your C and C++ source code. The Visual Workbench also encompasses a build manager which manages makefiles that can be used from within Visual Workbench or from the command line. This build manager is used to add new source files to a project, change compiler and linker options, and automatically generate source code dependencies. The Visual Workbench also includes a source level debugger. Debugging, and coding in general, is made much simpler by the help facility built into Visual Workbench. By placing the caret on a Windows, C, or C++ keyword and pressing the F1 key, help on that topic is displayed. Figure 4.1 shows Visual Workbench operating on the TEST project with two source code windows open for editing. AppWizard AppWizard is one component of the Visual Workbench. It automates the task of creating a minimalistic Windows program by prompting you with several options and then creating the skeleton source code necessary for that application. The skeleton program created by AppWizard supports window management, a basic menu structure, and basic menu commands. Some of the options offered determine whether the application will have:
Multiple Document Interface or Single Document Interface. A MDI application can have one or more child windows within the main application window. The Visual Workbench is an example of a MDI application because it allows you to have several source code windows open at any one time.
Slide 55: Chapter 4 ■ Visual C++
57
Initial Toolbar. The toolbar is a row of bitmap buttons that appears beneath the application’s menu.
Printing and Print Preview. Context Sensitive Help.
FIGURE 4.1.
Visual Workbench.
Figure 4.2 shows the option dialog of AppWizard. The output of AppWizard is several C+ + source and header files, the project makefile, and the resource file. Below is a list of files for an AppWizard-generated project called TEST. The TEST project uses the Single Document Interface.
TEST.MAK TEST.H. Project file compatible with Visual Workbench and command-line tool NMAKE. Main include file for application; includes other projectspecific include files and declares the CTestApp application class. Main application source file. Contains the application class CTestApp. Listing of all resources the program uses. It includes icons, bitmaps, and cursors stored in the RES subdirectory. File can be edited directly with App Studio.
TEST.CPP TEST.RC
RES\TEST.ICO Icon file used as application’s icon. Icon is included by the main resource file TEST.RC.
Slide 56: 58
Part I ■ Introduction to Networking
RES\TEST.RC2
TEST.DEF
TEST.CLW MAINFRM.H and MAINFRM.CPP TESTDOC.H and TESTDOC.CPP TESTVIEW.H and TESTVIEW.CPP STDAFX.H and STDAFX.CPP RESOURCE.H
FIGURE 4.2.
Contains resources not edited by App Studio. Initially contains a VERSIONINFO resource that you can customize for your application. Contains information about the application that must be provided so that it runs with Microsoft Windows. Defines parameters such as name and description of application and size of initial local heap. Contains information used by ClassWizard to edit existing classes or add new classes. Contain frame class CMainFrame, which is derived from CFrameWnd and controls all SDI frame and features. Contain the CTestDoc document class. Contain the CTestView view class. Used to build a precompiled header file to make and compiles substantially faster. Defines resource IDs. App Studio reads and updates this file.
AppWizard options.
App Studio App Studio is used to create and edit Windows resources. With it, you can manipulate the resources for menus, dialog boxes, bitmaps, icons, cursors, strings in a string table, and keyboard accelerators. App Studio automatically generates unique resource IDs for the resources you create. These IDs are stored in the RESOURCE.H file by default. Figure 4.3 shows App Studio editing the resource file from the TEST project listed previously.
Slide 57: Chapter 4 ■ Visual C++
59
FIGURE 4.3.
App Studio.
ClassWizard ClassWizard is perhaps the most empowering component of the Visual C++ development environment. With it, you can derive classes from several standard MFC base class objects and to add member functions and variables to those classes. ClassWizard is used to map Windows messages, as well as user-defined messages, to specific member functions. With just a few mouse clicks, you can associate a specific message to a particular function name. ClassWizard creates the function prototype for that function in the header file for the class and also creates a stub function for you to add code to in the C++ file.
A common use of ClassWizard is to associate a class derived from CDialog with a dialog resource created with App Studio. First you design a dialog resource with App Studio and then launch ClassWizard. If ClassWizard is launched directly from App Studio
and the dialog resource being edited doesn’t yet have a class associated with it,
ClassWizard will automatically prompt you for a class name for the new dialog box class. Once the basic class is created, you can link member functions to the buttons and controls of the dialog box. You can also associate variables to the controls of the dialog box. For example, if the dialog box contains an edit control used to enter a numeric value, you can link an integer variable to that control. The integer variable becomes a member variable to the dialog’s class object. When your application needs the value entered into that edit control, it simply references the integer member variable.
Slide 58: 60
Part I ■ Introduction to Networking
FIGURE 4.4.
ClassWizard.
Class Browser The Class Browser is integrated into Visual Workbench. You can use it to view class hierarchical relationships. You can select a class and view all classes derived from it or view the class from which it was derived. Figure 4.5 shows the base class graph of the
CTestApp
derived from CWinApp. Clicking CTestApp reveals specific information for that object in the other part of the class browser viewer. The upper-right portion shows the member functions particular to CTestApp. Double-clicking one of these names puts you into the Visual Workbench editor at the appropriate spot in the source file for CTestApp. The lower-right portion shows the source files where the CTestApp object is implemented.
FIGURE 4.5.
object from the TEST application. The browser shows us that
CTestApp
is
Class Browser.
Slide 59: Chapter 4 ■ Visual C++
61
Message Mapping in an MFC Program
You will remember from the discussion in Chapter 3 that Windows is an event-driven operating system that relies on messages being delivered to the appropriate destination window in order for program execution to take place. Visual C++ makes the coding of message-based code almost trivial. With ClassWizard, you can define an object derived from a list of standard MFC objects. These base class objects are windows that expect to receive messages. ClassWizard allows for an easy mapping between messages and functions that perform the desired operations in response to those messages. After creating your derived class from the applicable base class, ClassWizard will create the member functions that you need to run in response to a message stimulus. A member function is created in the class declaration and a stub code for the member function is inserted into the implementation file for the class. The base classes from which your classes can be derived already have many member functions ready to be overridden by your special purpose code. One of these member functions reacts in response to the WM_CHAR message, which tells the window that the user has pressed a key on the keyboard. Figure 4.4 shows ClassWizard with the WM_CHAR message mapped to OnChar. The code created by ClassWizard in the class declaration for the TEST application follows:
class CTestView : public CView {
//{{AFX_MSG(CTestView) afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags); //}}AFX_MSG DECLARE_MESSAGE_MAP() }
Notice that the code ClassWizard adds is placed between the commented lines
MSG(CTestView)
//{{AFX
and
//}}AFX
MSG.
The appropriate code in the implementation file
follows:
BEGIN_MESSAGE_MAP(CTestView, CView) //{{AFX_MSG_MAP(CTestView) ON_WM_CHAR() //}}AFX_MSG_MAP END_MESSAGE_MAP()
void CTestView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default
Slide 60: 62
Part I ■ Introduction to Networking
CView::OnChar(nChar, nRepCnt, nFlags); }
When a WM_CHAR message comes in that is destined for the
CTestView
window, the
message map is examined to see how the message should be routed. Because
ON_WM_CHAR is in the message map, MFC knows that the application has a message handler in place. The WM_CHAR message is one for which there is a default handler already in place in CTestView’s base class of CView. MFC knows that WM_CHAR maps to the OnChar member function. You will see in a later chapter how to add userdefined message handlers that are unique to your application. Because MFC doesn’t know of these message handlers, you have to specify in the message map the name of the member function that handles that message.
Support Files for Building WinSock Applications
To compile an application that uses the WinSock API, you need the WinSock header file WINSOCK.H, which contains all the function prototypes and structure definitions. Linking the application requires either WINSOCK.LIB or WSOCK32.LIB depending on whether you’re targeting the 16-bit or 32-bit Windows platform. WINSOCK.H and WSOCK32.LIB are included with Visual C++ 1.1 for Windows NT. WINSOCK.H and WINSOCK.LIB are available from your TCP/IP stack provider. To ensure source code compatibility between the 16-bit and 32-bit programs, the standard 16-bit Windows include file VER.H must be named WINVER.H, as it is named in the 32bit environment. If you’re using a 16-bit compiler, I suggest going into the compiler’s include directory and copying VER.H to WINVER.H. A detailed discussion of how to build WinSock applications is included in Chapter 8, “Sample Applications.”
Summary
The Visual C++ integrated development environment has greatly simplified development of Windows programs. The well-integrated tools make it easy to create applications that use the MFC library. The WinSock class library developed later in this book will make extensive use of MFC’s functionality. The development of these classes will show the power and flexibility of Visual C++.
Slide 61: Basics of WinSock Programming rogramming
5 Startup and Shutdown Functions 6 Conversion and Database Functions 7 Socket Functions 8 Sample Applications
II
Slide 62: Chapter 5■ Startup and Shutdown Functions 65
5
Startup and and Startup Shutdown Shutdown Functions
Slide 63: 66
Part II ■ Basics of WinSock Programming
The WinSock functions your application needs are located in the dynamic link library named WINSOCK.DLL or WSOCK32.DLL depending on whether the 16-bit or 32-bit version of Windows is being targeted. Your application is linked with either WINSOCK.LIB or WSOCK32.LIB as appropriate. The include file where the WinSock functions and structures are defined is named WINSOCK.H for both the 16-bit and 32-bit environments. Before your application uses any WinSock functions, the application must call an initialization routine called WSAStartup(). Before your application terminates, it should call the WSACleanup() function.
WSAStartup
The WSAStartup() function initializes the underlying Windows Sockets Dynamic Link Library (WinSock DLL). You will remember that the WinSock API is independent of the specific TCP/IP stack vendor. This is what gives WinSock-compliant applications
the capability of running on any number of TCP/IP stacks. The
WSAStartup()
tion gives the TCP/IP stack vendor a chance to do any application-specific initialization that may be necessary. WSAStartup() is also used to confirm that the version of the WinSock DLL is compatible with the requirements of the application.
The prototype of the WSAStartup( ) function follows:
int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);
func-
The wVersionRequired parameter is the highest version of the WinSock API the calling application can use. The high-order byte specifies the minor version number and the low-order byte specifies the major version number. The lpWSAData parameter is a pointer to a WSADATA data structure that receives details of the WinSock implementation.
Version Checking One objective of the WSAStartup() function is to confirm that the WinSock implementation meets the requirements of the application. As of the summer of 1994, Versions
1.0 and 1.1 of WinSock have been released. Most WinSock compliant TCP/IP stacks in use today, including the stacks supplied by Microsoft, implement Version 1.1 of the WinSock specification.
The wVersionRequired parameter in the call to WSAStartup() has a high-order byte that specifies the minor version number and a low-order byte that specifies the major version number.
The following code shows how a of WinSock Version 1.0:
wVersionRequired
parameter is constructed that has a requirement
wVersionRequired = MAKEWORD(0, 1);
Slide 64: Chapter 5■ Startup and Shutdown Functions 67
The second parameter to the WSAStartup() function is a pointer to a WSADATA data structure. After the call to the initialization function, this structure contains information
detailing the WinSock implementation. The format:
typedef struct WSAData { WORD wVersion; WORD wHighVersion; char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char FAR * lpVendorInfo; } WSADATA;
WSADATA
structure has the following
To verify that the WinSock compliant stack meets the version requirements of your
application, the application should check the value in
wVersion
function returns. For example, if your application requires WinSock Version 2.0, compare the high-order byte of wVersion to 0 and the low-order byte to 2. If either of these comparisons fail, the application should display a message to the program’s user explaining the failure. The following code fragment shows a typical implementation:
WORD wVersionRequested = MAKEWORD(1, 1); // WinSock 1.1 requested WSADATA wsaData; // WinSock details int nErrorStatus; // error status nErrorStatus = WSAStartup(wVersionRequested, &wsaData); if (nErrorStatus != 0) { // display an error message explaining that WinSock // initialization has failed and return return; } // check the WinSock version information if ( (LOBYTE(wsaData.wVersion) != LOBYTE(wVersionRequested)) || (HIBYTE(wsaData.wVersion) != HIBYTE(wVersionRequested)) ) { // display an error message explaining that the WinSock // implementation doesn’t meet the version requirements // of the application WSACleanup(); return; } // terminate WinSock use
after the
WSAStartup()
This code segment, or something resembling it, is placed early in a program’s execution path. Typically it is found in WinMain() of a program built with the Windows SDK or CWinApp::InitInstance() for an MFC program.
Slide 65: 68
Part II ■ Basics of WinSock Programming
In the preceding code, notice that the
WSADATA
structure has
wVersion
and
wHighVersion
as member variables . w H i g h V e r s i o n has the same format as w V e r s i o n and wVersionRequested. A WinSock DLL may contain support for multiple WinSock ver-
sions. For example, it’s common for a WinSock DLL to support Versions 1.0 and 1.1. If the application requests Version 1.0 from a WinSock DLL that supports 1.0 and 1.1, wVersion will contain 1.0 and wHighVersion will contain 1.1. This says that the WinSock DLL will supply Version 1.0 functionality to the application but it has the capability of supporting Version 1.1 functionality. The following table lists application and WinSock interaction for differing application version requirements and WinSock capabilities:
App Ver WinSock Ver
wVersion wHigh wVersion
Requested 1.1 1.1 1.0 1.1 1.1 1.1 2.0 2.0
End Version Result 1.1 1.0 1.1 1.1 1.0 App uses 1.1 App uses 1.0 App uses 1.0 App uses 1.1 App fails
WSAStartup( )
1.1 1.1 1.0, 1.1 1.0 1.0 1.0, 1.1 1.1 1.0, 1.1 1.1 1.0
1.0
1.1
1.0
1.1 1.0 1.0 1.1 1.0
--
--
fails
1.0, 1.1 1.0, 1.1 1.1, 2.0 1.1 2.0 1.1
1.1 1.1 1.1
1.1 1.1 1.1
App uses 1.1 App uses 1.1 App fails
The general rules that WSAStartup() follows are as follows: If wVersionRequested matches one of the versions supported by the WinSock DLL, set wVersion to wVersionRequested and set wHighVersion to the highest WinSock version that is supported (this value may or may not be the same as
wVersion).
If wVersionRequested specifies a later version than the WinSock DLL supports, set wVersion and wHighVersion to the highest version that this WinSock
supports. It is then up to the application to decide whether this WinSock’s capabilities are adequate. If wVersionRequested specifies an earlier version than the WinSock DLL supports, WSAStartup() returns a failure code(). The internal WinSock error code is set to WSAVERNOTSUPPORTED. To access the internal WinSock error code, use the WSAGetLastError() function outlined later in this chapter.
Slide 66: Chapter 5■ Startup and Shutdown Functions 69
Retrieving Vendor Information and TCP/IP Stack Capabilities
The other capability of WSAStartup() is to provide to the calling application information about the underlying TCP/IP stack. We’ve examined the wVersion and wHighVersion variables of the WSADATA structure already. The remaining variables provide details of the WinSock implementation in use as follows:
■
is a pointer to a null-terminated ASCII string that describes the WinSock implementation. This string may be up to 256 characters long and usually contains a reference to the WinSock vendor, whether it be Microsoft, Wollongong, Net Manage, FTP, or another vendor. ■ szSystemStatus is a pointer to a null-terminated ASCII string that contains relevant status or configuration information. ■ iMaxSockets is the maximum number of sockets that a single process can
szDescription
potentially open. Your application can use this number as a rough estimation of whether the WinSock DLL is usable by the application. It is not a guarantee that your application can allocate this number of sockets. ■ ■
iMaxUdpDg
is the size, in bytes, of the largest UDP datagram that can be sent or received by the WinSock implementation. If this number is set to 0 (zero) there is no implied size limitation. is vendor-specific, your program loses its stack independence if it makes use of this data.
is a pointer to a vendor-specific data structure. Because this data
lpVendorInfo
WSACleanup
The WSACleanup() function is used to terminate an application’s use of WinSock. For every call to WSAStartup() there has to be a matching call to WSACleanup(). WSACleanup() is usually called after your application’s message loop has terminated. In an MFC application, the ExitInstance() member function of the CWinApp class provides a convenient location to call WSACleanup(). The prototype follows:
int PASCAL FAR WSACleanup(void);
Slide 67: 70
Part II ■ Basics of WinSock Programming
WSAGetLastError
The WSAGetLastError() function doesn’t deal exclusively with startup or shutdown procedures, but it needs to be addressed early. Its function prototype looks like
int PASCAL FAR WSAGetLastError(void);
returns the last WinSock error that occurred. In the MS-DOS or UNIX programming worlds, you’re probably used to examining the errno variable, which is an application-specific global variable available in all programs. Because WinSock isn’t really part of the operating system but is instead a later add-on, errno couldn’t be used. As soon as a WinSock API call fails, you should call WSAGetLastError() to retrieve specific details of the error.
WSAGetLastError()
As an example, if WSAStartup() is called with a wVersionRequested, which is earlier than any WinSock API supported by the WinSock DLL, WSAStartup() returns an error indicator. Calling WSAGetLastError() immediately after the failed call to WSAStartup() reveals the WSAVERNTSUPPORTED error. The other possible error values generated by WSAStartup() are WSASYSNOTREADY, if the network subsystem is failing, and WSAEINVAL, if an invalid argument is passed.
Possible error values for WSACleanup() include WSANOTINITIALIZED if WSAStartup() wasn’t called successfully, WSAENETDOWN if the network subsystem is failing, and WSAEINPROGRESS if a blocking
WinSock operation is currently in progress.
Summary
This chapter discussed just the beginning of writing a WinSock application. Chapter 8, “Sample Applications,” presents a program that uses the WSADATA structure in the call to WSAStartup() to present some useful information to the application user. The next few chapters will continue to present the mandatory WinSock functions useful to most applications.
Slide 68: Chapter 6■ Conversion and Database Functions 71
6
Conversion and Database Functions tions
Slide 69: 72
Part II ■ Basics of WinSock Programming
WinSock provides a set of procedures commonly referred to as the database functions. The duty of these database functions is to convert the host and service names that are
used by humans into a format useable by the computer. The computers on an
internetwork also require that certain data transmitted between them be in a common format. WinSock provides several conversion routines to fulfill this requirement.
Note
grams that run on their own but are presented instead to help clarify the textual description of the functions used in the sample. Study these examples so that you can use them in your own programs but don’t worry about actual program implementation issues now. Later chapters will draw from these code snippets to produce complete programs.
This chapter contains several small code samples. These aren’t complete pro-
Conversion Routines and Network Byte Ordering
There are several conditions under which a WinSock function should be called with a parameter stored in a particular format. An internetwork using WinSock is supposed to allow disparate computer systems to communicate. These different internetworked hosts are likely to have different hardware architectures based on the CPU used in the computer. They may store internal numerical data differently from one another. The way in which a CPU internally stores a number is called its byte ordering. To facilitate the different byte ordering used in different CPUs, WinSock provides a set of conversion functions. These conversion functions have the job of turning a host byte-ordered number into a number using the network byte-ordering scheme. Network byte ordering is the standard by which all TCP/IP connected computers must transmit certain data. In effect, the network byte-ordering sequence is the lowest common denominator of all internetworked computers. There are four primary byte-order conversion routines. They handle the conversions to and from unsigned short integers and unsigned long integers.
Unsigned Short Integer Conversion The htons() and ntohs() functions convert an unsigned short from host-tonetwork order and from network-to-host order, respectively. The prototypes look like
u_short PASCAL FAR htons(u_short hostshort); u_short PASCAL FAR ntohs(u_short netshort);
Slide 70: Chapter 6■ Conversion and Database Functions 73
takes as input an unsigned short in its native host format and returns that number in network order. ntohs() takes as input an unsigned short in network order and returns that number in the native host format.
htons()
On an Intel 80•86 CPU, integers are stored with the least significant bit in the lower part of an integer’s address space. Take the decimal number 43794 as an example. In hexadecimal notation this number is written as AB12. Suppose, also, that this value is stored at memory location n. On an Intel 80•86, the byte value at location n is 12 and the byte value at memory location n + 1 is AB. You can see that the least significant byte of the two-byte quantity is stored in the lower address space. This is the opposite of network byte ordering. The output of htons(43794) has AB in the lower address space and 12 stored in the higher address space of the two-byte quantity. On a different hardware platform, such as the Motorola 68000, the ntohs() function doesn’t do any byte manipulation because the 68000’s native byte ordering is the same as network byte ordering.
Unsigned Long Integer Conversion
htonl() and ntohl() functions work like htons() and ntohs() except that they operate on four-byte unsigned longs rather than unsigned shorts. The prototypes look like the following:
The
u_long PASCAL FAR htons(u_long hostlong); u_long PASCAL FAR ntohs(u_long netlong);
On an Intel 80•86 CPU, the decimal number 2870136116 is stored in memory, from lowest address space to highest, as hexadecimal 34 CD 12 AB. The output of htonl(2870136116) has AB in the lower address space, 12 stored in the next higher address space, and so on.
Caution
About byte ordering: Your program may run as expected under test conditions if the hosts involved in the test have the same native byte-ordering scheme. Problems may develop later if you ever try to connect your program to a host with a different byte-ordering scheme. As an example, say that you tested both a client application and a server application on an Intel 80•86 CPU. Everything may run fine even if you forget to use the conversion routines. Now, say that you move the server process over to a Motorola 68000-based Macintosh platform. The server “listens” on a well-known port. I’ll use port number 427 as an example. In hexadecimal, that port is 01AB. The Macintosh server application is listening for connections to 01AB. If the 80•86-based client then tries to
Slide 71: 74
Part II ■ Basics of WinSock Programming
connect to port 427 without first calling the htons() conversion routine, it is really trying to connect to port AB01 hexadecimal, which is 43777 in decimal. Hence, the client never connects to the server process running on the Macintosh, or at least not the intended server process. The functions that require their parameters to be in network byte order are so noted in the text accompanying each function’s description.
Converting IP Addresses WinSock provides another set of conversion functions that provide a translation between the ASCII representation of a dotted-decimal IP address and the internal 32-bit, byteordered number required by other WinSock functions.
Converting an IP Address String to Binary
converts a dotted-decimal IP address string into a number suitable for use as an Internet address. Its function prototype is as follows:
inet_addr()
unsigned long PASCAL FAR inet_addr(const char FAR * cp);
is a pointer to a string representing an IP address in dotted-decimal notation. The function returns a binary representation of the Internet address given. This value is already in network byte order, so there is no need to call htonl(). If the cp string doesn’t contain a valid IP address, inet_addr() returns INADDR_NONE. One possible cause for such an error is that the IP address has a component greater than 255. Remember that each of the four components of a dotted-decimal IP address represent one of four bytes of an unsigned long, therefore it’s illegal to have any component with a value greater than 255 because the value of a byte must be between zero and 255 inclusive.
cp inet_addr()
The following code fragment shows a typical call to inet_addr(). Of course, your real programs won’t have hard-coded IP addresses; you’ll most likely allow users to specify IP addresses when they configure your application.
u_long ulIPAddress = inet_addr(“166.78.16.148”);
The value of ulIPAddress after this code fragment has executed will be hexadecimal A64E1094. inet_addr() simply takes each component of the IP address and stores it in binary as one byte of the four-byte IP address. You don’t need to specify all four parts of the IP address, though. inet_addr() can take an IP address in any of the following dotteddecimal notations: a.b.c.d, a.b.c, a.b, or a. The a.b.c.d value is a typical IP address as shown in the preceding code sample. If a quantity is omitted, the last defined quantity
Slide 72: Chapter 6■ Conversion and Database Functions 75
is simply extended to fill the remaining bytes to make a total of four bytes. For example, if the string passed to inet_addr() is “166.78.16”, following the a.b.c format, the returned unsigned long is hexadecimal A64E0010.
Converting a Binary IP Address to a String
inet_ntoa()
performs the opposite job of
inet_addr().
Its function prototype is as fol-
lows:
char FAR * PASCAL FAR inet_ntoa(struct in_addr in);
is a structure that contains an Internet host address. You’ll see that some WinSock functions manipulate IP addresses as unsigned longs and others as in_addr structures. To remedy this difference, some byte copying is in order. This is shown in the following sample code. On success, the inet_ntoa() function returns a pointer to a string with a dotteddecimal representation of the IP address. On error, NULL is returned. A NULL value means that the IP address passed as the in parameter is invalid.
in
Following is a piece of somewhat contrived code:
// first get an unsigned long with a valid IP address u_long ulIPAddress = inet_addr(“166.78.16.148”); // copy the four bytes of the IP address into an in_addr structure IN_ADDR in; memcpy(&in, &ulIPAddress, 4); // convert the IP address back into a string char lpszIPAddress[16]; lstrcpy(lpszIPAddress, inet_ntoa(in));
I said the previous sample was contrived because of the way the binary IP address was retrieved. The binary IP address ulIPAddress is retrieved by using inet_addr() to convert an IP address string. In an actual program, the IP address on which you want to use inet_ntoa() will most likely come as the result of another WinSock call, not entered by the user or hard-coded; this part of the code is for demonstration purposes only. Once you have this unsigned long, it needs to be stored in an in_addr structure to be used by
inet_ntoa(),
pointer returned by inet_ntoa() is only temporary. It may be invalid after the next call to a WinSock function, so it is best to copy it into a variable in the application. A buffer of 16 bytes is allocated because this is the longest that a valid four-byte IP address will ever be (that is, “255.255.255.255” plus the terminating NULL character).
so
memcpy()
is used. Next, the conversion function is called. The string
What’s My Name?
Some applications need to know the name of the computer on which they are running. The gethostname() function provides this functionality. It was added to the WinSock
Slide 73: 76
Part II ■ Basics of WinSock Programming
1.1 specification. The function’s prototype looks like the following:
int PASCAL FAR gethostname(char FAR * name, int namelen);
name
is a far pointer to a character array that will accept the null-terminated host name,
namelen
and
(zero) on success and SOCKET_ERROR on failure. On a return value of SOCKET_ERROR, you can call WSAGetLastError() to determine the specifics of the problem. Possible error
values include
WSAEFAULT
is the size of that character array. The
gethostname()
function returns 0
if WSAStartup() wasn’t called successfully, WSAENETDOWN if the network subsystem is failing, or WSAEINPROGRESS if a blocking WinSock operation is currently in progress.
WSANOTINITIALIZED
if the buffer was too small to accept the host name,
The following code fragment shows a typical call to gethostname():
#define HOST_NAME_LEN (50) char lpszHostName[HOST_NAME_LEN]; char lpszMessage[100]; // will accept the host name // informational message
if (gethostname(lpszHostName, HOST_NAME_LEN) == 0) wsprintf(lpszMessage, “This computer’s name is %s”, lpszHostName); else wsprintf(lpszMessage, “gethostname() generated error %d”, WSAGetLastError()); MessageBox(NULL, lpszMessage, “Info”, MB_OK);
Note
The name populated by gethostbyname() may be a simple name or a fully determine which format is returned. The only thing guaranteed about the name variable is that it can be parsed by the gethostbyname() function, which will be discussed later.
qualified domain name. For example, my computer may be recognized as goober or goober.ping.com. It’s up to those who implement WinSock to
Host Name Resolution
Humans use a textual representation for the hosts to which their programs connect. The computer requires a host’s address to be a 32-bit integer stored in a standardized way as described in the previous section on network byte ordering. Your program cannot connect to another computer until that computer’s IP address is in the 32-bit format. To remedy this difference, your program can use either the gethostbyname() or inet_addr()
functions. gethostbyname() is used if you know either the simple name or the fully qualified domain name. inet_addr() is used if you know the IP address.
Slide 74: Chapter 6■ Conversion and Database Functions 77
Tip
Most programs that have a configuration to select the host with which the program communicates enable the user to enter either a host name or an IP address. Your program should call inet_addr() first with the user’s input. If this
function returns successfully, your conversion job is finished; otherwise, you should call gethostbyname(), assuming that the user entered a host name.
Finding a Host’s IP Address
The main duty of
gethostbyname()
This function, and its asynchronous counterpart named WSAAsyncGetHostByName(), may perform a simple table lookup on a host file local to the computer on which the program is running, or it may send the request across the network to a name server. Figures 6.1 and 6.2 show the different means of host name resolution. The application programmer doesn’t really know which method is used to resolve the host name and it usually isn’t important, with one caveat, which is described in the section on WSAAsyncGetHostByName(). The function’s prototype looks like the following:
struct hostent FAR * PASCAL FAR gethostbyname(const char FAR * name);
is to take a host name and return its IP address.
is a far pointer to a null-terminated character array that contains the name of the computer about which you want host information. The hostent structure returned has the following format:
name
struct char char short short char #define }; hostent { FAR * h_name; FAR * FAR * h_aliases; h_addrtype; h_length; FAR * FAR * h_addr_list; h_addr h_addr_list[0] // // // // // // official name of host alias list host address type length of address list of addresses address, for backward compatibility
On success, the
WSAGetLastError()
gethostbyname()
function returns a pointer to a
NULL
hostent NULL
structure, and
, you can call
on failure, the function returns
to determine the specifics of the problem. Possible error values include the following: WSANOTINITIALIZED if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSAHOST_NOT_FOUND if the host name couldn’t be resolved; WSATRY_AGAIN if the cause of the failure could be temporary, such as a name server being down; WSANO_RECOVERY if there was an unrecoverable error;
WSANO _DATA WSAEINPROGRESS
. On a return value of
if a blocking WinSock operation is currently in progress; or if the blocking call was canceled by WSACancelBlockingCall().
if the host name is valid but no appropriate data could be found;
WSAEINTR
Slide 75: 78
Part II ■ Basics of WinSock Programming
Tip
About blocking versus asynchronous WinSock function calls: Certain WinSock functions are classified as blocking when their return times are indeterminate. If a program blocks on a function call in the nonpreemptive Windows 3.1 environment, the performance of the entire computer system may be affected. While the blocking function is in its blocking state, the message loop for the application doesn’t receive any CPU time. Because this is unacceptable, the WinSock developers came up with a scheme whereby, under the nonpreemptive versions of Windows, a special message loop runs while a blocking function call is waiting to complete its operation. This ensures that the other programs on the computer get some CPU time.
Of course, the Windows NT environment, with its true preemptive multitasking capabilities, doesn’t require this work-around, but it can be accessed for backward compatibility. Actually, even Windows NT can take
advantage of this feature if you look at the thread level. Under Windows NT, a program may consist of one or more threads of execution. When a blocking call is executed, only the thread that made the blocking call is affected; the other threads of the program continue to get CPU time as do the other applications running on the computer. If this special message loop was running for the thread that called the blocking function, that thread could receive additional messages. WinSock has a default message loop but you can substitute your own using the WSASetBlockingHook() function. The only WinSock function that can be called safely from within this blocking hook function is WSACancelBlockingCall(). If this cancel function is executed by the special blocking hook function, the blocking WinSock function call will return
WSAEINTR.
because a much simpler and easily portable solution exists. This other solution involves the use of WinSock asynchronous functions. These functions begin with the WSAAsync prefix. They were designed specifically for the messagebased Windows environment and provide a much “cleaner” solution to the preceding problem.
This book doesn’t examine the use of this special blocking hook function
Slide 76: Chapter 6■ Conversion and Database Functions 79
FIGURE 6.1.
WinSock using a local file lookup.
Application
1) Application calls a WinSock database function
3) WinSock database function returns
WinSock Library
2) WinSock does the lookup on a local flat file database
Flat File Database
Using a couple of the functions described thus far, you can display the IP address of any host on your internetwork as well as find out your own machine’s name and IP address. The following sample code fragment does just that:
#define HOST_NAME_LEN (50) char lpszHostName[HOST_NAME_LEN]; char lpszMessage[100]; char lpszIP[16]; PHOSTENT phostent; IN_ADDR in; // // // // // will accept the host name informational message IP address string pointer to host entry structure Internet address structure
// find the name of the machine this program is running on if (gethostname(lpszHostName, HOST_NAME_LEN) != 0) wsprintf(lpszMessage, “gethostname() generated error %d”, WSAGetLastError()); else { // get the host entry structure for this machine if ((phostent = gethostbyname(lpszHostName)) == NULL) wsprintf(lpszMessage, “gethostbyname() generated error %d”, WSAGetLastError()); else { // copy the four byte IP address into a Internet address structure memcpy(&in, phostent->h_addr, 4); // format the results, converting the IP address into a string wsprintf(lpszMessage, “Host %s has IP address “, phostent->h_name); wsprintf(lpszIP, “%s”, inet_ntoa(in));
Slide 77: 80
Part II ■ Basics of WinSock Programming
lstrcat(lpszMessage, lpszIP); } } MessageBox(NULL, lpszMessage, “Info”, MB_OK);
FIGURE 6.2.
WinSock using a networked database server.
Application
1) Application calls a WinSock database function
5) WinSock database function returns
WinSock Library
2) WinSock sends the request across the network to a server
4) Network database server sends the response
Network Database Server 3) Network database server does a database lookup
Suppose that the computer on which this program runs is called “saturn.” The call to gethostname() will set “saturn” as the host name, and that string will be copied into lpszHostName. Suppose also that this computer uses a host file for name resolution, as opposed to using a networked name server. One line of that host file might look like this:
166.78.16.200 saturn
looks for “saturn” in the host file, finds the line on which it resides, and extracts the associated IP address. It then places all this information into a hostent host entry structure. The end result is the formatted message describing that “saturn” has an IP address of “166.78.16.200”.
gethostbyname()
Slide 78: Chapter 6■ Conversion and Database Functions 81
Note
Notice that in this sample, the IP address still had to be copied into an in_addr structure, but this time the source wasn’t an unsigned long as it was in the
sample. This time, the source of the IP address was the hostent host entry structure. The h_addr member variable of the hostent structure is a
inet_ntoa()
pointer to the first byte of the four-byte IP address, already stored in network byte order.
Asynchronously Finding a Host’s IP Address In the introduction to gethostbyname(), I said that there was one caveat with its use. In the getXbyY functions, one of which is gethostbyname(), the data retrieved might come from the local host or might come by way of a request over the network to a server of some kind. As soon as network communications is introduced into the picture, you have to be concerned with response times and the responsiveness of the application to the user while those network requests are taking place.
The WSAAsyncGetHostByName() function is the asynchronous version of gethostbyname(). It was added to WinSock to complement the Berkeley socket function for the messagepassing architecture of Microsoft Windows. This function is described as asynchronous because calling the function doesn’t suspend execution of the calling application,
but instead allows the application to continue until the request generated by WSAAsyncGetHostByName() has completed. When gethostbyname() or any other getXbyY
function is called, there is no guarantee when that function might return with a response. If the function generates a network operation, the response time is indeterminate. While that request is outstanding, your program is halted; the user can’t move or close the window or even cancel the operation. Not only that, but in the nonpreemptive Windows 3.1 environment, other applications will suffer; the entire system will seem to come to a temporary, or not so temporary, halt. Using WSAAsyncGetHostByName() makes your application responsive to the user’s input and doesn’t adversely affect other applications running on the computer. Once the request has completed, a Windows message is posted to a window in the application. While the request is still outstanding (for example, if the networked domain name server is doing a database lookup and preparing to send the search results back over the network) the message loop in the calling application, as well as the message loops of the other applications running on the computer, continue to operate, making all the programs responsive to user manipulation.
Slide 79: 82
Part II ■ Basics of WinSock Programming
Note
bother with the extra overhead required to use the asynchronous versions of the getXbyY functions; the blocking functions will do fine because you know they will return immediately and won’t cause any responsiveness problems for any running applications.
The function prototype for WSAAsyncGetHostByName() is as follows:
HANDLE PASCAL FAR WSAAsyncGetHostByName(HWND hWnd, u_int wMsg, const char FAR * name, char FAR * buf, int buflen);
Practically speaking, if you know that the environment under which your program runs uses a local host’s file to resolve host names, you don’t need to
hWnd
has completed its asynchronous operation. wMsg is the message that will be posted to hWnd when the asynchronous operation is complete. wMsg is generally a user-defined message (that is, WM_USER + 1). name is a pointer to a string that contains the host name for which information is being requested (that is, “goober” or “goober.ping.com”). buf is a pointer to an area of memory that, on successful completion of the host name lookup, will contain the hostent structure for the desired host. Note that this buffer must be larger than the hostent structure itself, because WinSock uses the additional area to store related information. WinSock provides a defined value
WSAAsyncGetHostByName()
is the handle to the window to which a message will be sent when
named
MAXGETHOSTSTRUCT,
that there is enough space allocated. MAXGETHOSTSTRUCT for safety’s sake.
WSAAsyncGetHostByName()
which you can use as the size of the buffer. This size will ensure
buflen
is the size of the
buf
buffer. It should be
If the asynchronous operation is initiated successfully , the return value of
is a handle to the asynchronous task. On failure of initialization, the function returns 0 (zero), and WSAGetLastError() should be called to find out the reason for the error. Possible error values include the following: WSANOTINITIALIZED if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSAEWOULDBLOCK if the function cannot be scheduled at this time due to a resource conflict within the specific WinSock implementation; or WSAEINPROGRESS if a blocking WinSock operation is currently in progress. Notice that the function’s return value doesn’t tell you whether the requested information was retrieved successfully; it only tells you whether the function was started properly.
The previous sample code, which displays the name and IP address of the machine on which the program runs, can be reworked to use the following asynchronous calls:
// global variables #define WM_USER_GETHOSTBYNAME (WM_USER + 1) #define HOST_NAME_LEN (50)
Slide 80: Chapter 6 ■ Conversion and Database Functions 83
char lpszHostName[HOST_NAME_LEN]; char lpszMessage[100]; char lpszIP[16]; PHOSTENT phostent;
// // // //
will accept the host name informational message IP address string pointer to host entry structure
char lpszHostEntryBuf[MAXGETHOSTSTRUCT]; // host entry structure IN_ADDR in; // Internet address structure HANDLE hGetHostByName; // handle of asynchronous request // this function is [part of] the window procedure long FAR PASCAL WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { // check menu items case WM_COMMAND: // handle the menu item to get this host’s name and IP address if (wParam == ID_GETHOSTBYNAME) { // find the name of the machine this program is running on if (gethostname(lpszHostName, HOST_NAME_LEN) != 0) { wsprintf(lpszMessage, “gethostname() generated error %d”, WSAGetLastError()); MessageBox(NULL, lpszMessage, “Info”, MB_OK); } else { // get the host entry structure for this machine if ((hGetHostByName = WSAAsyncGetHostByName(hWnd, WM_USER_GETHOSTBYNAME, lpszHostName, lpszHostEntryBuf, MAXGETHOSTSTRUCT)) == 0) { wsprintf(lpszMessage, “WSAAsyncGetHostByName() generated error %d”, WSAGetLastError()); MessageBox(NULL, lpszMessage, “Info”, MB_OK); } } } break; case WM_USER_GETHOSTBYNAME: // check for an error if (WSAGETASYNCERROR(lParam) != 0) MessageBox(NULL, “WSAAsyncGetHostByName() had an error”, “Info”, MB_OK); else { // assign a hostent host entry pointer to the buffer phostent = (PHOSTENT)lpszHostEntryBuf; // copy the four byte IP address into a Internet address structure memcpy(&in, phostent->h_addr, 4); // format the results, converting the IP address into a string wsprintf(lpszMessage, “Host %s has IP address “, phostent->h_name); wsprintf(lpszIP, “%s”, inet_ntoa(in)); lstrcat(lpszMessage, lpszIP); MessageBox(NULL, lpszMessage, “Info”, MB_OK); }
Slide 81: 84
Part II ■ Basics of WinSock Programming
break; default: break; } }
Note that the first thing done in the
call to error
WSAASYNCERROR ( ) WSAAsyncGetHostByName() WSAENOBUFS,
WM_USER_GETHOSTBYNAME
message handler is a
request. A value of 0 (zero) means everything is fine. Other possible values are any error messages in WINSOCK.H. If WSAASYNCERROR() returns structure wasn’t big enough. To be safe, use a buffer at least MAXGETHOSTSTRUCT bytes in size. Also, for your information, in the WM_USER_GETHOSTBYNAME message handler, wParam is the asynchronous task handle for the currently returning operation. This means that you could use the same WM_USER message for multiple, simultaneously outstanding asynchronous requests. You would then examine wParam to determine which specific operation was returning at that instance in time.
Doing It with Visual C++
. This is a macro that determines the success of the to hold the
the buffer passed to
WSAAsyncGetHostByName()
hostent
This book is about the use of WinSock with Microsoft Visual C++ and the Microsoft Foundation Classes. The previous example was given in the “old-fashioned” SDK style as a way of introducing the first asynchronous function. The remaining samples in this book will be based primarily on Visual C++ and MFC. Using MFC, the preceding sample code could be implemented as follows. First comes the class declaration. This example has a class named CMyWindow derived from the base class CFrameWnd. CFrameWnd is a class provided by MFC. This sample doesn’t show the entire class declaration, only the pieces needed to replicate the previous SDK sample:
class CMyWindow : public CFrameWnd {
// member variables #define HOST_NAME_LEN (50) char m_lpszHostName[HOST_NAME_LEN]; // will accept the host name char m_lpszMessage[100]; // informational message char m_lpszIP[16]; // IP address string PHOSTENT m_phostent; // pointer to host entry structure char m_lpszHostEntryBuf[MAXGETHOSTSTRUCT]; // host entry structure IN_ADDR m_in; // Internet address structure HANDLE m_hGetHostByName; // handle of asynchronous request // member functions in the message map // {{AFX_MSG(CMyWindow) afx_msg void OnDoAsyncGetHostByName();
Slide 82: Chapter 6■ Conversion and Database Functions 85
afx_msg LONG OnAsyncGetHostByName(WPARAM wParam, LPARAM lParam); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
One thing you’ll notice is that all the variables that are global in the SDK sample are now encapsulated into the class in which they are used. This is just one of the benefits of the C++ object-oriented language. Note also that these variables, the class member variables, are preceded by the m_ prefix. Tagging member variables in this manner helps you recognize them more easily in the implementation of the class. The next section of the class declaration contains the prototypes for the functions that are in the window’s message map. The message map is used by MFC to automate the routing of messages to their designated windows. It takes the place of the switch-case construct in a traditional SDK window procedure. The implementation of the CMyWindow class begins with the message map for the window as follows:
#define WM_USER_ASYNCGETHOSTBYNAME (WM_USER + 1) BEGIN_MESSAGE_MAP(CMyWindow, CFrameWnd) //{{AFX_MSG_MAP(CMyWindow) ON_COMMAND(ID_TEST_ASYNCGETHOSTBYNAME, ON_MESSAGE(WM_USER_ASYNCGETHOSTBYNAME, OnAsyncGetHostByName) //}}AFX_MSG_MAP END_MESSAGE_MAP()
OnDoAsyncGetHostByName)
The first entry in the message map is for a menu item that will launch the search. The ON_COMMAND macro automates the parsing of the WM_COMMAND message that is used in an SDK program . It matches up the appropriate menu ID , in this case ID_TEST_ASYNCGETHOSTBYNAME, and associates it with the OnDoAsyncGetHostByName() member function . When the user selects the menu item that has I D _ T E S T _ A S Y N C G E T H O S T B Y N A M E as its identifier in the menu resource , the OnDoAsyncGetHostByName() function is called. That function is implemented as follows:
void CMyWindow::OnDoAsyncGetHostByName() { // find the name of the machine this program is running on if (gethostname(m_lpszHostName, HOST_NAME_LEN) != 0) { wsprintf(m_lpszMessage, “gethostname() generated error %d”, WSAGetLastError()); MessageBox(m_lpszMessage, “Info”); } else { // get the host entry structure for this machine if ((m_hGetHostByName = WSAAsyncGetHostByName(m_hWnd, WM_USER_ASYNCGETHOSTBYNAME, m_lpszHostName, m_lpszHostEntryBuf, MAXGETHOSTSTRUCT)) == 0) { wsprintf(m_lpszMessage, “WSAAsyncGetHostByName() generated error %d”,
Slide 83: 86
Part II ■ Basics of WinSock Programming
WSAGetLastError()); MessageBox(m_lpszMessage, “Info”); } } }
The second entry in the message map is for a user-defined message that indicates the asynchronous function has completed. The ON_MESSAGE macro automates the parsing of WM_USER messages that are used in an SDK program. It matches up a specific userdefined message, in this case WM_USER_ASYNCGETHOSTBYNAME, and associates it with the OnAsyncGetHostByName() member function. When the WM_USER_ASYNCGETHOSTBYNAME message is generated by WinSock on the completion of the asynchronous call, the OnAsyncGetHostByName() function is executed. That function is implemented as follows:
LONG CMyWindow::OnAsyncGetHostByName(WPARAM wParam, LPARAM lParam) { // check for an error if (WSAGETASYNCERROR(lParam) != 0) MessageBox(“WSAAsyncGetHostByName() had an error”, “Info”); else { // assign a hostent host entry pointer to the buffer m_phostent = (PHOSTENT)m_lpszHostEntryBuf; // copy the four byte IP address into a Internet address structure memcpy(&m_in, m_phostent->h_addr, 4); // format the results, converting the IP address into a string wsprintf(m_lpszMessage, “Host %s has IP address “, m_phostent->h_name); wsprintf(m_lpszIP, “%s”, inet_ntoa(m_in)); lstrcat(m_lpszMessage, m_lpszIP); MessageBox(m_lpszMessage, “Info”); } return 0L; }
Note that both OnDoAsyncGetHostByName() and OnAsyncGetHostByName() have an almost identical implementation to the SDK version of this sample.
Note
About message maps and the Visual C++ ClassWizard: ClassWizard associates message identifiers, such as menu items, with class member functions. It automatically inserts a skeletal function in the implementation file for the class. This is handy because ClassWizard “knows” the correct format for the function prototype. The programs in this book were developed using Visual C++ 1.5 and Visual C++ 1.1 32-Bit Edition. The versions of ClassWizard in these versions of Visual C++ do not support the automatic generation of message map entries for user-defined messages. This means that for any WM_USER messages you create,
Slide 84: Chapter 6■ Conversion and Database Functions 87
you must manually insert the ON_MESSAGE macro into the message map, create a function prototype in the class definition, and create the member function from scratch.
Canceling an Outstanding Asynchronous Request
The handle returned by the asynchronous database functions , such as WSAAsyncGetHostByName ( ) , can be used to terminate the database lookup. The WSACancelAsyncRequest() function performs this task. Its prototype is the following:
int PASCAL FAR WSACancelAsyncRequest(HANDLE hAsyncTaskHandle);
is the handle to the asynchronous task you wish to abort. On success, this function returns 0 (zero). On failure, it returns SOCKET _ERROR , and W S A G e t L a s t E r r o r ( ) can be called . Possible errors include the following :
hAsyncTaskHandle WSANOTINITIALISED
if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSAEINPROGRESS if a blocking WinSock operation is currently in
progress;
WSAEINVAL
if the asynchronous routine being canceled has already completed. WSAEALREADY might result if the original operation has already completed and the resulting message has been processed or if the original operation has already completed but the resulting message is still waiting in the application’s message queue. By using WSACancelAsyncRequest() in your applications, you give users much greater control over the program. If users perform an operation that generates an asynchronous database call and the operation is taking an excruciatingly long time to complete, as it might when networked name servers are involved, it’s nice to let users regain control of the program instead of being at its mercy.
if the specified asynchronous task handle is invalid; or
WSAEALREADY
IP Address Resolution
IP address resolution is the opposite of host name resolution. In host name resolution,
using
gethostbyname()
dress when you know the host name. The goal of IP address resolution is to get the host
name, and other host information, when all you know is its IP address. The gethostbyaddr() and WSAAsyncGetHostByName() functions are used to fulfill this goal.
or
WSAAsyncGetHostByName(),
the objective is to get the IP ad-
If you haven’t yet, please read and get a full understanding of the gethostbyname() and WSAAsyncGetHostByName() functions; the remaining functions are used in a similar manner as those two functions, so the explanations for the following functions have been abbreviated.
Slide 85: 88
Part II ■ Basics of WinSock Programming
Finding a Host Name When You Know Its IP Address
gethostbyaddr() is to take the IP address of a host and return its name. This function, and its asynchronous counterpart named WSAAsyncGetHostByAddr(), might perform a simple table lookup on a host file local to the computer on which the program is running, or it might send the request across the network to a name server. The function’s prototype looks like the following:
The main duty of
struct hostent FAR * PASCAL FAR gethostbyaddr(const char FAR * addr, int len, int type);
addr
is a pointer to the IP address, in network byte order, of the computer about which
len
you want host information. The
WinSock 1.1, the length is always four because this version of the specification supports only Internet style addressing. type must always be PF_INET for the same reason.
gethostbyaddr()
is the length of the address to which
addr
points. In
success and NULL on failure. Upon a return value of NULL, you can call WSAGetLastError() to determine the specifics of the problem. Possible error values include the following: WSANOTINITIALIZED if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSAHOST_NOT_FOUND if the host name couldn’t be resolved; WSATRY_AGAIN if the cause of the failure could be temporary, such as a name server being
down;
WSANO_RECOVERY
function returns a pointer to a
hostent
host entry structure on
is valid but no appropriate data could be found; WSAEINPROGRESS if a blocking WinSock operation is currently in progress; or WSAEINTR if the blocking call was canceled by
WSACancelBlockingCall().
if there was an unrecoverable error;
WSANO_DATA
if the IP address
The following sample code fragment will find the host name that has the specified IP address:
u_long ulIPAddress = inet_addr(“166.78.16.201”); // binary IP address char lpszMessage[100]; // informational message char lpszIP[16]; // IP address string PHOSTENT phostent; // pointer to host entry structure IN_ADDR in; // Internet address structure // get the host entry structure for the specified IP address if ((phostent = gethostbyaddr((char *)&ulIPAddress, 4, PF_INET)) == NULL) wsprintf(lpszMessage, “gethostbyaddr() generated error %d”, WSAGetLastError()); else { // copy the four byte IP address into an Internet address structure memcpy(&in, phostent->h_addr, 4); // format the results, converting the IP address into a string wsprintf(lpszMessage, “Host %s has IP address “, phostent->h_name); wsprintf(lpszIP, “%s”, inet_ntoa(in)); lstrcat(lpszMessage, lpszIP); } MessageBox(NULL, lpszMessage, “Info”, MB_OK);
Slide 86: Chapter 6■ Conversion and Database Functions 89
Suppose that the computer with IP address 166.78.16.201 is called “jupiter.” Suppose also that this computer uses a host file for name resolution as opposed to a networked name server. One line of that host’s file might look like this:
166.78.16.201 jupiter
looks for the designated IP address in the host file, finds the line on which it resides, and extracts the associated host name. It then places all this information into a
gethostbyaddr() hostent
host entry structure. The end result is the formatted message describing that “jupiter” has an IP address of “166.78.16.201”.
Note
In the call to gethostbyaddr(), the IP address ulIPAddress had to be cast to a character pointer.
ulIPAddress is an unsigned long (four bytes in size) that contains the binary IP address in network byte order. gethostbyaddr() expects a pointer to the first byte of that four quantity, so you take the address of the variable and cast it to a character pointer.
Asynchronously Finding a Host Name When You Know Its IP Address The WSAAsyncGetHostByAddr() function is the asynchronous version of gethostbyaddr(). Its function prototype is as follows:
HANDLE PASCAL FAR WSAAsyncGetHostByAddr(HWND hWnd, u_int wMsg, const char FAR * addr, int len, int type, char FAR * buf, int buflen);
hWnd
has completed its asynchronous operation. wMsg is the userdefined message that will be posted to hWnd when the asynchronous operation is complete. addr is a pointer to the IP address, in network byte order, of the computer about which you want host information. len is the length of the address to which addr points and is always 4 (four) for Internet addresses. type must always be PF_INET because
WSAAsyncGetHostByAddr()
is the handle to the window to which a message will be sent when
WinSock 1.1 supports only Internet-style addressing.
buf
memory that, upon successful completion of the address lookup, will contain the hostent structure for the desired host. This buffer must be large enough to store the hostent
structure as well as other referenced data, therefore it should be at least
MAXGETHOSTSTRUCT
is a pointer to an area of
bytes long. sake.
buflen
is the size of the
buf
buffer. It should be
MAXGETHOSTSTRUCT
for safety’s
If the asynchronous operation is initiated successfully , the return value of WSAAsyncGetHostByAddr() is a handle to the asynchronous task. On failure of initializa-
Slide 87: 90
Part II ■ Basics of WinSock Programming
tion, the function returns 0 (zero) and WSAGetLastError() should be called to find out the reason for the error. Possible error values include the following: WSANOTINITIALIZED if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSAEWOULDBLOCK if the function cannot be scheduled at this time due to a resource conflict within the specific WinSock implementation; or WSAEINPROGRESS if a blocking WinSock operation is currently in progress. Note that the function’s return value doesn’t tell you whether the requested information was retrieved successfully; all it tells you is whether the function was started properly. This function is used much like WSAAsyncGetHostByName(). Using an MFC implementation method to replicate the example given in the gethostbyaddr() example would have class declaration such as the following:
class CMyWindow : public CFrameWnd {
// member variables u_long m_ulIPAddress; // binary IP address char m_lpszMessage[100]; // informational message char m_lpszIP[16]; // IP address string PHOSTENT m_phostent; // pointer to host entry structure IN_ADDR m_in; // Internet address structure HANDLE m_hGetHostByAddr; // handle of asynchronous request char m_lpszHostEntryBuf[MAXGETHOSTSTRUCT]; // host entry structure // member functions in the message map //{{AFX_MSG(CMyWindow) afx_msg void OnDoAsyncGetHostByAddr(); afx_msg LONG OnAsyncGetHostByAddr(WPARAM wParam, LPARAM lParam); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Note that the variables are now member variables of the class. The implementation of the CMyWindow class begins with the message map for the window:
#define WM_USER_ASYNCGETHOSTBYADDR (WM_USER + 2) BEGIN_MESSAGE_MAP(CMyWindow, CFrameWnd) //{{AFX_MSG_MAP(CMyWindow) ON_COMMAND(ID_TEST_ASYNCGETHOSTBYADDR, ON_MESSAGE(WM_USER_ASYNCGETHOSTBYADDR, OnAsyncGetHostByAddr) //}}AFX_MSG_MAP END_MESSAGE_MAP()
OnDoAsyncGetHostByAddr)
The first entry in the message map is for a menu item that will launch the search. That function is implemented as follows:
void CMyWindow::OnDoAsyncGetHostByAddr() { // get a binary IP address m_ulIPAddress = inet_addr(“166.78.16.201”);
Slide 88: Chapter 6■ Conversion and Database Functions 91
// get the host entry structure if ((m_hGetHostByAddr = WSAAsyncGetHostByAddr(m_hWnd, WM_USER_ASYNCGETHOSTBYADDR, (char *)&m_ulIPAddress, 4, PF_INET, m_lpszHostEntryBuf, MAXGETHOSTSTRUCT)) == 0) { wsprintf(m_lpszMessage, “WSAAsyncGetHostByAddr() generated WSAGetLastError()); MessageBox(m_lpszMessage, “Info”); } }
error
%d”,
In this example, the IP address is hard-coded; in production programs, values such as this should be user configurable. The second entry in the message map is for the userdefined message that indicates that the asynchronous function has completed. When the WM_USER_ASYNCGETHOSTBYADDR message is generated by WinSock upon the completion of the asynchronous call, the OnAsyncGetHostByAddr() function is executed. That function is implemented as follows:
LONG CMyWindow::OnAsyncGetHostByAddr(WPARAM wParam, LPARAM lParam) { // check for an error if (WSAGETASYNCERROR(lParam) != 0) MessageBox(“WSAAsyncGetHostByAddr() had an error”, “Info”); else { // assign a hostent host entry pointer to the buffer m_phostent = (PHOSTENT)m_lpszHostEntryBuf; // copy the four byte IP address into a Internet address structure memcpy(&m_in, m_phostent->h_addr, 4); // format the results, converting the IP address into a string wsprintf(m_lpszMessage, “Host %s has IP address “, m_phostent->h_name); wsprintf(m_lpszIP, “%s”, inet_ntoa(m_in)); lstrcat(m_lpszMessage, m_lpszIP); MessageBox(m_lpszMessage, “Info”); } return 0L; }
Note that the OnAsyncGetHostByAddr() member function is like OnAsyncGetHostByName(), with the only difference being the text in the error message. These functions are the same because each is manipulating a hostent host entry structure.
Caution
Don’t forget to use the WSAGETASYNCERROR() macro to check for an error in your message handlers for the asynchronous calls.
Slide 89: 92
Part II ■ Basics of WinSock Programming
Service Name Resolution
So far, you have seen how to retrieve a binary IP address, whether it be derived from a host name or the host’s IP address. But the IP address of a host is only half of the equation when it comes to making a network connection between client and server applications; the port number provides the other half of the equation. When a computer is running a server application, it’s said to be providing a service. Each service is uniquely identified by a well-known port number. The server program “listens” for connections on the well-known port and the client program opens a connection to that port. The port numbers must be unique to distinguish the many server programs that a host may provide. The port numbers must be well-known so that application programmers can request them by name. Figure 6.3 shows a host that is providing two services: port number 37 is acting as a time server over UDP, and port number 79 is acting as a finger server over TCP. Note that the ports out of which the clients connect are represented as question marks. Clients don’t need to specify a port when they create their outbound sockets; the socket can be assigned a unique port at runtime by the TCP/IP stack.
FIGURE 6.3.
A host providing two services and two connecting clients.
mars 166.78.16.202
79 finger 37 time
?
?
saturn 166.78.16.200
jupiter 166.78.16.201
The
getservbyname()
trieving the port number when you know its service name. Using a service name in your program, as opposed to a port number, allows the user of your program to decide which port should be designated for the service your program is providing. Users appreciate this level of control. As the types of services users’ computers provide, or the ways in which they’re used, change over time, users have the flexibility of easily configuring their machines as they see fit.
and
WSAAsyncGetServByName()
functions
are
responsible
for
re-
Slide 90: Chapter 6■ Conversion and Database Functions 93
The Services File
The service name to port translation is commonly supported by a flat file database called the services file. A partial listing of a common services file looks like the following:
# # # # # This file contains port numbers for well-known services as defined by RFC 1060 (Assigned Numbers). Format: <service name>
<port number>/<protocol> 37/tcp 37/udp 79/tcp timserver timserver
[aliases...]
[#<comment>]
time time finger
The lines preceded by a pound sign (#) are comments. Notice the reference to RFC 1060. This Internet Request for Comment outlines some standard well-known ports. This ensures that nobody creates a service that utilizes an already established port number. When you create your own custom servers, you need to allocate a port number between 1024 and 5000 exclusive. The ports from 1024 and below are reserved for universally well-known ports. Often, these ports are allocated to new services when someone invents the new service and distributes the specification through an RFC. Following the header comment is the listed services. The left-most column contains the name of the service. The next column has the port number and transport-level protocol separated by a forward slash (/). The remaining columns contain aliases for the service. In the preceding example, the time service is recognized as “time” or “timeserver.” The finger service has no aliases. The transport-level protocol field specifies either User Datagram Protocol or Transmission Control Protocol. This is the type of socket (datagram or stream) that must be used to communicate with the designated service. Note that the time service responds to port 37 on both a UDP and TCP connection. The port number/protocol pair provides for a unique correlation to a service within each transport protocol; this is to say that a service might respond to UDP port 100, and a completely different service might respond to TCP port 100. It’s also possible for a specific service to respond to one UDP port and an entirely different TCP port.
Tip
For custom applications that you produce, it’s usually sufficient to simply refer to a service number by its port number. This frees you from having to make an entry in the services table for your custom services. For the sake of flexibility, make your server and client applications configurable with respect to the port numbers they use. Allow the server to listen for connections on a configurable port and make sure that the port to which the client connects is also
Slide 91: 94
Part II ■ Basics of WinSock Programming
different servers hard-coded to use the same port number. If your server program must run on a computer with another server that was written to use a hard-coded port number, you can change the server and client configuration to use an unused port. With this information hard-coded, the change requires a recompile of the applications.
configurable. This will ensure an easy fix to the problem of having several
Finding a Service’s Port Number The getservbyname() function gets service information corresponding to a specific service name and protocol. Its function prototype looks like the following:
struct servent FAR * PASCAL FAR getservbyname(const char FAR * name, const char FAR * proto);
is a pointer to a string that contains the service for which you are searching. The service name is either the official service name or an alias. proto is a pointer to a string that contains the transport protocol to use; it’s either “udp”, “tcp”, or NULL. A NULL proto will match on the first service in the services table that has the specified name, regardless of the protocol. The servent structure returned has the following format:
name
struct char char short char }; servent { FAR * s_name; FAR * FAR * s_aliases; s_port; FAR * s_proto; // // // // official service name alias list port # protocol to use
A note on transport protocols: Protocol names are case sensitive. “TCP” is different from “tcp.” Ensure that you are using the exact format as listed in your services file. structure on success and NULL on failure. On a return value of NULL, you can call WSAGetLastError() to determine the specifics of the problem. Possible error values include the following: WSANOTINITIALISED if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSANO_RECOVERY if there was an unrecoverable error; WSANO_DATA if the service name is valid but no appropriate data The
getservbyname()
function returns a pointer to a
servent
could be found; WSAEINPROGRESS if a blocking WinSock operation is currently in progress; or WSAEINTR if the blocking call was canceled by WSACancelBlockingCall().
This code fragment searches for the time service on a UDP transport connection:
PSERVENT pservent; char lpszMessage[100]; char lpszPort[6]; // pointer to service entry structure // informational message // port number string
Slide 92: Chapter 6■ Conversion and Database Functions 95
// get the service entry structure for the time service using UDP if ((pservent = getservbyname(“time”, “udp”)) == NULL) wsprintf(lpszMessage, “getservbyname() generated error %d”, WSAGetLastError()); else { // format the results wsprintf(lpszMessage, “Service %s using protocol %s has port “, pservent->s_name, pservent->s_proto); wsprintf(lpszPort, “%d”, ntohs(pservent->s_port)); lstrcat(lpszMessage, lpszPort); } MessageBox(NULL, lpszMessage, “Info”, MB_OK);
The end result is the formatted message describing that the “time” service using the “udp” protocol has port 37. Notice that the port number, pservent->s_port, must be
converted from network to host byte ordering.
If the sample was rewritten to not specify the transport protocol, the results would be different:
PSERVENT pservent; char lpszMessage[100]; char lpszPort[6]; // pointer to service entry structure // informational message // port number string
// get the service entry structure for the time service if ((pservent = getservbyname(“time”, NULL)) == NULL) wsprintf(lpszMessage, “getservbyname() generated error %d”, WSAGetLastError()); else { // format the results wsprintf(lpszMessage, “Service %s using protocol %s has port “, pservent->s_name, pservent->s_proto); wsprintf(lpszPort, “%d”, ntohs(pservent->s_port)); lstrcat(lpszMessage, lpszPort); } MessageBox(NULL, lpszMessage, “Info”, MB_OK);
The end result of this sample is the formatted message describing that the “time” service using the “tcp” protocol has port 37. The TCP result is given because that entry appears first in the services file.
Asynchronously Finding a Service’s Port Number
is the asynchronous counterpart to function prototype is as follows:
WSAAsyncGetServByName()
HANDLE PASCAL FAR WSAAsyncGetServByName(HWND hWnd, u_int wMsg, const char FAR * name, const char FAR * proto, char FAR * buf, int buflen);
getservbyname().
Its
Slide 93: 96
Part II ■ Basics of WinSock Programming
hWnd
has completed its asynchronous operation. wMsg is the userdefined message that will be posted to hWnd when the asynchronous operation is complete. name is a pointer to a service name about which you want service information. proto is a pointer to a protocol name; it is “tcp”, “udp”, or NULL. If proto is NULL, the first matching service is returned. buf is a pointer to an area of memory that, on successful completion of the service lookup, will contain the servent structure for the desired service. This buffer must be large enough to store the servent structure as well as other referenced data; therefore, it should be at least MAXGETHOSTSTRUCT bytes long. buflen is the size of the buf buffer. It should be MAXGETHOSTSTRUCT for safety’s sake.
WSAAsyncGetServByName()
is the handle to the window to which a message will be sent when
If the asynchronous operation is initiated successfully , the return value of
WSAAsyncGetServByName()
is a handle to the asynchronous task. On failure of initialization, the function returns 0 (zero) and WSAGetLastError() should be called to find out the reason for the error. Possible error values include the following: WSANOTINITIALIZED if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSAEWOULDBLOCK if the function cannot be scheduled at this time due to a resource conflict within the specific WinSock implementation; or WSAEINPROGRESS if a blocking WinSock operation is currently in progress. The function’s return value doesn’t tell you whether the requested information was retrieved successfully; all it tells you is whether the function was started properly. This function is used much like the asynchronous functions discussed earlier in this chapter. Using an MFC implementation method to replicate the example given in the getservbyname() example would have a class declaration like the following:
class CMyWindow : public CFrameWnd {
// member variables PSERVENT m_pservent; // pointer to service entry structure char m_lpszMessage[100]; // informational message char m_lpszPort[6]; // port number string HANDLE m_hGetServByName; // handle of asynchronous request char m_lpszServEntryBuf[MAXGETHOSTSTRUCT]; // service entry structure // member functions in the message map //{{AFX_MSG(CMyWindow) afx_msg void OnDoAsyncGetServByName(); afx_msg LONG OnAsyncGetServByName(WPARAM wParam, LPARAM lParam); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Notice that the variables are now member variables of the class. The implementation of the CMyWindow class begins with the message map for the window:
#define WM_USER_ASYNCGETSERVBYNAME (WM_USER + 3)
Slide 94: Chapter 6■ Conversion and Database Functions 97
BEGIN_MESSAGE_MAP(CMyWindow, CFrameWnd) //{{AFX_MSG_MAP(CMyWindow) ON_COMMAND(ID_TEST_ASYNCGETSERVBYNAME, ON_MESSAGE(WM_USER_ASYNCGETSERVBYNAME, OnAsyncGetServByName) //}}AFX_MSG_MAP END_MESSAGE_MAP()
OnDoAsyncGetServByName)
The first entry in the message map is for a menu item that will launch the search. That function is implemented as follows:
void CMyWindow::OnDoAsyncGetServByName() { // get the service entry structure for the time service using UDP if ((m_hGetServByName = WSAAsyncGetServByName(m_hWnd, WM_USER_ASYNCGETSERVBYNAME, “time”, “udp”, m_lpszServEntryBuf, MAXGETHOSTSTRUCT)) == 0) { wsprintf(m_lpszMessage, “WSAAsyncGetServByName() generated error WSAGetLastError()); MessageBox(m_lpszMessage, “Info”); } }
%d”,
The second entry in the message map is for the user-defined message that indicates that the asynchronous function has completed. When the WM_USER_ASYNCGETSERVBYNAME message is generated by WinSock on the completion of the asynchronous call, the OnAsyncGetServByName() function is executed. That function is implemented as follows:
LONG CMyWindow::OnAsyncGetServByName(WPARAM wParam, LPARAM lParam) { // check for an error if (WSAGETASYNCERROR(lParam) != 0) MessageBox(“WSAAsyncGetServByName() had an error”, “Info”); else { // assign a servent service entry pointer to the buffer m_pservent = (PSERVENT)m_lpszServEntryBuf; // format the results wsprintf(m_lpszMessage, “Service %s using protocol %s has port “, m_pservent->s_name, m_pservent->s_proto); wsprintf(m_lpszPort, “%d”, ntohs(m_pservent->s_port)); lstrcat(m_lpszMessage, m_lpszPort); MessageBox(m_lpszMessage, “Info”); } return 0L; }
Port Resolution
Port resolution is the opposite of service name resolution. Its goal is, given a port number and transport protocol , to find the corresponding named service . The getservbyport() and WSAAsyncGetServByPort() functions fulfill this goal.
Slide 95: 98
Part II ■ Basics of WinSock Programming
Finding a Service Name When You Know Its Port Number The getservbyport() function gets service information corresponding to a specific port and protocol. Its function prototype looks like the following:
struct servent FAR * PASCAL FAR getservbyport(int port, const char FAR * proto);
is the service port, in network byte order. proto is a pointer to a protocol name; it is “tcp”, “udp”, or NULL. If proto is NULL, the first matching service is returned. The
port getservbyport()
on failure. On a return value of NULL, you can call WSAGetLastError() to determine the specifics of the problem. Possible error values include the following: WSANOTINITIALIZED if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSANO_RECOVERY if there was an unrecoverable error; WSANO_DATA if the port number is valid but no appropriate data could be found; WSAEINPROGRESS if a blocking WinSock operation is currently in progress; or WSAEINTR if the blocking call was canceled by
WSACancelBlockingCall().
function returns a pointer to a servent structure on success and
NULL
This code fragment searches for the service corresponding to port 37 on a UDP transport connection:
PSERVENT pservent; char lpszMessage[100]; char lpszPort[6]; // pointer to service entry structure // informational message // port number string
// get the service entry structure for the port 37 service using UDP if ((pservent = getservbyport(htons(37), “udp”)) == NULL) wsprintf(lpszMessage, “getservbyport() generated error %d”, WSAGetLastError()); else { // format the results wsprintf(lpszMessage, “Service %s using protocol %s has port “, pservent->s_name, pservent->s_proto); wsprintf(lpszPort, “%d”, ntohs(pservent->s_port)); lstrcat(lpszMessage, lpszPort); } MessageBox(NULL, lpszMessage, “Info”, MB_OK);
Note that the port number is converted to network byte order before it is passed to getservbyport(). The end result is the formatted message describing that port 37 using the “udp” transport protocol corresponds to the “time” service. Note the similarity between this sample and the one presented for getservbyname().
Slide 96: Chapter 6■ Conversion and Database Functions 99
Asynchronously Finding a Service Name When You Know Its Port Number
is the asynchronous counterpart to function prototype is as follows:
WSAAsyncGetServByPort()
HANDLE PASCAL FAR WSAAsyncGetServByPort(HWND hWnd, u_int wMsg, int port, const char FAR * proto, char FAR * buf, int buflen);
getservbyport().
Its
hWnd
has completed its asynchronous operation. wMsg is the userdefined message that will be posted to hWnd when the asynchronous operation is complete. port is the service port, in network byte order, of the service about which you want information. proto is a pointer to a protocol name; it is “tcp”, “udp”, or NULL. If proto is NULL, the first matching service is returned. buf is a pointer to an area of memory that, on successful completion of the service lookup, will contain the servent structure for the desired service. This buffer must be large enough to store the servent structure as well as other referenced data; therefore, it should be at least MAXGETHOSTSTRUCT bytes long. buflen is the size of the buf buffer. It should be MAXGETHOSTSTRUCT for safety’s sake.
WSAAsyncGetServByName()
is the handle to the window to which a message will be sent when
If the asynchronous operation is initiated successfully , the return value of
WSAAsyncGetServByName()
is a handle to the asynchronous task. On failure of initialization, the function returns 0 (zero) and WSAGetLastError() should be called to find out the reason for the error. Possible error values include the following: WSANOTINITIALIZED if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSAEWOULDBLOCK if the function cannot be scheduled at this time due to a resource conflict within the specific WinSock implementation; or WSAEINPROGRESS if a blocking WinSock operation is currently in progress. The function’s return value doesn’t tell you whether the requested information was retrieved successfully; all it tells you is whether the function was started properly. This function is used much like the other asynchronous functions discussed thus far.
Using an MFC implementation method to replicate the example given in the example would have a class declaration like the following:
class CMyWindow : public CFrameWnd {
getservbyport()
// member variables PSERVENT m_pservent; // pointer to service entry structure char m_lpszMessage[100]; // informational message char m_lpszPort[6]; // port number string HANDLE m_hGetServByPort; // handle of asynchronous request char m_lpszServEntryBuf[MAXGETHOSTSTRUCT]; // service entry structure
Slide 97: 100
Part II ■ Basics of WinSock Programming
// member functions in the message map // {{AFX_MSG(CMyWindow) afx_msg void OnDoAsyncGetServByPort(); afx_msg LONG OnAsyncGetServByPort(WPARAM wParam, LPARAM lParam); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Notice that the variables are now member variables of the class. The implementation of the CMyWindow class begins with the message map for the window:
#define WM_USER_ASYNCGETSERVBYPORT (WM_USER + 4) BEGIN_MESSAGE_MAP(CMyWindow, CFrameWnd) //{{AFX_MSG_MAP(CMyWindow) ON_COMMAND(ID_TEST_ASYNCGETSERVBYPORT, ON_MESSAGE(WM_USER_ASYNCGETSERVBYPORT, OnAsyncGetServByPort) //}}AFX_MSG_MAP END_MESSAGE_MAP()
OnDoAsyncGetServByPort)
The first entry in the message map is for a menu item that will launch the search. That function is implemented as follows:
void CMyWindow::OnDoAsyncGetServByPort() { // get the service entry structure for the port 37 service using UDP if ((m_hGetServByPort = WSAAsyncGetServByPort(m_hWnd, WM_USER_ASYNCGETSERVBYNAME, htons(37), “udp”, m_lpszServEntryBuf, MAXGETHOSTSTRUCT)) == 0) { wsprintf(m_lpszMessage, “WSAAsyncGetServByPort() generated error WSAGetLastError()); MessageBox(m_lpszMessage, “Info”); } }
%d”,
The second entry in the message map is for the user-defined message that indicates the asynchronous function has completed. When the WM_USER_ASYNCGETSERVBYPORT message is generated by WinSock on completion of the asynchronous call, the OnAsyncGetServByPort() function is executed. That function is implemented as follows:
LONG CMyWindow::OnAsyncGetServByPort(WPARAM wParam, LPARAM lParam) { // check for an error if (WSAGETASYNCERROR(lParam) != 0) MessageBox(“WSAAsyncGetServByPort() had an error”, “Info”); else { // assign a servent service entry pointer to the buffer m_pservent = (PSERVENT)m_lpszServEntryBuf; // format the results wsprintf(m_lpszMessage, “Service %s using protocol %s has port “, m_pservent->s_name, m_pservent->s_proto); wsprintf(m_lpszPort, “%d”, ntohs(m_pservent->s_port)); lstrcat(m_lpszMessage, m_lpszPort); MessageBox(m_lpszMessage, “Info”);
Slide 98: Chapter 6■ Conversion and Database Functions 101
} return 0L; }
Summary
This chapter examined the many conversion and database functions provided by WinSock. There are more such functions but the most commonly used ones were presented here. Chapter 8, “Sample Applications,” contains a complete application that uses several of these functions. The next chapter discusses the functions necessary for creating a socket, connecting through sockets, and sending data back and forth through a socket. With that knowledge, you’ll be ready to write fully functional WinSock applications.
Slide 99: Chapter 7■ Socket Functions 103
7
Socket Functions Socket Functions
Slide 100: 104
Part II ■ Basics of WinSock Programming
The preceding two chapters show how to initialize the WinSock library and how to resolve host names and services. This chapter discusses the remaining WinSock functions necessary to make a truly useful networked application. Among these functions
are the following: a connection, data.
socket()
the end-point a name,
send()
to create an end-point of communication,
bind()
listen()
to listen for incoming connections,
to send data, and
recv()
accept()
to accept
to receive
to give
and
sendto()
and
recvfrom()
Figure 7.1 shows the flow of WinSock function calls for a client and server using TCP. Figure 7.2 shows a similar flow of WinSock function calls, but this time for a client and server using UDP.
FIGURE 7.1.
Client/server WinSock function flow using TCP.
Server
socket() Create the Socket bind() Give the Socket a Name
Client
listen() Listen for Connections from Clients
Wait for Connections from Clients
socket() Create the Socket connect() Connect to the Server
accept() Accepting the connection causes a new socket to be created while the original socket continues to wait for new connections send() / recv() Send and Receive Data send() / recv() Send and Receive Data
closesocket() Close the Connection
closesocket() Close the Connection
Slide 101: Chapter 7
FIGURE 7.2.
■ Socket Functions 105
Client/server WinSock function flow using UDP.
Client Server
socket() Create the Socket socket() Create the Socket
bind() Give the Socket a Name sendto() / recvfrom() Send and Receive Data
sendto() / recvfrom() Send and Receive Data
closesocket() Close the Connection
closesocket() Close the Connection
Creating an End-Point of Communication
The socket() function creates an end-point of communication called a socket. Its function prototype is as follows:
SOCKET PASCAL FAR socket(int af, int type, int protocol);
specifies the address family this socket uses. WinSock 1.1 supports only the AF_INET, or Internet address family format. type is the type specification for the socket. For most applications, this value is either SOCK_STREAM, for a connection-oriented byte stream, or SOCK_DGRAM, for connectionless datagram service. protocol is the particular protocol to use and is usually set to 0 (zero), which lets socket() use a default value. The protocol can be defaulted because the address family (af) and socket type (type) combination already uniquely describe a socket’s protocol. If the family is AF_INET and the socket type is SOCK_DGRAM, the protocol must be UDP. Likewise, if the family is AF_INET and the socket type is SOCK_STREAM, the protocol must be TCP.
af
On success,
and
socket()
returns a socket descriptor. On failure,
INVALID_SOCKET
is returned
WSAGetLastError()
error values include the following: WSANOTINITIALIZED if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSAEAFNOSUPPORT if the address family specified by af isn’t supported; WSAEINPROGRESS if a blocking WinSock operation is currently in progress; WSAEMFILE if there are no more free socket descriptors; WSAENOBUFS if no buffer space can be created; WSAEPROTONOSUPPORT if the protocol specified by protocol isn’t supported; WSAEPROTOTYPE if the protocol is the wrong type for this socket; or WSAESOCKTNOSUPPORT if the socket type specified by type isn’t supported in the address family specified by af.
should be called to find out the reason for the error. Possible
Slide 102: 106
Part II ■ Basics of WinSock Programming
CAUTION
Several of the preceding error messages returned by WSAGetLastError() make reference to unsupported address families, socket types, or protocols. These parameters have several interdependencies that, if not arranged properly, can result in error. For example, a socket with the AF_INET address family, SOCK_STREAM type, and UDP protocol is impossible because the UDP protocol can’t support a byte stream. This book uses two basic sockets. Both have the AF_INET address family specifier. One socket has type SOCK_STREAM, and the other has type SOCK_DGRAM. The protocol is left as 0 (zero) to let the socket() function use the default. It figures out this default by examining the address family and socket type. AF_INET and SOCK_STREAM default to TCP. AF_INET and SOCK_DGRAM default to UDP.
Example Call to socket() The following code sample shows a call to the socket() function to create a stream socket:
SOCKET s; char lpszMessage[100]; // socket descriptor // informational message
s = socket(AF_INET, SOCK_STREAM, 0); if (s == INVALID_SOCKET) wsprintf(lpszMessage, “socket() generated error WSAGetLastError()); else lstrcpy(lpszMessage, “socket() succeeded”); MessageBox(NULL, lpszMessage, “Info”, MB_OK);
%d”,
Notice that the protocol field was set to 0 (zero) to allow socket() to use a default value generated from the address family and socket type combination.
Stream Versus Datagram A socket, generally speaking, is of the stream or datagram variety and has either SOCK_STREAM or SOCK_DGRAM, respectively, as its type specifier in the call to socket(). You have to make a choice about which type is more appropriate for your application.
The stream socket supports a connection-oriented, reliable byte stream. Data is guaranteed to arrive in the order it was sent and without any duplication. The stream socket sees the data flow as a continuous, bidirectional stream of bytes with no record boundaries.
Slide 103: Chapter 7■ Socket Functions 107
The datagram socket supports unconnected, unreliable packet transmission. Data may not arrive in the order it was sent, it may be duplicated, or it may not arrive at all. The datagram socket sees the data flow as a sequence of packets with record boundaries preserved.
Data Flow Behavior
A simple example illustrates the difference between stream and datagram data flow. Suppose that the following two strings were sent to a receiving socket using two separate calls to send() or sendto(): “This book is about” and “programming with WinSock”. For a stream socket, created with type SOCK_STREAM, the application doesn’t see these two strings as two separate records; record boundaries are lost. If the receiving socket does a recv() on this socket with a buffer size of ten bytes, the first recv() returns “This book “, the
second returns “is about the fifth returns “ock”.
p”,
the third returns
“rogramming”,
the fourth returns
“ with WinS”,
and
For a datagram socket, created with type
recvfrom() “This book
SOCK_DGRAM,
as two separate records; record boundaries are preserved. If the receiving socket does a and the second returns these strings is lost.
“
the application sees these two strings
recvfrom()
on this socket with a buffer size of 10 bytes, the first
“programmin”.
The remaining portion of each of
returns
From this example, you can see that streams and datagrams are appropriate for different tasks. For something inherently byte-stream oriented, such as a terminal emulator, streams are more appropriate. For something inherently record oriented, such as database record retrieval, datagrams may be more appropriate. But there is a trade-off in either scenario. The use of datagrams means that you may have to include some sort of ack/nack communication (acknowledgment/negative acknowledgment) in your application because the protocol does not do this for you. On the other hand, the use of streams means that you may have to keep track of record boundaries in your application.
Stream-Oriented Client/Server Communication
Stream-oriented, client/server communication, using socket type SOCK_STREAM, is more complicated than datagram-oriented communication; both the server and client applications must perform several extra steps that are unnecessary using datagrams. By explaining the more involved stream scenario first, I hope to ease the understanding of the datagram scenario presented later.
Slide 104: 108
Part II ■ Basics of WinSock Programming
How a Server Accepts a Connection from a Client In a server, the stream socket is bound to a well-known name. Then the server application listens for connections on that socket. When a client connects to the server, the server accepts the new connection. At this point, data transfer begins.
Giving the Socket a Name
Creating a socket does little more than allocate a socket descriptor for your application from the list of available descriptors. To make it useful, you need to give the socket a name. The bind() function does this. Its prototype is as follows:
int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR *addr, int namelen);
is the socket descriptor returned by socket(). addr is a pointer to the address, or name, to assign to the socket. namelen is the length of the structure addr points to.
s
On success,
bind ( )
should be called to find out the reason for the error. Possible error values include WSANOTINITIALIZED if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSAEADDRINUSE if the address specified by addr is already in use; WSAEFAULT if namelen is too small; WSAEINPROGRESS if a blocking WinSock call is currently in progress; WSAEAFNOSUPORT if the address family specified in the structure addr points to isn’t supported by this protocol; WSAEINVAL if the socket is already bound to an address; WSAENOBUFS if no buffer space can be created; or WSAENOTSOCK if the socket descriptor s is invalid.
WSAGetLastError()
returns 0 (zero). On failure,
SOCKET _ERROR
is returned and
The sockaddr structure is defined as follows:
struct sockaddr { u_short sa_family; char sa_data[14]; }; // address family // up to 14 bytes of direct address
The format of sa_data depends on the address family. In WinSock 1.1, only the Internet addressing format is supported. For this reason, the sockaddr_in structure is provided.
Use it rather than ture follows:
sockaddr
when calling
bind().
The format of the
sockaddr_in
struc-
struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; };
// // // //
address family service port Internet address filler
Slide 105: Chapter 7■ Socket Functions 109
for WinSock 1.1; this value matches the af argument in the call to socket(). sin_port is the port number, in network byte order, on which your server application provides its service. sin_addr is an in_addr structure that contains the IP address, in network byte order, on which your server will listen for connections. The in_addr structure is used to provide three different ways of examining the IP address: as four bytes, as two shorts, or as one long. The format of in_addr is as follows:
sin_family AF_INET
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; #define s_addr #define s_host #define s_net #define s_imp #define s_impno #define s_lh }; S_un.S_addr // can be used for most tcp & ip code S_un.S_un_b.s_b2 // host on imp S_un.S_un_b.s_b1 // network S_un.S_un_w.s_w2 // imp S_un.S_un_b.s_b4 // imp # S_un.S_un_b.s_b3 // logical host
must be
Notice the definition of s_addr. This will be the most common way of accessing the IP address, as an unsigned long in network byte order, because the database and conversion routines manipulate the IP address similarly. The remaining field of the sockaddr_in structure, sin_zero, is provided as a filler to buffer the remaining eight bytes that are allotted for an address (2 byte port + 4 byte IP address + 8 byte filler = 14 bytes total). Following is an example of using bind():
SOCKET s; char lpszMessage[100]; SOCKADDR_IN addr; // socket descriptor // informational message // Internet address
// create a stream socket s = socket(AF_INET, SOCK_STREAM, 0); if (s != INVALID_SOCKET) { // fill out the socket’s address information addr.sin_family = AF_INET; addr.sin_port = htons(1050); addr.sin_addr.s_addr = htonl(INADDR_ANY); // bind the socket to its address if (bind(s, (LPSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR) { wsprintf(lpszMessage, “ bind() generated error %d”, WSAGetLastError()); MessageBox(NULL, lpszMessage, “Info”, MB_OK); } else }
Slide 106: 110
Part II ■ Basics of WinSock Programming
Notice the assignment of
addr.sin_port
application listens for connections on port 1050. You also could use the
to
htons(1050).
This tells you that this server
getservbyname()
or WSAAsyncGetServByName() functions, as in the following example, to assign a port number:
LPSERVENT pservent; // pointer to service entry structure pservent = getservbyname(“daytime”, “tcp”); if (pservent != NULL) addr.sin_port = pservent->s_port; // already in network byte order
The next line in the sample is the assignment of addr.sin_addr.s_addr, the actual IP address. In this sample, the IP address is set to htonl(INADDR_ANY). This tells you that this server listens for connections on any network to which the host is connected.
FIGURE 7.3.
Server computer on two networks.
Client Network 1
Client
Server Network 2
Client
Client
In most server applications, the name bound to a socket has its IP address set to INADDR_ANY. This tells WinSock that you are willing to accept requests from any network to which the host is connected. The only time this is an issue is if the host running your server application has more than one IP address assigned to it. For example, this might be the case if the host has two Ethernet cards as shown in Figure 7.3. One Ethernet card is assigned one IP address (say 166.78.16.200) and the other Ethernet card has a
Slide 107: Chapter 7 Functions
■ Socket 111
different IP address (say 166.78.16.201). In this case, you may want to place a limit whereby clients can connect through only one IP address or the other, but not both. In this case you do something like the following:
addr.sin_addr.s_addr = inet_addr(“166.78.16.200”);
One last thing to note about this code sample is the call to
address must be cast to a long pointer to a sockaddr_in structure (SOCKADDR_IN).
Listen for Connections
sockaddr
parameter’s structure (LPSOCKADDR) because addr is a
bind()
itself. The
addr
Now that you can name a socket, you can put it to real use by listening for connections to that socket from client applications. The listen() function does this. Its prototype is as follows:
int PASCAL FAR listen(SOCKET s, int backlog);
is the socket descriptor on which to listen for connections. backlog is a count of pending connections that may be queued up before the server application processes them. backlog must be between one and five, inclusively.
s
On success,
listen()
should be called to find out the reason for the error. Possible error values include: WSANOTINITIALIZED if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSAEADDRINUSE if the address specified by addr is already in use; WSAEINPROGRESS if a blocking WinSock call is currently in progress; WSAEINVAL if the socket hasn’t been bound to an address using bind() or the socket is already connected; WSAEISCONN if the socket is already connected; WSAEMFILE if there are no more free file descriptors; WSAENOBUFS if no buffer space can be created; WSAENOTSOCK if the socket descriptor s is invalid; or WSAEOPNOTSUPP if the socket s doesn’t support the listen() operation (this could happen if socket s is of type SOCK_DGRAM).
WSAGetLastError()
returns 0 (zero). On failure,
SOCKET_ERROR
is returned and
NOTE
Backlog acts as a safety net by preventing the WinSock layer from allocating lots of resources. Suppose that your server application is very slow and can process client connections only once every five seconds. Suppose also that the socket has a backlog of three. If four clients try to connect to the server socket within five seconds, the fourth client attempt will generate a WSAECONNREFUSED error at the client side.
Slide 108: 112
Part II ■ Basics of WinSock Programming
Following is a code snippet showing the use of the listen() function:
SOCKET s; char lpszMessage[100]; SOCKADDR_IN addr; // socket descriptor // informational message // Internet address
// create a stream socket s = socket(AF_INET, SOCK_STREAM, 0); if (s != INVALID_SOCKET) { // fill out the socket’s address information addr.sin_family = AF_INET; addr.sin_port = htons(1050); addr.sin_addr.s_addr = htonl(INADDR_ANY); // bind the socket to its address if (bind(s, (LPSOCKADDR)&addr, sizeof(addr)) != SOCKET_ERROR) { // listen for connections (queueing up to three) if (listen(s, 3) == SOCKET_ERROR) { wsprintf(lpszMessage, “listen() generated error %d”, WSAGetLastError()); MessageBox(lpszMessage, “Info”); } else } }
Accept a Connection
Now you have a named socket listening for connections. The next thing for a server to do is accept a connection from a client. The accept() function does this. Its prototype is as follows:
SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);
is the socket descriptor on which to accept a connection request. addr is a pointer to a sockaddr structure that will accept the address of the connecting client. You may pass NULL for this parameter or a pointer to a sockaddr_in structure, as in the bind() example. addrlen is a pointer that will accept the actual length of the address structure in addr. If addr is NULL, addrlen can also be NULL; otherwise, the value pointed to by addrlen should initially contain the length of the structure pointed to by addr. This is more clearly explained in the following examples.
s
On success, accept() returns a socket descriptor. This returned socket descriptor is the one used for communication with the client; the original socket s passed in the call to accept() remains available to accept additional connections. On failure, INVALID_SOCKET
is returned, and WSAGetLastError() should be called to find out the reason for the error. Possible error values include: WSANOTINITIALIZED if WSAStartup() wasn’t called
Slide 109: Chapter 7 Functions
■ Socket 113
successfully; WSAENETDOWN if the network subsystem is failing; WSAEFAULT if addrlen is too small; WSAEINTR if the blocking call was canceled; WSAEINPROGRESS if a blocking WinSock call is currently in progress; WSAEINVAL if listen() wasn’t called before accept(); WSAEFILE if the queue is empty and there are no descriptors available; WSAENOBUFS if no buffer space can be created; WSAENOTSOCK if the socket descriptor s is invalid; WSAEOPNOTSUPP if the socket s does not support the accept() operation (this could happen if socket s is of type SOCK_DGRAM); or WSAEWOULDBLOCK if the socket is marked as nonblocking and no connections are present to be accepted. Following is a code snippet that shows the accept() call in use:
SOCKET s; SOCKET clientS; char lpszMessage[100]; SOCKADDR_IN addr; SOCKADDR_IN clientAddr; IN_ADDR clientIn; int nClientAddrLen; // // // // // // socket descriptor client socket descriptor informational message Internet address Internet address IP address
// create a stream socket s = socket(AF_INET, SOCK_STREAM, 0); if (s != INVALID_SOCKET) { // fill out the socket’s address information addr.sin_family = AF_INET; addr.sin_port = htons(1050); addr.sin_addr.s_addr = htonl(INADDR_ANY); // bind the socket to its address if (bind(s, (LPSOCKADDR)&addr, sizeof(addr)) != SOCKET_ERROR) { // listen for connections (queueing up to three) if (listen(s, 3) != SOCKET_ERROR) { // set the size of the client address structure nClientAddrLen = sizeof(clientAddr); // accept a connection clientS = accept(s, (LPSOCKADDR)&clientAddr, &nClientAddrLen); if (clientS == INVALID_SOCKET) { wsprintf(lpszMessage, “ accept() generated error %d”, WSAGetLastError()); MessageBox(lpszMessage, “Info”); } else { // copy the four byte IP address into an IP address structure memcpy(&clientIn, &clientAddr.sin_addr.s_addr, 4); // print an informational message wsprintf(lpszMessage, “accept() ok: client IP address is %s, port is %d”, inet_ntoa(clientIn), ntohs(clientAddr.sin_port));
Slide 110: 114
Part II ■ Basics of WinSock Programming
MessageBox(lpszMessage, “Info”);
} } } }
In this example, the accept() function is called with a pointer to a sockaddr_in structure and a pointer to an integer containing the length of the sockaddr_in structure. When the accept() function successfully returns, the sockaddr_in structure contains the address information of the connecting client. As noted previously, you can pass NULL for these two parameters and no client information will be conveyed to the accepting server application. The alternative method of discovering details about the connecting client is to use the getpeername() function. Its prototype is as follows:
int PASCAL FAR getpeername(SOCKET s, struct sockaddr FAR *name, int FAR * namelen);
Its parameters are the same as those of accept() except that the socket s is the socket descriptor that’s returned by accept(), not the socket descriptor that’s used to listen for connections. The other difference is that the function prototype uses name and namelen rather than addr and addrlen, respectively. The inconsistent use of the terms name and address is a problem with some WinSock functions.
On success,
getpeername()
should be called to find out the reason for the error. Possible error values include: WSANOTINITIALIZED if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSAEFAULT if namelen is too small; WSAEINPROGRESS if a blocking WinSock call is currently in progress; WSAENOTSOCK if the socket descriptor s is invalid; or WSAENOTCONN if the socket isn’t connected to a client.
To use this function you do something like this:
// accept a connection clientS = accept(s, NULL, NULL); if (clientS == INVALID_SOCKET) { wsprintf(lpszMessage, “accept() generated error %d”, WSAGetLastError()); MessageBox(lpszMessage, “Info”); } else { if (getpeername(clientS, (LPSOCKADDR)&clientAddr, &nClientAddrLen)) == SOCKET_ERROR) { wsprintf(lpszMessage, “getpeername() generated error %d”, WSAGetLastError()); MessageBox(lpszMessage, “Info”);
returns 0 (zero). On error,
SOCKET_ERROR
is returned and WSAGetLastError()
Slide 111: Chapter 7 Functions
■ Socket 115
} else { // copy the four byte IP address into an IP address structure memcpy(&clientIn, &clientAddr.sin_addr.s_addr, 4); // print an informational message wsprintf(lpszMessage, “client IP address is %s, port is %d”, inet_ntoa(clientIn), ntohs(clientAddr.sin_port)); MessageBox(lpszMessage, “Info”);
} }
What If No Clients Are Trying to Connect?
In the previous example no mention was made of what the outcome of the code segment is if there is no client trying to connect to the server when the server executes the accept() function. In this scenario, the server application blocks, waiting for a client connection. This is similar to what happens with the getXbyY functions discussed in an earlier chapter. And just as there is a work-around for the getXbyY problem, using the WSAAsyncGetXByY functions, there is an answer to the accept() problem as well. The solution lies in using nonblocking sockets. By default, a socket created with socket() is in blocking mode. There are two methods for putting the socket into nonblocking mode.
Doing It the Berkeley Way
The Berkeley method of using nonblocking sockets involves two functions: ioctl() and select(). ioctl() is the UNIX function to perform input/output control on a file descriptor or socket. Because a WinSock socket descriptor may not be a true operating system file
descriptor, ioctl() can’t be used, so ioctlsocket() is provided instead. determine the status of one or more sockets.
// put socket s into nonblocking mode u_long ulCmdArg = 1; // 1 for nonblocking, 0 for blocking ioctlsocket(s, FIONBIO, &ulCmdArg);
select()
is used to
The use of ioctlsocket() to convert a socket to nonblocking mode looks like this:
Once a socket is in its nonblocking mode, calling a normally blocking function simply returns WSAEWOULDBLOCK if the function can’t immediately complete, as in the following example.
// put socket s into nonblocking mode u_long ulCmdArg = 1; // 1 for nonblocking, 0 for blocking ioctlsocket(s, FIONBIO, &ulCmdArg);
Slide 112: 116
Part II ■ Basics of WinSock Programming
SOCKET clientS; clientS = accept(s, NULL, NULL); if (clientS == INVALID_SOCKET) { int nError = WSAGetLastError(); // if there is no client waiting to connect to this server, // nError will be WSAEWOULDBLOCK }
Your server application could simply call accept() periodically until the call succeeded, or you could use the select() call to query the status of the socket. The select() function checks the readability, writeability, and exception status of one or more sockets. Even the most die-hard UNIX or Berkeley sockets supporter probably agrees that the use of select() is fairly unintuitive. As one example, if select() tells you that a socket is readable, that could mean the socket is ready to connect to a client, or it could mean there is some data sent by a client ready to be read. To use select() in a Windows program would require that it be called periodically, as the result of a timer or every time through the application’s message loop. No matter what, its use doesn’t fit well within the message-driven architecture of Windows. Thankfully, WinSock provides a more “Windows native” method of performing nonblocking socket operations.
Doing It the Windows Way
WinSock provides a function called WSAAsyncSelect() to solve the problem of blocking socket function calls. It is a much more natural solution to the problem than using
and select(). It works by sending a Windows message to notify a window of a socket event. Its prototype is as follows:
ioctlsocket()
int PASCAL FAR WSAAsyncSelect(SOCKET s, HWND hWnd, u_int wMsg, long lEvent);
is the socket descriptor for which event notification is required. hWnd is the Window handle that should receive a message when an event occurs on the socket. wMsg is the message to be received by hWnd when a socket event occurs on socket s. It is usually a userdefined message (WM_USER + n). lEvent is a bitmask that specifies the events in which the application is interested.
s WSAAsyncSelect()
returns 0 (zero) on success and
SOCKET_ERROR
on failure. On failure,
WSAGetLastError() WSANOTINITIALIZED
if WSAStartup() wasn’t called successfully; WSAENETDOWN if the network subsystem is failing; WSAEINPROGRESS if a blocking WinSock call is currently in progress; or WSAEINVAL if one of the parameters is invalid.
should be called. Possible error values include the following:
Slide 113: Chapter 7 Functions
■ Socket 117
TIP
Calling WSAAsyncSelect() automatically puts the socket into a nonblocking state. There is no need to use ioctlsocket() to do this first.
is capable of monitoring several socket events. Table 7.1 lists these events, which are represented by lEvent in the function prototype.
WSAAsyncSelect()
Table 7.1. WSAAsyncSelect() Events.
Event
FD_READ FD_WRITE FD_OOB FD_ACCEPT FD_CONNECT FD_CLOSE
Meaning
Socket ready for reading Socket ready for writing Out-of-band data ready for reading on socket Socket ready for accepting a new incoming connection Connection on socket completed Connection on socket has been closed
The lEvent parameter is constructed by doing a logical OR on the events in which you’re interested. For example, the following code will post a WM_USER + 1 message to the window handle specified by hWnd when there is an incoming connection to socket s or when socket s has data to be read:
long lEvent = FD_ACCEPT | FD_READ; WSAAsyncSelect(s, hWnd, WM_USER + 1, lEvent);
TIP
Issuing WSAAsyncSelect() for a socket cancels any previous the same socket. You can’t do separate calls like this:
WSAAsyncSelect(s, hWnd, WM_USER + 1, FD_ACCEPT); WSAAsyncSelect(s, hWnd, WM_USER + 1, FD_READ);
WSAAsyncSelect() for
The preceding code will ignore FD_ACCEPT events; only FD_READ events will be posted as message WM_USER + 1. You also can’t use separate calls to WSAAsyncSelect() to assign different messages to the different events for a specific socket. For example, the following code is incorrect:
Slide 114: 118
Part II ■ Basics of WinSock Programming
WSAAsyncSelect(s, hWnd, WM_USER + 1, FD_ACCEPT); WSAAsyncSelect(s, hWnd, WM_USER + 2, FD_READ);
The FD_ACCEPT event will never generate message (WM_USER + 2).
WM_USER + 1.
Only FD_READ will generate a
To cancel all event notifications, call WSAAsyncSelect() with wMsg and lEvent set to 0 (zero), as in the following:
WSAAsyncSelect(s, hWnd, 0, 0L);
You can see that there are six events you can express interest in. This section is about how a server accepts a connection from a client, so that’s the area of WSAAsyncSelect() on which I’ll concentrate. Please note that other sections of this chapter use WSAAsyncSelect() to monitor the sending and receiving of data, as well as other events. The basic use of WSAAsyncSelect() is the same for all events, so I’ll give a full description of an appropriate message handler here. The FD_ACCEPT event is generated whenever a listening socket has a client wishing to make a connection. An example of calling WSAAsyncSelect() from within a Visual C++ MFC program follows.
BOOL CServerWindow::StartListening() { // m_s is the socket descriptor which is a member // variable of the CServerWindow class // m_s has already been created and bound to a name // listen for connections if (listen(m_s, 3) == SOCKET_ERROR) return FALSE; // get asycnchronous event notification of accept // to this object’s window (m_hWnd) if (WSAAsyncSelect(m_s, m_hWnd, WM_USER + 1, FD_ACCEPT) == SOCKET_ERROR) return FALSE; return TRUE; }
You also need a member function in CServerWindow to handle the generated when a client connection is requested of socket m_s:
BEGIN_MESSAGE_MAP(CServerWindow, CFrameWnd) //{{AFX_MSG_MAP(CServerWindow) ON_MESSAGE(WM_USER + 1, OnAsyncSelect)
WM_USER + 1
message that’s
Slide 115: Chapter 7 Functions
■ Socket 119
//}}AFX_MSG_MAP END_MESSAGE_MAP() LONG CServerWindow::OnAsyncSelect(WPARAM wParam, LPARAM lParam) { // wParam is the socket descriptor // lParam is a status or error indicator // check for an error if (WSAGETSELECTERROR(lParam) != 0) return 0L; // what event are we being notified of? if (WSAGETSELECTEVENT(lParam) == FD_ACCEPT) { // m_clientS is defined as SOCKET in the CServerWindow class declaration m_clientS = accept(m_s, NULL, NULL); if (m_clientS == INVALID_SOCKET) { int nError = WSAGetLastError(); if (nError == WSAEWOULDBLOCK) // There really isn’t a client ready to connect. // This error should never occur for the FD_ACCEPT event // so it should be treated just like this event // notification function hadn’t been called. ; else // some other error ; } } }
Notice the call to WSAGETSELECTERROR. This is a macro provided in WINSOCK.H, which is called to determine whether there is an error in the asynchronous event. It returns 0 (zero) on success and an error value on failure. For the FD_ACCEPT event notification message, the error could be WSAENETDOWN, which means the network subsystem is down.
Also note that the WSAGETSELECTEVENT macro is called to determine the event that triggered this message handler even though the only way the CServerWindow::OnAsyncSelect() function is called is if the FD_ACCEPT event occurs. This macro
will be used in later sample programs where the class’s member function handles several WinSock events for a particular socket.
If for some reason the CServerWindow::OnAsyncSelect() function is called with the FD_ACCEPT event but there is no client trying to connect to this server application,
accept()
returns INVALID_SOCKET. Calling WSAGetLastError() will return WSAEWOULDBLOCK, which tells you that this function is set up for nonblocking mode and if it weren’t, it would block.
Slide 116: 120
Part II ■ Basics of WinSock Programming
How Clients Connect to Servers You have now seen how a server creates a socket, gives it a name, listens for connections, and accepts connections from clients. This section explains what a client does to connect to a server application.
Giving the Socket a Default Name
After the call to socket(), the bind() function may be used to give the socket a name. This step might be necessary because calling socket() simply reserves a socket descriptor. By naming the socket with bind(), you give the socket a port, out of which it communicates with a server. The use of bind() to give a client socket a default name is shown here:
SOCKET s; char lpszMessage[100]; SOCKADDR_IN addr; // socket descriptor // informational message // Internet address
// create a stream socket s = socket(AF_INET, SOCK_STREAM, 0); if (s != INVALID_SOCKET) { // fill out the socket’s address information addr.sin_family = AF_INET; addr.sin_port = 0; // let WinSock assign a port addr.sin_addr.s_addr = htonl(INADDR_ANY); // bind the socket to its address if (bind(s, (LPSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR) { wsprintf(lpszMessage, “ bind() generated error %d”, WSAGetLastError()); MessageBox(NULL, lpszMessage, “Info”, MB_OK); } else }
Note that addr.sin_port is assigned 0 (zero). This allows WinSock to assign any unused port it sees fit. This is in contrast to a server socket, which needs to listen for connections on a specific port. It really doesn’t matter which port a client uses. But if you do want to know which port a socket was assigned, you can use the getsockname() function. Its function prototype is as follows:
int PASCAL FAR getsockname(SOCKET s, struct sockaddr FAR *name, int FAR * namelen);
Slide 117: Chapter 7 Functions
■ Socket 121
It is used much like
getpeername().
An example call to
getsockname()
is listed here:
SOCKET s; // socket descriptor char lpszMessage[100]; // informational message SOCKADDR_IN addr; // Internet address used by bind() SOCKADDR_IN addrAssigned; // Internet address assigned by bind() int nAddrAssignedLen = sizeof(addrAssigned); // create a stream socket s = socket(AF_INET, SOCK_STREAM, 0); if (s != INVALID_SOCKET) { // fill out the socket’s address information addr.sin_family = AF_INET; addr.sin_port = 0; // let WinSock assign a port addr.sin_addr.s_addr = htonl(INADDR_ANY); // bind the socket to its address if (bind(s, (LPSOCKADDR)&addr, sizeof(addr)) != SOCKET_ERROR) { // find out what port was assigned by WinSock if (getsockname(s, (LPSOCKADDR)&addrAssigned, &nAddrAssignedLen) == 0) // now addrAssigned.sin_port contains the port number // that WinSock assigned to this client port
} }
In this example, as in the server example, the socket’s IP address is set to INADDR_ANY. This means that the socket can use any network interface the computer has. This use is fine for most instances. It may be necessary to specify a particular address if the computer on which the client application runs is connected to more than one network through more than one network interface. Figure 7.4 shows one possible scenario. The client computer in Figure 7.4 has two IP addresses; suppose that they are 166.12.34.101 and 166.12.34.102. If you want the client limited to going through only one network interface, you can do the following instead of using INADDR_ANY:
addr.sin_addr.s_addr = inet_addr(“166.12.34.101”);
Connecting to a Server
A client connects to a server using the connect() function. Its prototype is as follows:
int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR *name, int namelen);
Slide 118: 122
Part II ■ Basics of WinSock Programming
FIGURE 7.4.
Client computer on two networks.
Server 1 Network 1
Client Network 2
Server 2
s
is the socket to use for the connection.
name
is the address of the server to connect to. It is
namelen
always a sockaddr_in Internet address structure for WinSock 1.1. parameter. On success,
connect() WSAGetLastError()
is the length of the
SOCKET_ERROR
name
should be called to find out the cause of the error. Possible error values include the following: WSANOTINITIALIZED if WinSock hasn’t been successfully initialized with a call to WSAStartup(); WSAENETDOWN if the network subsystem is failing; WSAEADDRINUSE if the address is already in use; WSAEINTR if the blocking call was canceled with WSACancelBlockingCall(); WSAEINPROGRESS if a blocking WinSock call is in progress; WSAEADDRNOTAVAIL if the address specified by addr isn’t available; WSAENOSUPPORT
returns 0 (zero). On failure, it returns
and
if the addresses in the specified family can’t be used with this socket; WSAECONNREFUSED if the server forcefully refused the connection attempt; WSAEDESTADDREQ if name isn’t specified; WSAEFAULT if namelen is invalid; WSAEINVAL if socket s isn’t bound to an address; WSAEISCONN if socket s is already connected; WSAEMFILE if there are no free file descriptors; WSAENETUNREACH if the network can’t be reached from this host at this time; WSAENOBUFS if no buffer space is available; WSAENOTSOCK if socket s isn’t a valid socket
descriptor; WSAETIMEDOUT if the attempt to connect timed out; or s is marked as nonblocking and this connection can’t be completed immediately.
WSAEWOULDBLOCK
if socket
Slide 119: Chapter 7■ Socket Functions 123
An example of using
SOCKET s; char lpszMessage[100]; SOCKADDR_IN addr; SOCKADDR_IN addrServer;
connect()
is shown here:
// socket descriptor // informational message // Internet address used by bind()
// s is a socket that has been bound to a default address
// fill out the address information of the server application // (the server has IP address 166.34.56.100 and listens on port 1050) addrServer.sin_family = AF_INET; addrServer.sin_port = htons(1050); addrServer.sin_addr.s_addr = inet_addr(“166.34.56.100”); if (connect(s, (LPSOCKADDR)&addrServer, sizeof(addrServer)) == SOCKET_ERROR) // error
What if the Server Isn’t Listening?
The
connect()
server to which the client application is trying to connect isn’t listening for connections? Using a blocking socket, the connect() function won’t return until the server accepts the connection. If the server never accepts the connection request, the client application remains hung until the WSAETIMEDOUT error is generated; the client’s message loop never executes. To battle this problem, the WSAAsyncSelect() function is used. The event of importance in this scenario is FD_CONNECT. The following example code shows how WSAAsyncSelect() might be called in a Visual C++ MFC program:
BOOL CClientWindow::DoConnect() { // m_s is the socket descriptor which is a member // variable of the CClientWindow class // m_s has already been created and bound to a default name // fill out the address information of the server application // (the server has IP address 166.34.56.100 and listens on port 1050) m_addrServer.sin_family = AF_INET; m_addrServer.sin_port = htons(1050); m_addrServer.sin_addr.s_addr = inet_addr(“166.34.56.100”); // get asycnchronous event notification of // connect to this object’s window (m_hWnd) if (WSAAsyncSelect(m_s, m_hWnd, WM_USER + 1, FD_CONNECT) == SOCKET_ERROR) { // error ... }
call suffers from the same problem as
accept().
What happens if the
Slide 120: 124
Part II ■ Basics of WinSock Programming
// make the connection if (connect(m_s, (LPSOCKADDR)&m_addrServer, SOCKET_ERROR) { int nError = WSAGetLastError(); if (nError == WSAEWOULDBLOCK) { // this is ok...just wait for async notice } else { // some other error } } }
sizeof(m_addrServer))
==
You also need a member function in CClientWindow to handle the WM_USER generated when a client connection is completed:
BEGIN_MESSAGE_MAP(CClientWindow, //{{AFX_MSG_MAP(CClientWindow) CFrameWnd)
+1
message that’s
ON_MESSAGE(WM_USER + 1, OnAsyncSelect) //}}AFX_MSG_MAP END_MESSAGE_MAP() LONG CClientWindow::OnAsyncSelect(WPARAM wParam, LPARAM lParam) { // wParam is the socket descriptor // lParam is a status or error indicator // check for an error if (WSAGETSELECTERROR(lParam) != 0) return 0L; // what event are we being notified of? if (WSAGETSELECTEVENT(lParam) == FD_CONNECT) { // you now know that the m_s socket is connected to the server } }
This message handler follows the same format as the CServerWindow::OnAsyncSelect() function. First, check for an error using the WSAGETSELECTERROR macro. Next, use WSAGETSELECTEVENT to determine which event caused this message handle to be called. The FD_CONNECT event tells us the client socket is connected to the server.
Sending and Receiving Data on a Stream Socket
You have now seen how a server accepts a connection and how a client makes a connection. Once the client and server sockets are connected, data can be sent and received.
Slide 121: Chapter 7■ Socket Functions 125
The send() and recv() functions are used to send and receive stream data, respectively. These two functions can also operate on datagrams, but this discussion is limited to their use with respect to stream sockets.
Sending Data
Sending data over a stream socket involves the use of WinSock’s prototype is as follows:
int PASCAL FAR send(SOCKET s, const char FAR * buf, int len, int flags);
send()
function. Its
is the connected socket to send data over. buf is a pointer to a buffer containing the data to be transmitted. len is the number of bytes in buf. flags specifies the way in which the send() call is made. flags can be any of the following logically ORed together: 0 (zero) for no special send
s
options, MSG_DONTROUTE if the data shouldn’t be subject to routing, and MSG_OOB to send out-ofband data. On success, send() returns the number of bytes sent. This could range from 1 (one) to len. On failure, SOCKET_ERROR is returned and WSAGetLastError() specifies the following: WSANOTINITIALIZED if WinSock wasn’t initialized with WSAStartup(); WSAENETDOWN if the network subsystem is failing; WSAEACCESS if the address is a broadcast address but
the appropriate flag wasn’t set;
WSAEINTR
if a blocking call is in progress; WSAEFAULT if buf isn’t in a valid part of the user address space; WSAENETRESET if the connection needs to be reset; WSAENOBUFS if there is a internal WinSock buffer deadlock; WSAENOTCONN if the socket s isn’t connected; WSAENOTSOCK if the socket s isn’t a valid socket descriptor; WSAEOPNOTSUPP if MSG_OOB was specified by flags but the socket s isn’t a stream socket;
WSACancelBlockingCall(); WSAEINPROGRESS WSAESHUTDOWN
if the blocking call was canceled with
as nonblocking and the
if the socket
s
has been shutdown;
WSAEWOULDBLOCK s
send()
would block;
WSAEMSGSIZE
if socket
if the socket
s
s
is a datagram socket
WSAECONNABORTED WSAECONNRESET
is marked
and the buffer is too large;
WSAEINVAL
if the virtual circuit was aborted due to timeout or other failure; or virtual circuit was reset by the remote side.
NOTE
if the socket
hasn’t been bound;
if the
Out-of-band (OOB) data can be thought of as a logically independent transmission channel associated with each pair of connected stream sockets. Out-of-band data is delivered to the user independently of normal stream data.
Slide 122: 126
Part II ■ Basics of WinSock Programming
Unfortunately, there are two conflicting interpretations of out-of-band implementation. Internet RFC 793 introduced the concept of out-of-band data and RFC 1122 provided the host requirements. The problem arises because the Berkeley implementation of out-of-band data handling does not follow RFC 1122. Most TCP/IP stack providers document their implementation so that you know which they use: BSD or RFC 1122. Because of this difference of opinion, it is best not to use out-of-band data in your applications. Of course, if your application must interoperate with an existing client or server, perhaps running on a UNIX computer, you have no choice.
The following demonstrates a typical call to send():
SOCKET s; char pszBuf[100]; int nBufLen; int nBytesSent; int nError; // // // // // socket to communicate over buffer to send number of bytes in buffer to send bytes sent error status
// create, bind, and connect socket s ... lstrcpy(pszBuf, “Hello, World!”); nBufLen = lstrlen(pszBuf); nBytesSent = send(s, pszBuf, nBufLen, 0); if (nBytesSent == SOCKET_ERROR) { nError = WSAGetLastError(); } else { // nBytesSent is the number of bytes successfully sent }
The
send()
and getXbyY functions. If none of the data can be sent for some reason—because the receiving socket has too much incoming data spooled up, for example—send() blocks waiting for the receiver to read the spooled up data. While send() is blocking, the user loses control over your application. To prevent this problem, the WSAAsyncSelect() function is used to put the socket into nonblocking send mode. The event of interest in this scenario is FD_WRITE. An FD_WRITE message is posted when a socket is first connected
with
connect ( )
function has the same problem with blocking as the
accept(),
connect(),
and buffer space becomes available. Therefore, an application can assume that sends are possible starting from the first FD_WRITE message and lasting until a send returns WSAEWOULDBLOCK. After such a failure, the application will be notified that sends are again possible with a new FD_WRITE message.
WSAEWOULDBLOCK
or accepted with
accept ( )
, and then after a
send ( )
fails with
Slide 123: Chapter 7■ Socket Functions 127
A Visual C++ MFC example makes this more understandable. Following is a function to make a connection with a server. Note that this example is looking at this problem from the client’s perspective, but a server uses the same techniques. The following code is similar
to the
connect()
example except that it calls
WSAAsyncSelect()
with both
FD_CONNECT
and
FD_WRITE.
BOOL CClientWindow::DoConnect() { // m_s is the socket descriptor which is a member // variable of the CClientWindow class // m_s has already been created and bound to a default name // m_addrServer is the address of the server and has // already been assigned // get asycnchronous event notification of connect // and writeability to this object’s window (m_hWnd) if (WSAAsyncSelect(m_s, m_hWnd, WM_USER + 1, FD_CONNECT | FD_WRITE) == SOCKET_ERROR) { // error ... } // make the connection if (connect(m_s, (LPSOCKADDR)&m_addrServer, SOCKET_ERROR) { int nError = WSAGetLastError(); if (nError != WSAEWOULDBLOCK) { // a real error ... } } }
sizeof(m_addrServer))
==
You also need a member function in CClientWindow to handle the WM_USER + 1 message that’s generated when a client connection is completed and when the socket is writeable.
BEGIN_MESSAGE_MAP(CClientWindow, CFrameWnd) //{{AFX_MSG_MAP(CClientWindow) ON_MESSAGE(WM_USER + 1, OnAsyncSelect) //}}AFX_MSG_MAP END_MESSAGE_MAP() LONG CMainFrame::OnAsyncSelect(WPARAM { int nError; // int nBytesSent; // static char *pszBuf, *pszBufTmp; // static int nBufLen; // // check for an error if (WSAGETSELECTERROR(lParam) != 0) wParam, LPARAM lParam) error status bytes sent buffer to send length of buffer
Slide 124: 128
Part II ■ Basics of WinSock Programming
{ // ... return 0L; } // what event are we being notified of? switch (WSAGETSELECTEVENT(lParam)) { case FD_CONNECT: // client made a connection // allocate some buffer space and fill // it with useful data to send nBufLen = 5000; pszBuf = (char *)malloc(nBufLen); if (pszBuf == NULL) nBufLen = 0; else { // this temporary pointer will be used // to move through the buffer pszBufTmp = pszBuf; // fill the buffer with some useful data to send ... } break; // case FD_CONNECT case FD_WRITE: // client can send data now // is there any data to send? if (nBufLen > 0) { // send as many bytes as possible do { // send data in 100 byte chunks if (nBufLen < 100) nBytesSent = send(m_s, pszBufTmp, nBufLen, 0); else nBytesSent = send(m_s, pszBufTmp, 100, 0); if (nBytesSent == SOCKET_ERROR) { nError = WSAGetLastError(); if (nError == WSAEWOULDBLOCK) // ok, we’ll get another FD_WRITE eventually else // a real error ... } else { // advance the pointer and decrement the byte count pszBufTmp += nBytesSent; nBufLen -= nBytesSent; if (nBufLen == 0)
Slide 125: Chapter 7■ Socket Functions 129
free(pszBuf); } } while ((nBytesSent != SOCKET_ERROR) && (nBufLen > 0)); } break; // case FD_WRITE } // switch return 0L; }
Notice
handles multiple WinSock events, so a switch statement is used. As soon as the client’s connect succeeds, the FD_CONNECT message is sent, telling you that the socket is connected.
Immediately after that, the FD_WRITE message is sent, telling you that the socket is writeable. In this example, there is only one call to WSAAsyncSelect ( ) done in
CClientWindow::DoConnect().
that
the
asynchronous
select
message
handler,
CClientWindow::OnAsyncSelect(),
and the
the
FD_WRITE
DoConnect()
This single call registers interest in both the FD_CONNECT events. You also could use two separate calls. The first call, in the member function, could simply register interest in FD_CONNECT. Then, in
member function’s handling of
FD_CONNECT FD_WRITE
OnAsyncSelect()
expressing interest in just the the same result.
WSAAsyncSelect()
event. Either technique gives
, you could call
In this example, a buffer is allocated and filled with data to send in response to the FD_CONNECT event. The FD_WRITE handler sends the data in 100-byte pieces until the entire buffer is transmitted. I used 100-byte buffers in this example so that you could see the dowhile loop in action. This loop is executed as long as there are bytes to send and there are no errors. If you get a WSAEWOULDBLOCK error, it doesn’t pose a problem because the FD_WRITE handler will be called again as soon as the network subsystem can do it. Thanks to the use of the static variables, when the FD_WRITE handler gets called again, the rest of the buffer is sent from where it left off.
Receiving Data
Sending is just half the battle. This section describes receiving data using the function. Its prototype is as follows:
int PASCAL FAR recv(SOCKET s, char FAR * buf, int len, int flags);
recv()
is the connected socket from which to receive data. buf is a pointer to a buffer that will receive the data. len is the size of buf. flags specifies the way in which the recv() call is made. flags can be any of the following logically ORed together: 0 (zero) for no special receive options, MSG_PEEK to copy the data to buf but not remove it from the internal WinSock queues, and MSG_OOB to process out-of-band data. On success, recv() returns the number of bytes of received. This could range from 0 (zero) to len.
s
Slide 126: 130
Part II ■ Basics of WinSock Programming
A return value of 0 (zero) means the connection has been closed. On failure,
is returned and
WSAGetLastError()
SOCKET_ERROR
initialized with WSAStartup(); WSAENETDOWN if the network subsystem is failing; WSAEINTR if the blocking call was canceled with WSACancelBlockingCall(); WSAEINPROGRESS if a blocking call is in progress; WSAENOTCONN if the socket s isn’t connected; WSAENOTSOCK if the socket s isn’t a valid socket descriptor; WSAEOPNOTSUPP if MSG_OOB was specified by flags but the socket s isn’t a stream socket; WSAESHUTDOWN if the socket s has been shutdown; WSAEWOULDBLOCK if the socket s is marked as nonblocking and the recv() would block; WSAEMSGSIZE if socket s is a datagram socket and the datagram was too large for buf; WSAEINVAL if the socket s hasn’t been bound; WSAECONNABORTED if the virtual circuit was aborted due to timeout or other failure; WSAECONNRESET if the virtual circuit was reset by the remote side. The following demonstrates a typical call to recv():
SOCKET s; #define BUFSIZE (100) char pszBuf[BUFSIZE]; int nBytesRecv; int nError; // // // // // socket to communicate over receive buffer size buffer to receive data number of bytes received error status
specifies:
WSANOTINITIALIZED
if WinSock wasn’t
// create, bind, and connect socket s ... nBytesRecv = recv(s, pszBuf, BUFSIZE, 0); if (nBytesRecv == SOCKET_ERROR) { nError = WSAGetLastError(); } else { // nBytesRecv is the number of bytes successfully received }
The
recv()
getXbyY,
waiting for the sender to send some data. While recv() is blocking, the user loses control over your application. To prevent this problem, the WSAAsyncSelect() function is used to put the socket into nonblocking receive mode. The event of interest in this scenario is FD_READ. An FD_READ message is posted when a socket has data available to be read. works differently from FD_WRITE. For FD_WRITE, you get a single FD_WRITE when the socket is first connected. You must then send data until the send() results in a WSAEWOULDBLOCK error. You won’t get another FD_WRITE message until the condition that generated the WSAEWOULDBLOCK is cleared up. With FD_READ, you get the first FD_READ when data first arrives. If you then do a recv() and there is still more data waiting to be received, you will receive another FD_READ event notification. For example, suppose that the TCP/IP layer receives 100 bytes of data on a socket and WinSock posts a FD_READ
FD_READ
function has the same problem with blocking as the accept(), connect(), and send() functions. If there is no data waiting to be received, recv() blocks
Slide 127: Chapter 7 Functions
■ Socket 131
message to the appropriate window. If the application calls recv() with a buffer size of only 50, the first 50 bytes are returned in the buffer, and WinSock posts another FD_READ message to let the application know there is data still waiting to be read. A Visual C++ MFC example makes this more understandable. Following is a function to make a connection with a server. Note that this example is looking at this problem from the client’s perspective but a server uses the same techniques. The following code is similar to the connect() example.
BOOL CClientWindow::DoConnect() { // m_s is the socket descriptor which is a member // variable of the CClientWindow class // m_s has already been created and bound to a default name // m_addrServer is the address of the server and has // already been assigned // get asycnchronous event notification of connect if (WSAAsyncSelect(m_s, m_hWnd, WM_USER + 1, FD_CONNECT) == SOCKET_ERROR) { // error ... } // make the connection if (connect(m_s, (LPSOCKADDR)&m_addrServer, SOCKET_ERROR) { int nError = WSAGetLastError(); if (nError != WSAEWOULDBLOCK) { // a real error ... } } }
sizeof(m_addrServer))
==
You also need a member function in CClientWindow to handle the WM_USER + 1 message that’s generated when a client connection is completed and when the socket is readable:
BEGIN_MESSAGE_MAP(CClientWindow, CFrameWnd) //{{AFX_MSG_MAP(CClientWindow) ON_MESSAGE(WM_USER + 1, OnAsyncSelect) //}}AFX_MSG_MAP END_MESSAGE_MAP() LONG CMainFrame::OnAsyncSelect(WPARAM wParam, LPARAM lParam) { #define BUFSIZE (100) char pszBuf[BUFSIZE]; // receive buffer int nError; // error status int nBytesRecv; // bytes received
Slide 128: 132
Part II ■ Basics of WinSock Programming
// check for an error if (WSAGETSELECTERROR(lParam) != 0) { // ... return 0L; } // what event are we being notified of? switch (WSAGETSELECTEVENT(lParam)) { case FD_CONNECT: // client made a connection // get notices of readability if (WSAAsyncSelect(m_s, m_hWnd, WM_USER + 1, FD_READ) == SOCKET_ERROR) { // error ... } break; // case FD_CONNECT case FD_READ: // client can receive data now nBytesRecv = recv(m_s, pszBuf, BUFSIZE, 0); if (nBytesRecv == SOCKET_ERROR) { nError = WSAGetLastError(); if (nError == WSAEWOULDBLOCK) // this should never happen but handle it anyway // so it is differentiated from a real error else // a real error ... } else { // got some data ... } break; // case FD_READ } // switch return 0L; }
CClientWindow::OnAsyncSelect(), handles multiple WinSock events, so a switch statement is used. As soon as the client’s connect succeeds, the FD_CONNECT message is sent, telling us that the socket is connected. This
Notice that the asynchronous select message handler,
example uses two separate calls to WSAAsyncSelect ( ) . The first call, in the DoConnect() member function, simply registers interest in FD_CONNECT. Then, in the OnAsyncSelect() member function’s
handling of FD_CONNECT, just the FD_READ event.
WSAAsyncSelect()
is called again, this time expressing interest in
Slide 129: Chapter 7■ Socket Functions 133
Out-of-band data is received similarly to regular in-line data, unless the socket is configured to receive out-of-band data in-line. When a socket is configured this way, out-of-band data’s urgency loses its effectiveness. Assuming that a socket isn’t receiving out-of-band data in-line, the only differences to the preceding code would be as follows. The line that registers interest in the FD_READ event is expanded to read:
if (WSAAsyncSelect(m_s, m_hWnd, WM_USER + 1, FD_READ | FD_OOB) == SOCKET_ERROR) { // error ... }
You also need a FD_OOB handler in the CMainFrame::OnAsyncSelect() function. It is identical to the FD_READ event handler except the call to recv() is as follows:
nBytesRecv = recv(m_s, pszBuf, BUFSIZE, MSG_OOB);
Datagram-Oriented Client/Server Communication
Datagram-oriented client/server communication, using socket type SOCK_DGRAM, is simpler than the stream-oriented communication described in the preceding section. A server doesn’t need to listen for and accept connections from clients, and a client doesn’t need to connect to a server. Instead, each piece of data is individually addressed.
How a Server Prepares for Communication In the stream environment, a server must create a socket, bind the socket to a well-known name, listen for connections on the socket, and accept connections on the socket. In the datagram environment, a server must only create a socket and bind the socket to a well-known name.
Creating the socket is exactly like the stream counterpart except that specified rather than SOCK_STREAM:
SOCKET s; s = socket(AF_INET, SOCK_DGRAM, 0);
SOCK_DGRAM
is
The last step in preparing a server for communication is binding the socket to a wellknown name. This procedure is identical to that of the stream socket:
SOCKADDR_IN addr; int nError; addr.sin_family = AF_INET; addr.sin_port = htons(2050); addr.sin_addr.s_addr = htonl(INADDR_ANY);
Slide 130: 134
Part II ■ Basics of WinSock Programming
if (bind(s, (LPSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR) { nError = WSAGetLastError(); // ... }
In this code snippet, the server is preparing to receive data on port 2050, from any network interface the host has available.
How a Client Prepares for Communication In the stream environment, a client must create a socket, fill out an Internet address structure of the server, and connect to the server. In the datagram environment, a client must only create a socket. After that, an Internet address structure is used for each communication. Sending Datagram Data To send a datagram, use the sendto() function. Its prototype is as follows:
int PASCAL FAR sendto(SOCKET s, const char FAR * buf, int len, int flags, const struct sockaddr FAR *to, int tolen);
is the socket to send on. buf is a pointer to the data to send. len is the length of the buf buffer. flags specifies the way in which sendto() is called. flags can be any of the following logically ORed together: 0 (zero) for no special send options, MSG_DONTROUTE if the data shouldn’t be subject to routing, and MSG_OOB to send out-of-band data. to is a pointer to the Internet address of the intended receiver. For WinSock 1.1, to must be a sockaddr_in structure. tolen is the length of the to parameter.
s
On success,
sendto()
returned and WSAGetLastError() should be called. Possible error values are the following: WSANOTINITIALIZED if WinSock wasn’t initialized with WSAStartup(); WSAENETDOWN if the network subsystem is failing; WSAEACCESS if the address is a broadcast address but the appropriate flag was not set; WSAEINTR if the blocking call was canceled with WSACancelBlockingCall(); WSAEINPROGRESS if a blocking call is in progress; WSAEFAULT if buf or to are not in a valid part of the user address space or if to is too small to hold a sockaddr structure; WSAENETRESET if the connection needs to be reset; WSAENOBUFS if there is a internal WinSock buffer deadlock; WSAENOTCONN if the socket s isn’t connected (for SOCK_STREAM only); WSAENOTSOCK if the socket s isn’t a valid socket descriptor; WSAEOPNOTSUPP if MSG_OOB was specified by flags but the socket s isn’t a stream socket;
WSAESHUTDOWN
returns the number of bytes sent. On failure,
SOCKET_ERROR
is
as nonblocking and the sendto() would block; WSAEMSGSIZE if socket s is a datagram socket and the buf buffer is larger than the maximum supported by the particular WinSock implementation; WSAECONNABORTED if the virtual circuit was aborted due to timeout or other failure; WSAECONNRESET if the virtual circuit was reset by the remote
if the socket
s
has been shutdown;
WSAEWOULDBLOCK
if the socket
s
is marked
Slide 131: Chapter 7■ Socket Functions 135
side;
isn’t available from the local machine; WSAENOSUPPORT if the addresses in the specified family can’t be used with this socket; WSAEDESTADDRREQ if a destination address is required; or WSAENETUNREACH if the network can’t be reached from this host at
WSAEADDRNOTAVAIL to
if the address specified by
this time.
CAUTION
The WSAEMSGSIZE error will result if you try to send too large a datagram. This maximum is WinSock vendor dependent. To find out the maximum size for your particular WinSock implementation, examine the iMaxUdpDg variable in the WSAData structure that was passed to
WSAStartup().
Here is an example of using the sendto() function:
SOCKET s; SOCKADDR_IN addr; #define BUFSIZE (100) char pszBuf[BUFSIZE]; int nBufLen; int nBytesSent; int nError; s = socket(AF_INET, SOCK_DGRAM, 0); if (s == INVALID_SOCKET) { nError = WSAGetLastError(); // ... } else { // fill out the address of the recipient addr.sin_family = AF_INET; addr.sin_port = htons(2050); addr.sin_addr.s_addr = inet_addr(“166.78.16.150”); // assign some data to send lstrcpy(pszBuf, “Hello, World!”); nBufLen = lstrlen(pszBuf); // send the datagram nBytesSent = sendto(s, pszBuf, nBufLen, 0, (LPSOCKADDR)&addr, sizeof(addr)); if (nBytesSent == SOCKET_ERROR) { nError = WSAGetLastError(); // ... } else { // data was sent } closesocket(s); }
Slide 132: 136
Part II ■ Basics of WinSock Programming
In this example, data is sent to port 2050 on the host with IP address 166.78.16.150. Notice that no connection was established before transmission. One of the capabilities of datagram sockets (SOCK_DGRAM) is that they can be sent to
multiple recipients with one call to sendto(). This is accomplished by specifying the INADDR_BROADCAST address, as in the following:
addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
By default, a socket doesn’t support broadcast transmission. If you try to send to the INADDR_BROADCAST address, you get the WSAEACCESS error. To remedy this, use the setsockopt() function, as in the following:
BOOL bBroadcast = TRUE; // TRUE = enable, FALSE = disable setsockopt(s, SOL_SOCKET, SO_BROADCAST, (LPSTR)&bBroadcast, sizeof(bBroadcast));
may block, so you can use WSAAsyncSelect(), just as it was used for stream communication, by expressing interest in the FD_WRITE event.
sendto()
Receiving Datagram Data To receive a datagram, use the recvfrom() function. Its prototype is as follows:
int PASCAL FAR recvfrom(SOCKET s, char FAR * buf, int len, int flags, struct sockaddr FAR *from, int FAR * fromlen);
s
is the socket descriptor to use for communication.
len
buf
is a buffer to accept the incomrecvfrom()
ing data.
any of the following logically ORed together: 0 (zero) for no special receive options, MSG_PEEK to peek at the incoming data by copying it into buf but not removing it from the input queue, and MSG_OOB to receive out-of-band data (for streams only). from is an optional pointer to a sockaddr_in structure, which will contain the address of the sending socket upon return of recvfrom(). fromlen is an optional pointer to the length of from. fromlen should be initialized to the size of sockaddr_in before it is used. On success, recvfrom() returns the number of bytes received. On failure, SOCKET_ERROR is returned and WSAGetLastError() should be called. Possible error values are as follows: WSANOTINITIALIZED if WinSock wasn’t initialized with WSAStartup(); WSAENETDOWN if the
network subsystem is failing;
WSAEINTR
is the size of
buf. flags
specifies the way
is made.
flags
can be
if a blocking call is in progress; WSAEFAULT is too small to hold a sockaddr structure; WSAEINVAL if socket s isn’t bound; WSAENOTCONN if the socket s isn’t connected (for SOCK_STREAM only); WSAENOTSOCK if the socket s isn’t a valid socket descriptor; WSAEOPNOTSUPP if MSG_OOB was specified by flags but the socket s isn’t a stream socket; WSAESHUTDOWN if the socket s has been shutdown; WSAEWOULDBLOCK if the socket s is marked as nonblocking and the sendto() would block;
WSACancelBlockingCall(); WSAEINPROGRESS
if the blocking call was canceled with
if
fromlen
Slide 133: Chapter 7■ Socket Functions 137
if socket s is a datagram socket and the data was too large to fit into buf (the data is truncated); WSAECONNABORTED if the virtual circuit was aborted due to timeout or other failure; or WSAECONNRESET if the virtual circuit was reset by the remote side.
WSAEMSGSIZE
Here is an example of using the recvfrom() function in a datagram server application:
char pszMessage[100]; // informational message SOCKET s; // socket to receive data on SOCKADDR_IN addr; // address of the socket #define BUFSIZE (100) // receive buffer size char pszBuf[BUFSIZE]; // receive buffer int nBytesRecv; // number of bytes received int nError; // error code SOCKADDR_IN addrFrom; // address of sender int nAddrFromLen = sizeof(addrFrom); // lengh of sender structure IN_ADDR inFrom; // IP address of sender s = socket(AF_INET, SOCK_DGRAM, 0); if (s == INVALID_SOCKET) { nError = WSAGetLastError(); // ... } else { // fill out the name this server will read data from addr.sin_family = AF_INET; addr.sin_port = htons(2050); addr.sin_addr.s_addr = htonl(INADDR_ANY); // bind the name to the socket if (bind(s, (LPSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR) { nError = WSAGetLastError(); // ... } else { nBytesRecv = recvfrom(s, pszBuf, 100, 0, (LPSOCKADDR)&addrFrom, &nAddrFromLen); if (nBytesRecv == SOCKET_ERROR) { nError = WSAGetLastError(); // ... } else { // got some data ... // copy the four byte IP address into an IP address structure memcpy(&inFrom, &addrFrom.sin_addr.s_addr, 4); // print an informational message wsprintf(pszMessage, “server received %d bytes from %s, port is %d”, nBytesRecv, inet_ntoa(inFrom), ntohs(addrFrom.sin_port));
Slide 134: 138
Part II ■ Basics of WinSock Programming
MessageBox(pszMessage, “Datagram Server Info”); } } closesocket(s); }
Note that in this example the optional from parameter was provided. This gives the receiver the ability to send data back to the sender. This is demonstrated in the next chapter’s datagram example program.
sendto() function, the recvfrom() function may block. Use WSAAsyncSelect() with the event to solve this problem. Implementation is similar to that of the stream example.
As with the
FD_READ
Closing a Socket
The previous sections explain how network applications create sockets and communicate through them. The last thing to do is close the socket. The closesocket() function’s prototype is as follows:
int PASCAL FAR closesocket(SOCKET s);
is the socket to close. On success, it returns 0 (zero). On failure, SOCKET_ERROR is returned and WSAGetLastError() reveals the following: WSANOTINITIALIZED if WinSock wasn’t initialized with WSAStartup(); WSAENETDOWN if the network subsystem is failing; WSAEINTR if the blocking call was canceled with WSACancelBlockingCall(); WSAEINPROGRESS if a blocking call is in progress; WSAENOTSOCK if the socket s isn’t a valid socket descriptor; or WSAEWOULDBLOCK if the socket s is marked as nonblocking and the closesocket() would block.
s
There are several variables that determine the closing characteristics of a socket. These characteristics are determined by the socket’s linger options as set with setsockopt() (see Table 7.2).
Table 7.2. Linger Behavior on closesocket().
Option
SO_LINGER SO_LINGER SO_DONTLINGER
Interval
Type of Close Wait for Close?
Zero Hard Nonzero Graceful Don’t care Graceful
No Yes No
Slide 135: Chapter 7■ Socket Functions 139 isn’t blocked, even if queued data
If
SO_LINGER
has not yet been sent or acknowledged. This is called a hard close because the socket is closed immediately and any unsent data is lost. Any recv() call on the remote side of the circuit can fail with WSAECONNRESET.
If
SO_LINGER
is set with a zero timeout interval,
closesocket()
the remaining data has been sent or until the timeout expires. This is called a graceful disconnect. Note that if the socket is set to nonblocking and SO_LINGER is set to a nonzero timeout, the call to closesocket() will fail with an error of WSAEWOULDBLOCK.
If
SO_DONTLINGER
is set with a nonzero timeout interval, the
closesocket()
call blocks until
ately. However, any data queued for transmission will be sent, if possible, before the underlying socket is closed. This is also called a graceful disconnect. Note that in this case, the WinSock implementation may not release the socket and other resources for an arbitrary period, which may affect applications that expect to use all available sockets.
To set the linger options of a socket, use setsockopt(). The following three code segments demonstrate the three entries in Table 7.2.
// Option Interval Type of Close Wait for Close? // SO_LINGER Zero Hard No LINGER ling; ling.l_onoff = 1; // linger on ling.l_linger = 0; // timeout in seconds setsockopt(s, SOL_SOCKET, SO_LINGER, (LPSTR)&ling, sizeof(ling)); // Option Interval Type of Close Wait for Close? // SO_LINGER Non-zero Graceful Yes LINGER ling; ling.l_onoff = 1; // linger on ling.l_linger = 5; // timeout in seconds setsockopt(s, SOL_SOCKET, SO_LINGER, (LPSTR)&ling, sizeof(ling)); // Option Interval Type of Close Wait for Close? // SO_DONTLINGER Don’t care Graceful No LINGER ling; ling.l_onoff = 0; // linger off ling.l_linger = 0; // timeout in seconds setsockopt(s, SOL_SOCKET, SO_LINGER, (LPSTR)&ling, sizeof(ling));
is set on a stream socket, the
closesocket()
call will return immedi-
If your application wants to know when the socket has been closed, use WSAAsyncSelect() and specify the FD_CLOSE event. If WSAGETSELECTERROR returns 0 (zero) for the FD_CLOSE event, the socket was closed gracefully. An error value of WSAECONNRESET tells you the socket was abortively disconnected.
Slide 136: 140
Part II ■ Basics of WinSock Programming
Summary
This chapter discussed the socket related functions necessary to make a client/server application, using both datagrams and streams. Stream communication is complicated by the need to make the connection between the client and server, but this trade-off provides for a robust communication path. Although datagram communication is easy to initiate, it is limited by its inherent unreliability. The next chapter develops four sample applications that use the functions discussed in this chapter. These sample chapters provide the cohesion between this chapter’s disparate presentation of several WinSock functions.
Slide 137: Chapter 8■ Sample Applications 141
8
Sample Sample Applications
Slide 138: 142
Part II ■ Basics of WinSock Programming
This chapter presents four sample programs that make use of the WinSock functions described in the preceding three chapters. The first sample initializes WinSock and offers you a dialog box to view specifics about the WinSock implementation on which the program is running. The second sample application gives you access to WinSock database functions, in both their blocking and nonblocking modes of operation. The third and fourth samples are composed of two programs each: a client that sends either datagrams or stream data and a server that receives them and sends them back to the client.
Maintaining 16-Bit and 32-Bit Projects
With the help of the Microsoft Foundation Class library, it’s very easy to maintain the same source code for both a 16-bit executable and a 32-bit executable. Unfortunately, maintaining the Visual C++ projects for these different executable versions isn’t as easy. The project files (makefiles) for 16-bit Visual C++ 1.5 and 32-bit Visual C++ 1.1 aren’t compatible; you must maintain two separate projects. The easiest way to do this is to use the Visual C++ product that you like best (16-bit or 32-bit) to create a project and then create a makefile for the other environment. As an example, suppose that a project named PROJ is initially developed with the 16-bit compiler. The Visual C++ 16-bit project file is called PROJ.MAK. After program development is far enough along, rename the PROJ.MAK file to PROJ.M16 and remove all temporary files in the project’s directory (for example, *.OBJ and *.RES). Next, launch 32-bit Visual C++ and select New… from the Project menu. Add all of the source needed to build the project, as well as any libraries it needs to link with. Call this new project PROJ as well. Use this project file to build the 32-bit version. When you wish to switch back to the 16-bit environment, rename PROJ.MAK to PROJ.M32 and then copy PROJ.M16 to PROJ.MAK. If you’re wondering why not just use different project names such as PROJ16.MAK and PROJ32.MAK, the answer lies in Visual C++ and its associated tools, such as App Studio and ClassWizard. These tools use the project file’s name when determining what other files are named. This makes it difficult to use App Studio and ClassWizard effectively. This limitation also makes it difficult to use separate directories for the projects, as in \PROJ\16BIT\PROJ.MAK and \PROJ\32BIT\PROJ.MAK. To simplify the procedure of switching between 16-bit and 32-bit project files, a couple of batch files are used. The batch file shown in Listing 8.1 is used to select which project file to use. Note that this batch file should be used only when you’re prepared to build the project under the new compiler, because all of the object files and other temporary files are removed by running the script.
Slide 139: Chapter 8 ■ Sample Applications
143
Listing 8.1. USE.BAT batch file.
@ECHO OFF REM Replace PROJ with the actual project name IF “%1”==”16" GOTO USE IF “%1”==”32" GOTO USE ECHO Directions for use: USE 16 or USE 32 GOTO END :USE IF NOT EXIST PROJ.M%1 GOTO NOFILE DEL *.APS DEL *.BSC DEL *.CLW DEL *.EXE DEL *.OBJ DEL *.PCH DEL *.PDB DEL *.RES DEL *.SBR DEL *.VCW DEL *.WSP COPY PROJ.M%1 PROJ.MAK GOTO END :NOFILE ECHO %1-bit project file does not exist GOTO END
:END
The batch file shown in Listing 8.2 is a script used to save the project file to the appropriate 16-bit or 32-bit makefile. Be careful when using this batch file because you could accidentally write over the 16-bit makefile with the 32-bit version, and vice versa. For example, don’t run 32-bit Visual C++, exit Visual C++, and then run SAVE 16. This will cause you to lose the 16-bit project file.
Listing 8.2. SAVE.BAT batch file.
@ECHO OFF REM Replace PROJ with the actual project name IF “%1”==”16" GOTO USE IF “%1”==”32" GOTO USE ECHO Directions for use: SAVE 16 or SAVE 32 GOTO END :USE
continues
Slide 140: 144
Part II ■ Basics of WinSock Programming
Listing 8.2. continued
ECHO Are you sure you are saving the correct version? ECHO Press CTRL-C to abort this procedure PAUSE COPY PROJ.MAK PROJ.M%1 :END
Using these two batch files to support 16-bit and 32-bit project makefiles gives you the flexibility of using either development environment with the same source code.
CAUTION
1.1 32-bit edition is named VER.H in Visual C++ 1.5. This header file is for the support of version information and is included in the RC2 file created by AppWizard. One possible solution would be to use an ifdef in the RC2 file, as in
#ifdef _WIN32 #include “winver.h” #else #include “ver.h” #endif
16-bit Visual C++ users: The WINVER.H header file shipped with Visual C++
Apparently, though, the Visual C++ resource compiler doesn’t interpret preprocessor directives as you might expect when they appear in an RC2 file. The solution I use is to copy VER.H to WINVER.H in the 16-bit Visual C++’s include directory (that is, C:\MSVC\INCLUDE). The sample programs in this book rely on WINVER.H’s existence. If you don’t copy VER.H to WINVER.H, you’ll receive a compile error about not finding WINVER.H. Reference the Microsoft Knowledgebase article Q103719 dated January 20, 1994, for more details on migrating 16-bit makefiles to 32-bit.
Slide 141: Chapter 8 ■ Sample Applications
145
WinSock TCP/IP Stack Information
This program, WSINFO, allows you to view the details of the WinSock TCP/IP stack that the computer is running. It uses the following WinSock functions: WSAStartup(), WSACleanup(), and WSAGetLastError(). This program is generated using Visual C++’s AppWizard feature, which creates a skeleton application from which to build upon. This book isn’t geared toward the beginning Visual C++ programmer, so only the first sample program is worked through step by step. The first step in producing this program is to use AppWizard to generate a skeletal application. This application uses the Single Document Interface rather than the Multiple Document Interface. There’s no need for any special features such as a toolbar, printing and print preview, context-sensitive help, or Object Linking and Embedding. This application is very simple in comparison to most. Use WSINFO as the project name. After AppWizard has finished its magic, edit WSINFO.H. This file contains the class declaration for the application class CWsinfoApp. Add the following publicly accessible member variables to the class:
WSADATA m_wsaData; BOOL m_bWinSockOK; int m_nWinSockError; // WinSock information // TRUE if WinSock startup succeeded // WinSock error code
contains the WinSock information returned by WSAStartup(). m_bWinSockOK is TRUE if WinSock startup succeeded; it’s FALSE otherwise. m_nWinSockError contains the error code if WinSock startup failed. The WSINFO.H file is also a good place to include the WINSOCK.H header file because WSINFO.H is included in the other source files of the project. Add the ExitInstance() function to the class. This function is called when the application exits, allowing us a good opportunity to shutdown WinSock.
m_wsaData
At this point, the CWsinfoApp class looks like the following:
class CWsinfoApp : public CWinApp { public: WSADATA m_wsaData; // WinSock information BOOL m_bWinSockOK; // TRUE if WinSock startup succeeded int m_nWinSockError; // WinSock error code public: CWsinfoApp(); // Overrides virtual BOOL InitInstance(); virtual int ExitInstance();
Slide 142: 146
Part II ■ Basics of WinSock Programming
// Implementation //{{AFX_MSG(CWsinfoApp) afx_msg void OnAppAbout(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Edit the WSINFO.CPP file to modify the
InitInstance ( )
CWsinfoApp class member functions. In InitInstance(), WSAStartup() is called. When modifications to InitInstance() are completed, it looks like the following:
BOOL CWsinfoApp::InitInstance() { // WinSock startup // If WSAStartup() is successful, we still // need to check the version numbers. WORD wVersionRequired = MAKEWORD(1, 1); // WinSock 1.1 required m_bWinSockOK = FALSE; // not OK m_nWinSockError = 0; // no WinSock error if (WSAStartup(wVersionRequired, &m_wsaData) == 0) { if (wVersionRequired == m_wsaData.wVersion) m_bWinSockOK = TRUE; else WSACleanup(); } else m_nWinSockError = WSAGetLastError(); // Standard initialization // If you are not using these features and wish to reduce the size // of your final executable, you should remove from the following // the specific initialization routines you do not need. SetDialogBkColor(); LoadStdProfileSettings(); // set dialog background color to gray // Load standard INI file options (including MRU)
and
ExitInstance ( )
// Register the application’s document templates. Document templates // serve as the connection between documents, frame windows and views. AddDocTemplate(new CSingleDocTemplate(IDR_MAINFRAME, RUNTIME_CLASS(CWsinfoDoc), RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(CWsinfoView)));
// create a new (empty) document OnFileNew(); if (m_lpCmdLine[0] != ‘\0’) { // TODO: add command line processing here } return TRUE; }
Slide 143: Chapter 8 ■ Sample Applications
147
In ExitInstance(), WSACleanup() is called. When modifications to completed, it looks like the following:
int CWsinfoApp::ExitInstance() { // WinSock cleanup // If WinSock was started successfully, it must be shutdown. if (m_bWinSockOK) WSACleanup(); // call the base class’ member function return CWinApp::ExitInstance(); }
ExitInstance()
are
Note that the base class’s ExitInstance() function is called to allow for the default processing of this event. Now use App Studio to create a dialog box resource. This dialog box is used to display the information contained in the WSADATA structure that’s defined in the application’s
class. Give the dialog box a caption of “WinSock Information” and an ID of IDD_DIALOG_WINSOCK_INFO. By default, App Studio includes an OK and
Cancel button in a dialog; remove the Cancel button because this dialog box is for informational purposes only and its return value is simply ignored. To display the data stored in the WSADATA structure, we need several fields. This example uses EDIT controls to display this information. Each EDIT control is preceded by a STATIC text control, which acts as a label. Create five EDIT controls aligned vertically with the following names:
IDC _EDIT _VERSION
,
IDC _EDIT _DESCRIPTION
,
IDC _EDIT _STATUS
,
IDC_EDIT_MAXIMUM_SOCKETS,
and
IDC_EDIT_MAXIMUM_DATAGRAM_SIZE.
From within App Studio and with the “WinSock Information” dialog box selected, run ClassWizard to create a class associated with this dialog resource. Name the class CWinSockInfoDlg with a base class of CDialog. Change the name of the source files that ClassWizard creates for this class to INFODLG.CPP and INFODLG.H. After the class is created, use ClassWizard to create a function to handle the dialog box
initialization phase . With the
CWinSockInfoDlg
under ClassWizard’s Object ID list. When you do this, a whole bunch of stuff will fill the Messages section of the ClassWizard window. These are the Windows messages that may be sent to the CWinSockInfoDlg class. Scroll down to the WM_INITDIALOG message and then click on the Add Function button. ClassWizard automatically generates a stub function calledOnInitDialog(). Exit App Studio and edit the INFODLG.CPP file. Add code to populate the fields of the dialog box with the information stored in the WSADATA structure. The WSADATA structure is a public member variable of the CWsinfoApp class, so you can access it from the
class name selected , select
CWinSockInfoDlg
Slide 144: 148
Part II ■ Basics of WinSock Programming
class by getting a pointer to the application object. The AfxGetApp() function is used for this purpose. The OnInitDialog() function should look like this when you’re done:
CWinSockInfoDlg
BOOL CWinSockInfoDlg::OnInitDialog() { CDialog::OnInitDialog(); // initialize the fields of the dialog box CWsinfoApp *pApp = (CWsinfoApp *)AfxGetApp(); // pointer to app LPWSADATA pWsaData = &(pApp->m_wsaData); // pointer to app’s WinSock info char pszMsg[100]; // buffer to use for formatting wsprintf(pszMsg, “%d.%d”, (int)(HIBYTE(pWsaData->wVersion)), (int)(LOBYTE(pWsaData->wVersion))); SetDlgItemText(IDC_EDIT_VERSION, pszMsg); SetDlgItemText(IDC_EDIT_DESCRIPTION, pWsaData->szDescription); SetDlgItemText(IDC_EDIT_STATUS, pWsaData->szSystemStatus); wsprintf(pszMsg, “%u”, pWsaData->iMaxSockets); SetDlgItemText(IDC_EDIT_MAXIMUM_SOCKETS, pszMsg); wsprintf(pszMsg, “%u”, pWsaData->iMaxUdpDg); SetDlgItemText(IDC_EDIT_MAXIMUM_DATAGRAM_SIZE, pszMsg); return TRUE; // return TRUE } unless you set the focus to a control
Now you have enough code to start WinSock and to populate an informational dialog box. The only thing missing is a way to launch the dialog box. Start App Studio and edit the menu resource. Add a menu item under the Help selection with a caption of “WinSock Information” and an ID of ID_WINSOCK_INFO. Run the ClassWizard to generate a function for the ID_WINSOCK_INFO message. Select CWsinfoApp for the class name, and then scroll through the object IDs until you reach ID_WINSOCK_INFO. In the message section, select COMMAND and then click the Add Function button. Use
OnWinsockInfo
as the function name to handle this menu item being selected. The code for the OnWinsockInfo member function looks like the following:
void CWsinfoApp::OnWinsockInfo() { // If WinSock startup was successful, display an informational // dialog box; otherwise, display an error message if (m_bWinSockOK) { CWinSockInfoDlg dlg; dlg.DoModal(); } else { char pszError[100]; if (m_nWinSockError) wsprintf(pszError, “WinSock m_nWinSockError); else
startup
failed
with
error
%d”,
Slide 145: Chapter 8 ■ Sample Applications
149
lstrcpy(pszError, “WinSock does not meet version requirements”); AfxMessageBox(pszError); } }
The only thing remaining before compiling and running the program is to change the linker options so that WINSOCK.LIB or WSOCK32.LIB is linked in for the 16-bit or 32-bit version, respectively. Figure 8.1 shows the WinSock Information menu item about to be selected. Figure 8.2 shows the result running on Windows NT using the WinSock TCP/IP stack supplied by Microsoft.
FIGURE 8.1.
About to select WinSock Information from Help menu.
FIGURE 8.2.
WinSock Information for the Windows NT TCP/IP Stack.
You may want to use the useful as a debugging aid.
CWinSockInfoDlg
class in applications you develop. It can be very
Slide 146: 150
Part II ■ Basics of WinSock Programming
WinSock Database Test Application
This program, DBTST, allows you to execute WinSock database lookups for hosts and services. It uses the following WinSock functions: WSAStartup(), WSACleanup() ,
WSAGetLastError ( )
,
WSACancelAsyncRequest ( )
,
WSAAsyncGetHostByName ( )
,
WSAAsyncGetHostByAddr() getservbyport(),
,
,
gethostbyname()
,
gethostbyaddr()
,
,
WSAAsyncGetServByName ( )
WSAAsyncGetServByPort ( )
and ntohs(). This program is created from scratch without the benefit of AppWizard. Visual C++ project files are utilized.
,
getservbybname ( )
Extra steps must be taken when creating programs from scratch, as opposed to letting AppWizard do the grunt work. The benefit of creating a program from scratch is that you don’t have to deal with possibly unneeded features such as documents and views. This approach is closer to doing things the old SDK way, but it still benefits from the Microsoft Foundation Classes. The first source files to look at are STDAFX.H and STDAFX.CPP. These files support the precompiled header feature. By including STDAFX.H in each implementation file (that is, *.CPP), there’s no need to include the mandatory Windows and MFC header files in each. STDAFX.H and STDAFX.CPP are shown in Listings 8.3 and 8.4, respectively. You must configure the compiler’s precompiled header compiler option to use this feature.
Listing 8.3. STDAFX.H for DBTST.
#include <afxwin.h> #include <winsock.h>
Listing 8.4. STDAFX.CPP for DBTST.
#include “stdafx.h”
The next files of interest implement the application object CTheApp. CTheApp is derived from the MFC class CWinApp. The class declaration is in DBTST.H, shown in Listing 8.5. The class is implemented in DBTST.CPP and is shown in Listing 8.6. Note that the WinSock startup isn’t as robust as in the WSINFO example; there are no error messages displayed on error, for example. Instead, the application simply exits immediately if WinSock can’t be initialized.
Slide 147: Chapter 8■ Sample Applications 151
Listing 8.5. DBTST.H for DBTST.
#ifndef __DBTST_H__ #define __DBTST_H__ #include “resource.h”
// CTheApp class declaration // class CTheApp : public CWinApp { private: WSADATA m_WsaData; // WinSock data structure public: BOOL InitInstance(); int ExitInstance(); }; #endif // __DBTST_H
Listing 8.6. DBTST.CPP for DBTST.
// CTheApp implementation file // #include “stdafx.h” #include “dbtst.h” #include “mainwnd.h”
// Creating a CTheApp object starts the application running // CTheApp NEAR TheApp;
// CTheApp::InitInstance // // When the CTheApp object is created, this member function is // automatically called. WinSock is initiated here. The main window // of the application is created and shown here. // BOOL CTheApp::InitInstance() { if (WSAStartup(MAKEWORD(1, 1), &m_WsaData) != 0) return FALSE; m_pMainWnd = new CMainWindow(); m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow();
continues
Slide 148: 152
Part II ■ Basics of WinSock Programming
Listing 8.6. continued
return TRUE; }
// CTheApp::ExitInstance // // When the application is closed, shutdown WinSock. // int CTheApp::ExitInstance() { WSACleanup(); return CWinApp::ExitInstance(); }
This application can look up either host or service information. To support this, two dialog boxes are used. The host dialog box is implemented in the CHostDlg class. This class is created using App Studio and ClassWizard. The class declaration file (HOST.H) is shown in Listing 8.7. Listing 8.8 shows the class implementation file (HOST.CPP).
CHostDlg
variable contains the string the user enters into the dialog box’s single EDIT control. The dialog box has three buttons: Asynchronous, Blocking, and Cancel. If the user presses the Asynchronous button, IDC_BUTTON_ASYNC is returned to the caller, signaling that the user wants the database lookup to be carried out asynchronously. Likewise, IDC_BUTTON_BLOCKING is returned when the Blocking button is pressed. Pressing Cancel aborts the lookup.
Listing 8.7. HOST.H for DBTST.
// CHostDlg dialog // class CHostDlg : public CDialog { // Construction public: CHostDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data //{{AFX_DATA(CHostDlg) enum { IDD = IDD_HOST }; CString m_stringHost; //}}AFX_DATA // Implementation protected: virtual void DoDataExchange(CDataExchange* pDX);
has as a public member variable a
CString
object named
m_stringHost.
This
// DDX/DDV support
Slide 149: Chapter 8 ■ Sample Applications
153
// Generated message map functions //{{AFX_MSG(CHostDlg) afx_msg void OnClickedButtonAsync(); afx_msg void OnClickedButtonBlocking(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Listing 8.8. HOST.CPP for DBTST.
// host.cpp : implementation file // #include “stdafx.h” #include “dbtst.h” #include “host.h” #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif
// CHostDlg dialog CHostDlg::CHostDlg(CWnd* pParent /*=NULL*/) : CDialog(CHostDlg::IDD, pParent) { //{{AFX_DATA_INIT(CHostDlg) m_stringHost = “”; //}}AFX_DATA_INIT } void CHostDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CHostDlg) DDX_Text(pDX, IDC_EDIT_HOST, m_stringHost); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CHostDlg, CDialog) //{{AFX_MSG_MAP(CHostDlg) ON_BN_CLICKED(IDC_BUTTON_ASYNC, OnClickedButtonAsync) ON_BN_CLICKED(IDC_BUTTON_BLOCKING, OnClickedButtonBlocking) //}}AFX_MSG_MAP END_MESSAGE_MAP()
// CHostDlg message handlers void CHostDlg::OnClickedButtonAsync() { UpdateData(TRUE);
continues
Slide 150: 154
Part II ■ Basics of WinSock Programming
Listing 8.8. continued
EndDialog(IDC_BUTTON_ASYNC); } void CHostDlg::OnClickedButtonBlocking() { UpdateData(TRUE); EndDialog(IDC_BUTTON_BLOCKING); }
The dialog box to prompt for which service to look up is implemented as the CServiceDlg class. The source code for this dialog box is in the SERVICE.H and SERVICE.CPP files shown in Listings 8.9 and 8.10, respectively. The code follows a similar format as the CHostDlg class except that there are two data entry fields in the services dialog: service and protocol.
Listing 8.9. SERVICE.H for DBTST.
// CServiceDlg dialog // class CServiceDlg : public CDialog { // Construction public: CServiceDlg(CWnd* pParent = NULL); // Dialog Data //{{AFX_DATA(CServiceDlg) enum { IDD = IDD_SERVICE }; CString m_stringService; CString m_stringProtocol; //}}AFX_DATA // Implementation protected: virtual void DoDataExchange(CDataExchange* pDX); // Generated message map functions //{{AFX_MSG(CServiceDlg) afx_msg void OnClickedButtonAsync(); afx_msg void OnClickedButtonBlocking(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
// standard constructor
// DDX/DDV support