Until the recent information explosion that began a few years ago, not too many programmers had heard of TCP/IP, the protocol suite that is used throughout the Internet for internetwork communication (that is, communication between subnetworks).
An obscure protocol a few years back, no new operating system can claim to be complete without it nowadays; Windows is no exception.
The most widely used application programming interface for TCP/IP programming is the Berkeley sockets library. Berkeley sockets have been used throughout the Internet for implementing TCP/IP applications. It was only natural that when the time came, Microsoft picked this API for TCP/IP under Windows.
The use Berkeley sockets is not limited to TCP/IP programming. Berkeley sockets represent an intertask communication mechanism that may use TCP/IP or other network protocols for the actual transmission of data. However, current WinSock implementations limit the use of the Berkeley socket interface to TCP/IP.
First, here's a review of the fundamentals of TCP/IP networking.
TCP/IP is somewhat of a misnomer; the internet protocol suite that it usually refers to consists of several components other than TCP and IP. Figure 39.1 provides a brief overview of these in the context of the seven layers of OSI, the Open Systems Interconnect standard.
Note: In the phrase "internet protocol," the term internet is used as a generic term and is written in lower case. There can be many internets (for example, the internetwork at a large organization) of which "the" Internet is but one, albeit the biggest by far.
TCP is the Transmission Control Protocol. TCP provides a reliable byte stream between two processes. Reliability in this context means that applications are not required to monitor for lost or corrupted packets. TCP is also a connection-oriented protocol. Applications that communicate via TCP establish a logical connection before any exchange of data can take place and terminate the connection when the data exchange has been completed. The term virtual circuit is sometimes used for this kind of a logical connection.
UDP is the User Datagram Protocol. UDP is a connectionless protocol. Data packets are addressed and delivered individually. UDP is not a reliable protocol; applications must be prepared to handle the loss of packets.
ICMP is the Internet Control Message Protocol. This protocol is often used to deliver error and control information on TCP/IP networks. ICMP packets are rarely used by user processes. A notable exception is the ping utility that is used to verify the accessibility of a foreign host by sending an ICMP "echo" packet and monitoring its return.
IP stands for (what else?) Internet Protocol. IP provides the packet delivery service on which TCP, UDP, and ICMP rely.
ARP is the Address Resolution Protocol. ARP is used to translate a network address to a hardware address.
RARP is the Reverse Address Resolution Protocol. RARP is used to translate a hardware address into a network address.
At the heart of TCP/IP data transmissions is the IP datagram. An IP datagram is a packet that encapsulates source and destination addresses, type of service information, user data, and error correction information.
An IP datagram consists of a header and a block of data. The data can be anything depending on the type of service and the user requirements. The header, on the other hand, contains a set of well-defined fields; this is where we now focus our attention.
Of the fields shown in Figure 39.2, the ones that are of considerable interest to us include the Protocol, Source Address, and Destination Address fields.
The Protocol field determines how the rest of the IP packet is interpreted. Several dozen values have been defined for this field. For example, a value of 6 implies a TCP packet; a value of 17 implies a UDP packet.
The addresses in a packet represent host addresses. Host addresses uniquely identify hosts on an internet.
An IP host address is a 32-bit number uniquely identifying an internet host. Or, to be more precise, an IP address uniquely identifies a particular interface on an internet host; gateway machines (those which have interfaces on more than one network) routinely have several host addresses.
According to convention, the 4-byte internet host address is usually written down as a set of four decimal numbers: for example, 127.0.0.1.
The internet host address is usually divided into two parts: the network address and the actual host address. The respective lengths of these two portions of the address vary depending on the most significant byte in the address.
Class A network addresses are those with the most significant byte between 0 and 127. A class A address consists of an 8-bit network address and a 24-bit host address. Because addresses beginning with 0 and 127 are reserved, there can be a maximum of 126 class A subnets on an internet. A class A subnet can contain up to 16,777,214 hosts. (Addresses in the form of nnn.0.0.0 and nnn.255.255.255 are reserved.)
Class B network addresses are those with the most significant byte between 128 and 191. Class B addresses consist of a 16-bit network address and a 16-bit host address. There can be a maximum of 16,383 class B subnets. A class B subnet may contain up to 65,534 hosts. (Again, addresses in the form of nnn.mmm.0.0 and nnn.mmm.255.255 are reserved.)
A class C network address is one with the most significant byte betweeen 192 and 223. This allows a total of 2,097,152 class C subnets. A class C subnet may contain up to 254 hosts. (Addresses in the form of nnn.mmm.kkk.0 and nnn.mmm.kkk.255 are reserved.)
Class D addresses (most significant byte between 224 and 255) are reserved for IP multicasting. This limited form of IP broadcasting is of no concern for most WinSock programmers.
Once the destination address of a packet is known, hosts on an internet can route the packet as appropriate. For most hosts, routing involves either forwarding a packet to destination that is known to the host or passing it along a default route. For example, if you have a class C subnet that is connected to an Internet service provider via a SLIP or PPP connection, this connection is your default route; any packets that are not addressed to a host on your subnet will be automatically forwarded to your provider.
Larger hosts maintain dynamically updated routing tables. Through these tables, the correct route of packets can be identified and in the case of a network error, alternate routes can be found.
Inasmuch as the WinSock programmer is concerned, routing is a transparent activity. You need not be concerned how the Internet accomplishes its magic when delivering a packet to a World Wide Web hosts in New Zealand or an FTP site in Alaska.
It was recognized early during the evolution of the Internet that numeric host addresses may not always be adequate. For one thing, they are difficult to remember; also, if a host's address changes for any reason, the likely result would be endless confusion. Therefore, a naming system was introduced that mapped numeric IP addresses to memorizable host names and vice versa.
In those early days of few hosts on the Internet, every host maintained a file (the hosts file) with a list of all Internet hosts and their addresses. However, as the Internet began its phenomenal growth, this solution quickly proved to be inadequate. First, the naming of Internet hosts had to be standardized; second, a mechanism needed to be found that would eliminate the need to maintain a copy of the hosts file on every Internet-connected computer.
The answer to these problems is the Domain Name System (DNS), development of which began in the early 1980s. DNS is a hierarchical naming system. The format of DNS host names is familiar to everyone who has ever used the Internet: a typical Internet host name is in the form of host.subdomain.domain.
On top of the hierarchy is the root domain, denoted by a single period. Next are the top-level domains. Top-level domain names have been assigned either by organization (mostly in the US) or by country name. Organizational top-level domains include the following:
GOV: Government bodies
EDU: Educational institutions
COM: Commercial enterprises
MIL: Military organizations
ORG: Other organizations
Top-level domains by country usually follow the ISO 3166 standard for two-letter country name abbreviations. Examples include US (USA), CA (Canada), DE (Germany).
How is a domain name translated into a numeric IP address and vice versa? DNS defines a distributed name service mechanism whereby hosts can act as name servers for various domains. Name servers constantly communicate with each other, supplying each other with information on specific hosts. Without going into too much detail, suffice it to say that when your application requests the IP address for a host name, this request is sent to your default name server, which, in turn, may query other name servers on the Internet to get the requested information. Again, the mechanism is completely transparent to the application programmer.
Clearly, knowing the destination host address of an IP packet is not sufficient for most applications. After all, a host can maintain several open connections, be engaged in several TCP/IP conversations at once. When an IP packet is received, how does the host determine which particular conversation would this packet be part of?
In the case of both TCP and UDP packets, in addition to the IP header, the packets also contain additional header information. The first 4 bytes of both TCP and UDP headers contain a 2-byte source and a 2-byte destination port number.
The port number, together with the IP number, identifies a socket that is unique throughout an internet. A pair of sockets uniquely identifies a connection (at least in the case of TCP, a connection-oriented protocol).
Note: TCP and UDP ports are not equivalent. For example, TCP port 25 and UDP port 25 refer to two different entities.
There are many types of services on IP networks that are in widespread use. Examples include FTP, telnet, gopher, the WWW, archie, DNS name service, whois, finger, and many others.
The protocols used for these services are defined in the Internet Requests For Comment documents, or RFCs (some of the most relevant RFCs are listed later in this chapter). The services themselves are usually available on well-known port numbers. For example, to connect to a telnet server on any host on the Internet, you would make a connection attempt to TCP port 23 on that machine. Well-known ports that a host recognizes are typically identified in the system's services file. This file is used in UNIX as well as Windows TCP/IP implementations.
On most systems, TCP and UDP port numbers 0-1023 are reserved for privileged processes.
The Berkeley sockets interface can be used for communicating using both connection-oriented protocols (TCP) and connectionless protocols (UDP). The programming model is the client-server model; servers wait for incoming requests, while clients initiate sessions.
There are some implementation differences between WinSock and the UNIX version of Berkeley sockets. Of these, perhaps the most significant is the fact that socket descriptors and file descriptors cannot be used interchangeably. This has a notable effect when porting applications that make an assumption of this equivalence.
Another difference is that the WinSock library requires to be initialized. Applications that intend to use WinSock functions must first call the WSAStartup function; when their work with the WinSock library is finished, they should call WSACleanup for proper termination.
The WinSock API also introduces several WinSock-specific functions for performing asynchronous I/O on sockets. This assists in the development of responsive GUI WinSock applications.
The WinSock library is initialized by a call to WSAStartup. The application calling this function provides the address to a WSADATA structure, which will hold initialization information.
During the call to WSAStartup, applications and the WinSock library negotiate a version number. The initialization request fails if there is no overlap between the version number supported by the application and the version number supported by the WinSock library.
If an error occurs, WSAStartup returns a nonzero value. Applications may retrieve extended error information through the function WSAGetLastError.
A socket is created by a call to the socket function. Parameters to this function indicate the type of the socket, the type of the network address, and the protocol being used. For example, the call
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
creates a TCP socket.
To associate a socket with an actual host address and port number, applications typically call the bind function. In addition to the socket identifier, or socket descriptor (which is returned by the socket call), bind takes a parameter that is a pointer to a structure describing the socket address. This structure is defined as follows:
In order to assign meaningful values to the host address field in the sockaddr_in structure, we must first obtain a 32-bit host address. To obtain an address when the symbolic name of a host is known, use the gethostbyname function.
When calling gethostbyname, applications pass the symbolic name of the host and receive a pointer to a hostent structure in return. The hostent structure is defined as follows:
char FAR * h_name;
char FAR * FAR * h_aliases;
char FAR * FAR * h_addr_list;
This structure is necessary because a host name may be associated with several host addresses (the reverse is also true). In most cases, applications just take the first (often only) address in h_addr_list; to make this easier, the symbol h_addr is defined as h_addr_list.
If an application wishes to use a numeric address instead, it can use the inet_addr function to convert a string containing a numeric address into a 32-bit address value. Once that value has been obtained, gethostbyaddr can be used to return a hostent structure for the specified host.
A problem with particular importance when it comes to designing applications that are expected to work on hybrid networks is the issue of byte ordering. Some system architectures as big-endian (most significant byte comes first), others are little-endian (most significant byte comes last). Examples of the latter include the Intel family of processors and DEC CPUs; examples of the former include the Motorola 68000 processor family.
Internet numbers (for example, host addresses) are always big-endian. To ensure correct conversion between machine-independent internet numbers and their machine-dependent representation, you can use the following set of functions: htonl, htons, ntohl, and ntohs. These functions connect short or long integers from network to host format or vice versa. Note that these functions may be implemented as macros.
In the case of the connection-oriented TCP protocol, the server application binds to a specific TCP port and then uses the listen function to indicate its willingness to accept incoming connection requests. Immediately after the call to listen, the server calls accept to wait for incoming connections. When accept returns, it provides the address of the peer process.
The client, after creating a socket using the socket call, can immediately initiate a connection using the connect call. It is not necessary to bind the socket to a specific port using bind prior to calling connect.
Once the connection has been successfully initiated, both the client and the server can use the send call to transmit data and the recv call to receive data. The semantics of send and recv are similar to the semantics of the read and write calls for low-level file I/O. Indeed, on UNIX systems it is possible to use the latter pair of functions to perform I/O on sockets. Unfortunately, as I mentioned earlier, this is not possible with WinSock, due to the differences between a file descriptor and a socket descriptor. For the same reason, it is not possible to use the close system call to close a socket; applications must use closesocket when a socket connection is about to be terminated. Either the client or the server can terminate the connection using closesocket.
Figure 39.3 provides an overview of setting up and using a TCP connection.
In the case of the connectionless UDP protocol, the sequence of events is somewhat different; the activities of the client and server application are more symmetrical. In this case, both the client and the server create their respective sockets and bind those to specific port numbers. The server then makes a call to the recvfrom function, which waits for any incoming data. The client, in turn, uses the sendto call to send data to a specific address. When this data is received by the server, the recvfrom call returns, and the server also obtains the address where the data has been received from. It can then use this address in a subsequent call to sendto, as it replies to the client. Figure 39.4 presents an overview of this process.
In the simplistic models shown in Figures 39.3 and 39.4, both the client and the server use blocking calls when waiting for data. A blocking call does not return to the calling function until the requested data becomes available. In other words, the application that makes such a call becomes suspended until the call is completed.
While this model will suffice in many simple situations, it is clearly unacceptable for interactive applications. Such an application (for example, a telnet client) cannot simply freeze until data becomes available from the server.
The solution employed by most UNIX TCP/IP applications relies on the select system call. This call makes it possible to wait on multiple (file or socket) descriptors. This way, a UNIX process can easily wait for data on both a socket or the standard input and spring into action whenever data is received on either of them.
Unfortunately, things are not this easy with WinSock. Remember that socket and file descriptors are not interchangeable? Unfortunately, select is no exception; it can only wait on multiple socket descriptors, not on a mix of socket and file descriptors.
While it is possible to monitor a socket while polling, this is not a very efficient solution. Fortunately, Win32's multithreading capability comes to our rescue. A process can easily start additional threads and have a separate thread for each input source. This mechanism works well for both command-line and graphical TCP/IP utilities. Nevertheless, the WinSock library offers yet another family of functions that assist in writing well-behaved TCP/IP applications without having to resort to multithreaded trickery. These asynchronous socket calls can thus also be used in 16-bit programs and programs intended for Win32s.
Asynchronous socket calls rely on the Windows message passing mechanism to communicate socket events to Windows applications.
At the center of this mechanism is the WSAAsyncSelect function call. Through this function, an application can wait for a combination of socket events. Applications may receive notifications indicating readiness for reading, writing, incoming and completed connections, and socket closure. (This call can also be used for notifications regarding out of band data, something that we have not discussed in this section.) The notification takes place in the form of a user-defined message that is posted to a window, also defined in the call to WSAAsyncSelect.
WSAAsyncSelect posts a single message for every event the application expressed an interest in. Once the message has been posted, no further messages will be posted for the same event until the application implicitly resets the event by calling the appropriate socket library function. For example, if a notification for incoming data was posted, no further such notifications will be posted for the given socket until the application retrieves that data with a call to recv or recvfrom.
Other asynchronous socket functions include, for example, asynchronous versions of the standard Berkeley gethostbyname and gethostbyaddr calls: WSAAsyncGetHostByName and WSAAsyncGetHostByAddr. WinSock applications can also influence the blocking mechanism used in the standard Berkeley-style calls by using the WSASetBlockingHook function.
It is time to put all this theory into practice. I decided to include a simple WinSock-based command-line utility. This program connects to a well-known service (the time service on TCP port 37) on the specified host and retrieves the current time in machine readable form, which it then formats and displays. With little modification, this application could be used to set the system time from a server that is known to keep reliable time.
The program shown in Listing 39.1 can be compiled from the command line by typing cl gettime.cpp wsock32.lib.
The time service is described in RFC868. An Internet time server listens to TCP port 37; when it receives a connection request, it sends the current time (GMT) as a 4-byte number representing the number of seconds elapsed since January 1, 1900.
The gettime application in Listing 39.1 requires a single command-line parameter, the name of the host to which it should make a connection attempt. After verifying that this parameter has been supplied, it makes an attempt to initialize the WinSock library.
Next, the host name supplied by the user is converted to a host address. (No attempt is made to resolve numeric addresses that the user might supply.) Next, a socket is created, and a connection attempt is made to the designated host on TCP port 37.
If the connection is successful, the program tries to receive exactly four bytes of data. When the receive operation is complete, the data is converted (ntohl); the time base is corrected (Win32 time functions use January 1, 1970 as the base date for time variablesthe number of seconds from January 1, 1900 and January 1, 1970 is 2,208,988,800); and finally, the time is displayed as a human-readable local time on standard output.
Before exiting, the program closes the socket and terminates the session with the WinSock library.
The Microsoft Foundation Classes library encapsulates socket functionality in the CAsyncSocket class. Member functions of CAsyncSocket correspond to many of the standard and asynchronous functions in the WinSock library.
An application wishing to use CAsyncSocket must first initialize the socket library via a call to AfxSocketInit; there is no need to call a corresponding cleanup function.
Typically, applications would derive a class from CAsyncSocket and override several callback functions that facilitate asynchronous operations.
The skeleton for this application can be created through the MFC AppWizard. Create a new project with the name GT. Specify that its user interface be dialog-based, and make sure that in Step two, you check Windows Sockets support. Set the dialog's title to a meaningful text string, such as "TCP/IP GetTime".
After AppWizard has finished creating the skeleton, the first step is to update the application's dialog. Add the fields shown in Figure 39.5. The name of the text field holding the host name should be IDC_HOST; the name of the text field holding the time should be IDC_TIME. The read-only attribute for IDC_TIME should be set. The Connect button's identifier should be IDC_CONNECT; the Close button can keep the IDCANCEL identifier.
Next, add three member variables using the ClassWizard. Two string variables (m_sHost and m_sTime) will correspond to the two text fields; the third variable, m_cConnect, will be of type CButton and correspond to the Connect button.
While using the ClassWizard, add also a message handler for the IDC_CONNECT button. This handler, CGTDlg::OnConnect, should process BN_CLICKED messages. It is in this handler function where we add most socket-related code.
To implement our time retrieval functionality, we need to derive a class from CAsyncSocket and create an object of this new type when the user clicks on the Connect button. The code that needs to be added to the dialog implementation file GTDlg.cpp is shown in Listing 39.2.
Listing 39.2. MFC gettime: the CGTSocket class and the CGTDlg::OnConnectfunction.
In the function CGTDlg::OnConnect, a new object of type CGTSocket is created. This type is derived from CAsyncSocket. The actual socket is created through a call to the Create member function, which is followed by a call to AsyncSelect. This call enables the callback functions OnReceive and OnClose. At this time, the Connect button is also disabled. Finally, the Connect member function is called to initiate a connection to the destination.
When the connection attempt succeeds, the time server immediately sends the time in the form of four data bytes. This triggers execution of the OnReceive function, which reads this data using Receive and uses the result to update the time field in the dialog. After sending the data, the server closes the connection; this results in a call to OnClose, which re-enables the Connect button in the dialog and destroys the CGTSocket object.
Note that in its present form, this program does not perform any error checking and is likely to fail if a network error occurs.
The purpose of the CAsyncSocket class is to provide a low-level interface to the WinSock library. In contrast, the CSocket class, which is derived from CAsyncSocket, provides somewhat higher level functionality.
Unlike CAsyncSocket, CSocket provides blocking. Its member functions do not return until a requested operation has been completed.
NOTE: The callback functions OnConnect and OnSend are never called for CSocket objects.
One particular use of CSocket objects is in conjunction with CFileSocket objects to enable the MFC serialization functions to work on sockets. A CFileSocket object can be attached to a CArchive and a CSocket; afterwards, data can be sent and received by simply using MFC serialization.
TCP/IP programming is a very broad subject with a tremendous amount of literature. This chapter is certainly not sufficient to do this complex topic full justice. Because of this, I decided to include some references that I hope assist you in finding the information you need.
For WinSock, the authoritative reference is Microsoft's WinSock specification, which is published on the Microsoft Developer Network CD-ROM.
There are several good guides on the subject. One particularly useful guide is Black's TCP/IP & Related Protocols (McGraw Hill, 1994). For more information on TCP/IP programming (and, in particular, programming with Berkeley sockets), see UNIX Network Programming by W. Richard Stevens (Prentice Hall, 1990). Although the guide (as its title implies) is aimed at the UNIX programmer, much of the discussion is not UNIX-specific, simply because the respective APIs are vendor-independent.
The official definitions of most Internet-related standards and protocols are found in the form of RFCs (Requests For Comment). These RFCs are available online. On the World Wide Web, use the following URL: http://www.cis.ohio-state.edu/htbin/rfc/rfc-index.asp. RFCs can also be requested by Internet e-mail from the InterNIC Directory and Database Services mail server. Send a message to email@example.com with the following command in the message body:
To obtain the RFC index, use the command
Several documents can be requested in a single mail if you separate their names with commas or include several document-by-name commands in the message body in separate lines.
The RFCs have also been published in CD-ROM format by a number of CD-ROM publishers (the one I use often is the Standards CD-ROM from InfoMagic). If you intend to do serious TCP/IP programming, I strongly recommend that you acquire a low-cost CD-ROM like this one.
Internet RFCs go back a long time. The earliest RFC on my CD-ROM, RFC0003, dates back to April 1969! Of course, many of the RFCs have long become obsolete.
RFCs that define the standards most commonly encountered by the WinSock programmer are as follows:
RFC0792, "Internet Control Message Protocol" (J. Postel, 1981)
RFC0793, "Transmission Control Protocol" (J. Postel, 1981)
RFC0826, "Ethernet Address Resolution Protocol: Or Converting Network Protocol Addresses to 48.bit Ethernet Address for Transmission on Ethernet Hardware" (D. Plummer, 1982)
RFC0903, "Reverse Address Resolution Protocol" (R. Finlayson, T. Mann, J. Mogul, M. Theimer, 1984)
RFC1034, "Domain namesConcepts and Facilities" (P. Mockapetris, 1987)
RFC1035, "Domain namesImplementation and Specification" (P. Mockapetris, 1987)
Two additional RFCs deserve mentioning. RFC1700 ("Assigned Numbers", J. Reynolds, J. Postel, 1994) contains all "well-known" numbers including protocol identifiers, port numbers, and the like. RFC1800 ("Internet Official Protocol Standards," J. Postel, 1995) is an invaluable reference to all the other RFC standards.
NOTE: A popular misconception is that any material published in the form of an Internet RFC represents a standard. This is not so. In addition to standards, Internet RFCs may contain Informational, Experimental, Standards Track (Proposed Standard, Draft Standard, Internet Standard), or Historic material. The nature of an RFC is usually noted on the front page.
TCP/IP is the name most often used to refer to the internet protocol suite. This protocol suite, which consists of protocols such as TCP, UDP, ICMP, IP, ARP, and RARP, provides the fundamentals for internetwork communication on the global Internet.
Under Windows, TCP/IP programming is accomplished using the WinSock library. WinSock is an API that closely mimics the Berkeley sockets API used on many UNIX systems. However, there are some differences arising from the fact that Windows and UNIX are very dissimilar architecturally. In particular, the Berkeley sockets library uses socket descriptors that are interchangeable with file descriptors used in low-level I/O operations. For this reason, standard C low-level I/O functions can be used on Berkeley sockets, something that is not true under Windows.
Berkeley sockets can be used to communicate using both connection-oriented protocols, such as TCP, and connectionless protocols, such as TCP/IP. In both cases, fundamental operations include the creation of a socket, binding a socket to a host address and port number, and sending and receiving data.
Host addresses can be obtained using the gethostbyname function that resolves symbolic system names into 4-byte TCP/IP addresses. To ensure machine-independent representation of host addresses and other numeric quantities, a set of functions is provided for the translation to and from host byte ordering and internet byte ordering.
Writing responsive applications requires that an application does not remain suspended indefinitely while waiting for socket input. The most generally used Berkeley sockets mechanism, namely the use of the select function, has limited utility under Windows as socket and file descriptors are not interchangeable, hence select cannot be used to wait for input on stdin, for example. However, Win32 applications can use multiple threads to furnish data exchange on multiple I/O channels simultaneously.
To facilitate the development of responsive applications without relying on the multithreaded capabilities of Windows 95 or Windows NT, applications can use asynchronous socket calls. These calls rely on Windows messaging to deliver information about incoming data or other socket events to the application.
Socket functionality is encapsulated in the CAsyncSocket class in the Microsoft Foundation Classes library. With the help of the CSocket class, which is derived from CAsyncSocket and provides synchronous socket I/O, and the CSocketFile class, applications can use MFC serialization functions on a socket interface.