[Index of Books][Next Chapter]

TCP/IP for DOS Socket Library Reference

Chapter 3. Application Programming Interface

This chapter explains how network applications can use the TCP/IP for DOS Socket Application Programming Interface (API). It discusses how the API software processes socket library function calls. Also covered are the interaction between client and server application programs, and how the socket library functions can facilitate this interaction.

A brief description of the Internet protocol family as defined by the API, Internet socket types, and the associated protocols is presented. This discussion is followed by a description of the programming considerations for the two types of included socket libraries: libraries for use with DOS programs and libraries for use with Windows programs. The chapter discusses three kinds of interfaces to the transport system (synchronous blocking, synchronous non-blocking, and asynchronous) and gives some guidelines for choosing an interface. Finally, the chapter discusses methods for handling errors returned by library functions. If you are already familiar with BSD Sockets programming, you may want to concentrate on Appendix A, which contains the reference pages for the functions.

A typical network application consists of two distinct programs: a client program and a server program. The client program runs on your workstation and gives you access to a server program. The server program is executed only when you require its services and you invoke the client program to communicate with the server program.

The client program can present a user interface, as in the case of terminal emulation where you seek to connect your workstation to a remote host. In other cases, the communication is a small part of a larger computation, and exchanges between the client and server are less visible. An example is a spelling checker that consults a large shared server-based dictionary.

Networking Programs and Sockets

Networking programs are composed of three parts:

Application-Specific Modules

The application-specific modules provide the unique functionality of the specific application on the network. These modules include user interface, non-network I/O support, application layer protocol software, and application-specific computations. For example, an FTP-based file transfer utility has specific modules to transfer files over the network. It contains an FTP protocol engine and local OS-specific functions that read and write files. These modules communicate with a compatible counterpart on the remote computer. For example, an FTP client program communicates with an FTP server program, not with a Telnet server program. This toolkit provides a library of functions to help you develop application-specific modules.

Transport Module

The transport module provides the communication path between the computer where the program (client or server) resides and the various other computers on the network. On each communicating computer, protocol-compatible transport modules must be available and used by cooperating network programs. This toolkit interfaces with a TCP/IP transport module, so that programs based on this toolkit can communicate with other programs that use TCP/IP.

Transport Interface

The transport interface defines the way programs access the transport module. The transport interface maps the program and transport communication endpoints onto one another. It lets the program send data to, and receive data from, the transport module. It also lets you control how the transport module communicates with the program. The socket library in this toolkit is a transport interface.

The use of a particular transport interface does not restrict the program from communicating with protocol-compatible programs on other hosts that use a different transport interface. For example, an FTP client program based on a 4.3BSD socket API can communicate with an FTP server application based on Transport Layer Interface (TLI) streams, as long as both transport modules are TCP/IP compatible. Both programs communicate according to the FTP standard, and that is what determines interoperability.

The communication endpoints for a TCP/IP transport module are defined by an IP address and port number. The socket library provides a mapping between communication endpoints and programs. A program allocates a socket and then binds it to an IP address and port for the program's use. The socket is then used to let the program send commands to the TCP/IP transport, send data to a remote endpoint, and receive data from the local endpoint. The socket lets the TCP/IP transport keep track of programs and their respective ports. The program need only keep track of its allocated sockets.

Socket Library Operations

The following list summarizes the usual order of operations when using the socket library to communicate with remote programs.

Table 1-1 illustrates the typical order of library function calls in a network program that uses stream sockets.

Table 1-1. Typical Library Function Order for Stream
Function Client Program Server Program
Allocate a socket socket() socket(), bind()
Initiate conversation connect() listen(), accept()
Transfer data soread() soread()
Conclude soclose() soclose()

Socket Types

Sockets are categorized into three types:

All network programs use either stream or datagram sockets. At this time, the toolkit does not support raw sockets.

NOTE: Throughout the remainder of this manual, references to sockets imply stream and datagram sockets only.

Sockets send data using the following socket library functions:

Sockets receive data using the following socket library functions:

Only connected stream sockets can use the following socket library functions:

Translation Between Hostname and Internet Address

A user typically specifies the host address as a symbolic name, but the system functions require the Internet address. The toolkit provides two methods for translating the symbolic name into an Internet address:

The sections that follow describe these two methods.

DNS Query

You can use the name resolver functions to resolve the mapping of a hostname to an internet address provided your workstation meets the following configuration requirements:

You can use the rhost() function from the DOS or Windows socket library to translate a name into an Internet address. (These functions are not part of the standard 4.3BSD socket interface.)

The rhost() function first queries the name servers on the network, and requests that the name servers resolve the name into an Internet address.

The rhost() function uses res mkquery() to form Domain Name System messages and res send() to send the messages to the name server(s). If no name server can resolve the query, the rhost() function looks the name up in the HOSTS file, using the gethostbyname() function.

HOSTS File Lookup

You can use the gethostbyname() function to translate from hostname to IP address. The gethostbyname() function has the following form in the DOS socket library:

result = gethostbyname(name) struct hostent *result; char *name;

The gethostbyname() format is similar in the Windows socket library:

result_value = gethostbyname(name) HANDLE result_value; LPSTR name;

This function searches the HOSTS file for an entry that matches the name argument, and returns a pointer or a global variable handle to a hostent structure containing the separated fields of the HOSTS file entry. The 32-bit Internet address, represented in network byte order, is in the hostent.h_addr_list field.

For more information on HOSTS file access or use of the hostent.h_addr_list field, refer to the gethostbyname() reference page in Appendix A and the HOSTS file reference page in Appendix B.

Socket Operations by the Client Program

Every client program typically performs a sequence of socket operations when it starts running, as indicated in the following list:

The following sections describe each of these functions. For more information, refer to the appropriate function reference pages in Appendix A, and to the SOCKET.H header file.

Allocating a Socket with the socket() Function

A socket() request allocates a socket on the local host. This function performs two operations:

The socket() function has the following format:

s = socket (domain, type, protocol)

The s variable returned by the function is a socket descriptor, which is similar to a file descriptor. A positive value indicates that the socket has been successfully opened. A negative value indicates an error.

The domain parameter is the protocol family. The protocol family must be PF_ INET.

The type parameter specifies the type of socket (TCP or UDP) to be used.

The currently defined socket types are listed in Table 1-2.

Table 1-2. Socket Type Values
Type Value Meaning
Stream (SOCK_STREAM) 1 For a reliable, sequenced, two-way connection-oriented virtual circuit
Datagram (SOCK_DGRAM) 2 For connectionless, potentially unreliable messges of a fixed maximum length (typically 512 bytes). Datagram sockets use UDP.
Raw (SOCK_RAW) 3 For direct access to the IP. The protocol field corresponds to the IP protocol type field found in the header of IP datagrams. (This socket type is used only by the PING program and is not discussed further.)

The protocol parameter is the particular protocol to be used with this socket. Normally, only a single protocol exists to support a particular socket type within a given protocol family. The value of this parameter should be zero, indicating that the default value should be used.

Binding a Socket with the bind() Function

A bind() function on a socket associates a network address and port with the socket. The function has the following format:

status = bind(s, name, namelen)

The status variable is the status code returned by the function. A value of 0 indicates that the binding was successful. A value of -1 indicates that the binding was unsuccessful.

The s parameter is the socket descriptor. This is the value previously returned by the socket() function.

The name parameter is a pointer to a structure containing a network address in the PF_INET domain. Either the IP address or the port number, or both, can be specified as zero. If the structure specifies an IP address of zero, the socket library assigns the local host IP address. If the structure specifies port number zero, the socket library assigns an unused temporary port number, not a well-known port number. Port numbers 1 through 1024 are reserved for well-known ports, and numbers greater than 1024 are used for temporary port numbers.

Typically, a client program uses zeroes for both IP address and port number in the structure. This usage allows the client communication endpoint to be unique for each session of the network application running with the same server.

The namelen parameter is the size, in bytes, of the structure pointed to by name.

Connecting a Socket with the connect() Function

A connect() function on a socket forwards a request to the TCP/IP transport system directing that an end-to-end connection be established between the client and the server. The function has the following format:

status = connect(s, name, namelen)

The status variable is the status code returned by the function. When the program calls connect(), the protocol module attempts to open the specified connection. When a connection is successfully established, a value of 0 is returned in status. A value of -1 is returned if the connection attempt fails.

The s parameter is the socket descriptor for the socket on the local workstation. This value is returned by the previous call to the socket() function.

The name parameter is a pointer to a structure containing a network address in the PF_INET domain. The client program specifies the IP address of the host running the server and the port on which the server is listening. The client must not use zeroes for these two fields.

The namelen parameter is the size in bytes of the structure pointed to by name.

When your program calls connect(), the protocol module attempts to open the specified connection. If the socket is already connected, a value of -1 is returned and the global variable errno is set to EISCONN. If a connection cannot be established, errno is set to ETIMEDOUT or ECONNREFUSED.

Reading and Writing Sockets with the soread() and sowrite() Functions

After you have established a connection, the soread() and sowrite() functions read and write data as though a direct full-duplex data path exists between the two processes. Both reading and writing occur on the same logical data stream.

The soread() function has the following format:

cc = soread (s, buf, nbytes)

The data is read from socket descriptor s, buf is a pointer to the buffer to receive the data, and nbytes is the number of bytes to read, up to a maximum of the size of the buffer.

The sowrite() function has the following format:

cc = sowrite(s, buf, nbytes)

The s parameter is the socket descriptor to which the data is written, buf is a pointer to the buffer containing the data, and nbytes is the number of bytes to write, up to a maximum of the size of the buffer.

Upon return, cc contains the number of characters actually read from or written to the logical data stream, or a value of -1 if an error was detected.

The application program is responsible for synchronizing its communication so that reading and writing do not cause a deadlock. The TCP/IP transport system guarantees reliable delivery of data. Thus, a simple command and response exchange is easy to implement.

An error can occur when reading or writing if the remote system is inaccessible for any reason: the cable fails, an intermediate gateway fails, the remote system malfunctions, or the remote

program terminates.

In addition to soread() and sowrite(), the socket library provides a number of other functions for performing network I/O including readv(), recv(), recvfrom(), recvmsg(), send(), sendmsg(), sendto(), and writev(). Refer to Appendix A for descriptions of these functions.

Closing a Socket with the soclose() Function

When the communication is concluded, the socket must be closed. Before closing the socket, a well-behaved client program informs the server that it is done. An error occurs if the remote host attempts to read or write after the connection is closed.

The soclose() function has the following format:

return_value = soclose(s)

The s parameter is the descriptor for the socket being closed.

The return_value parameter indicates whether or not there was an error closing the socket. A value of 0 indicates that the close was successful; a value of -1 indicates an error.

When a program terminates, it should close all sockets that it has allocated. Otherwise, the resources used by the socket are not freed in the library and the TCP/IP transport, and subsequent programs might be unable to open sockets. If a program needs to reallocate and reinitialize a socket before terminating, the socket must first be closed and then reopened by the program.

Socket Operations from the Server Program

The server typically performs the following sequence of operations:

A server program uses the socket() and bind() functions in the same way as a client program; however, with bind(), the server specifies the well-known and predefined port number that the client uses with the connect() function. Each server offering a specific service (for example, a file transfer server application) should be assigned a predefined port where only servers of that type listen.

Queuing Connection Requests with the listen() Function

A listen() function on a socket creates a queue for incoming connections for the socket. This function call must be issued before issuing any calls to accept(). The function has the following format:

status = listen(s, backlog)

The status variable is the status code returned by listen(). A value of 0 indicates that the function was successful. A value of -1 indicates that the function was unsuccessful.

The s parameter is the socket descriptor. This value was returned by the previous call to the socket() function.

The backlog parameter is an integer that specifies how many incoming connections can wait in the queue before being processed by calls to accept(). Each call to accept() removes one connection from the backlog queue. The value of backlog must be 0 or greater and must be less than 6.

Processing Queued Connection Requests with the accept() Function

An accept() function takes connection requests from the queue created by listen() and creates a socket for each request. The socket created by accept() is the server's communication endpoint for that connection request from the client. The function has the following format:

s2 = accept(s, addr, addrlen)

The s2 variable is the descriptor of a socket created by the accept() function with the same attributes as s. The s2 descriptor must be used for all I/O on this particular connection. A positive value indicates acceptance and represents the new socket descriptor. A value of -1 indicates nonacceptance.

The s parameter is the original socket descriptor, which is bound to the well-known port number. This value is returned by the previous call to the socket() function and is also used in the listen() function call.

A listening socket s continues to queue connection requests from remote clients until the queue backlog is reached or the socket is closed; therefore, multiple accepts can be performed on a single listening socket. Each accept() function removes one request from the queue and allows new connection requests to be admitted to the queue. This capability enables multiple clients to connect to a single well-known port on the server, and for the server to process the requests on a first-in, first-out (FIFO) basis.

The addr parameter is a result parameter pointing to a sockaddr structure. Upon successful completion, this structure is filled in with the Internet address and port used by the client program.

The addrlen parameter is a pointer to an integer variable that initially contains the length in bytes of addr. After the accept() function completes, addrlen contains the length of the resultant Internet address.

Concluding the Socket Operation

Depending on the design of your application, either the client or the server can close the connection first.

After a client and server have established a connection, the server transfers data with soread() and sowrite(), in the same manner as the client program. If the server initiates the connection close, a well-behaved program always informs its client before it calls the soclose() function. If the client initiates the close, it should also announce its intent to close the connection; the server should then call the soclose() function.

Protocol Families

Every network protocol is associated with a specific protocol family. A protocol family normally consists of several protocols, one per socket type, but each protocol family is not required to support all socket types. Multiple protocols within the same protocol family can support the same socket type.

To access a specific protocol, you request the appropriate protocol family and socket type when you create a socket. Each protocol normally accepts only one address format, usually determined by the inherent addressing structure of the protocol family and network architecture. Certain semantics of the basic socket types are protocol-specific. Each protocol supports the basic model for its particular socket type and can also provide nonstandard facilities or extensions.

The toolkit currently supports only the Internet protocol family, which has the following identifier:

#define PF INET 2 /* internetwork: UDP, TCP, etc. */

The Internet protocol family includes the following protocols:

TCP supports the stream socket type (SOCK_STREAM), while UDP supports the datagram socket type (SOCK_DGRAM). IP underlies both of these socket types. It provides end-to-end data delivery over a series of connected networks using (potentially) different media. ICMP, ARP, and RARP are not directly accessible; they are used internally between peer protocol stacks for management and control.

Refer to the TCP/IP for DOS Administrator's Guide for more information about Internet protocols.

Network Address Data Structures

sockaddr Structure

#include <SYS/SOCKET.H>

struct sockaddr { short sa_family; /* address family */ char sa_dat

The sockaddr structure uses the following variables to store addresses:

The SOCKET.H include file has the type definitions for pointers to sockaddr structures, which can be used for declarations. For the DOS socket library, it has the following type definition:

typedef struct sockaddr *SADDR_PTR

For the Windows socket library, it has the following type definition:

typedef struct sockaddr FAR *SADDR_PTR

sockaddr_in Structure

#include <SYS/SOCKET.H>

struct sockaddr in { /* to represent an Internet address */ short

The sockaddr_in structure uses the following variables to handle addresses:

You can declare a sockaddr_in structure and use the member fields to assign the values for port number and Internet address. You can then pass the structure to a socket library function by casting it as a struct sockaddr.

Transmission Control Protocol (TCP)

The Transmission Control Protocol (TCP) provides reliable, flow-controlled, two-way data transmission. TCP is a byte-stream protocol supporting stream (SOCK_STREAM) sockets. The socket address of each TCP socket is a unique identifier formed from the host's IP address and the TCP port address.

Sockets using the TCP protocol are either active or passive. Active sockets initiate connections to passive sockets. The functions called determine whether that socket is active or passive. To create a passive socket, call the listen() function. Only passive sockets can use the accept() function to accept incoming connections. To create an active socket, call the connect() function to initiate connections.

When specifying the local address in the bind() parameter addr, you should normally specify all zeroes. The protocol code then assigns the local address. You can still specify the TCP port in the sin_port field of the bind() parameter addr. If this field is 0, the system assigns a port number.

Once a connection has been established, the connection is uniquely identified by four components:

To distinguish among multiple connections between the same client and server, it is necessary that the client port be unique for each application.

To allocate a socket using TCP, use the following header file, structure declaration, and the socket() function call.

TCP Socket Allocation

#include <SYS/SOCKET.H>

int socket_id, return_code; struct sockaddr my_socket = [ PF_INET, 0, 0 }; socket_id = socket (PF_INET, SOCK_STREAM, 0); return code = bind (socket_id, &m

The TCP implementation in the toolkit supports one nonstandard feature: keep-alives. Keep-alives are packets that check whether a TCP peer is still functioning. They are enabled by setting the setsockopt() function's SO_KEEPALIVE option. Keep-alives periodically poll the remote system if the connection has been idle. Keep-alive packets are transmitted on a connection that has been idle for longer than one minute. If there is no response within four minutes, the connection is aborted. This timeout applies only to an established connection.

TCP implementations that do not closely follow the TCP specification might fail to respond to keep-alive messages, causing connections to be closed without apparent reason.

One of the error conditions listed in Table 1-3 could occur when you create or use a TCP socket.

Error Code Explanation
EADDRINUSE An attempt was made to bind a TCP socket to a port that was already allocated.
EADDRNOTAVAIL An attempt was made using socket() to create a TCP socket with an invalid network interface.
ECONNREFUSED The remote peer refused to establish a connection in response to a call to connect(). This error usually occurs because no process is listening to the port.
ECONNRESET The remote peer forced the connection to be closed. This error can occur any time during or after establishment of a connection.
EISCONN An attempt was made using connect() to establish a connection on a socket that was already connected.
ENOBUFS The system does not have memory for an internal data structure, This error can occur any time during and after establishment of a connection.
ETIMEDOUT The connection was dropped due to excessive retransmissions. This error can occur any time during or after establishment of a connection. It often indicates that the IP address is invalid, or that the host at the IP address is not active.

User Datagram Protocol (UDP)

The User Datagram Protocol (UDP) is a simple, potentially unreliable datagram protocol supporting SOCK_DGRAM sockets for the Internet protocol family. UDP sockets are connectionless and normally use the sendto() and recvfrom() functions. You can use the connect() function to set a destination for future packets, thereby allowing use of the send(), recv(), soread(), and sowrite() functions.

UDP address formats are identical to those used for TCP. UDP provides a port identifier in addition to the normal Internet address for the host. UDP port space is distinct from TCP port space; therefore, a UDP port number can have the same value as a TCP port number. A UDP port cannot be connected to a TCP port.

UDP Socket Creation

#include <SYS/SOCKET.H>

int socket_id; struct sockaddr my_socket = { PF_INET, 0, 0 }; socket_id = sock

The error codes listed in Table l -4 are associated with creation and use of UDP sockets.

Table 1-4. Error Codes Associated with UDP Sockets
Error Code Explanation
EADDRINUSE An attempt was made using bind() to bind a UDP socket to a port that was already allocated.
EADDRNOTAVAIL An attempt was made using socket() to create a UDP socket with an invalid network interface.
ENOBUFS The system does not have memory for an internal data structure.
ENOTCONN An attempt was made to send a datagram on an unconnected socket without specifying the destination address.

Programming Environment

For programs written for DOS, there are eight socket libraries, each corresponding to a different memory model and C compiler:

The installation procedure places these files in the \NET\TOOLKIT\LIB directory. Each of these libraries is provided as a static link library that you link with your other modules when you create your program.

The Windows Socket Library requires two library files:

WLIBSOCK.DLL is a dynamic link library (DLL). Because it is used by the Windows applications in the TCP/IP for DOS product, it is included in that product rather than in the separate toolkit package. Installation of TCP/IP for DOS places this file in the \NET\BIN directory.

In contrast to a conventionally linked library, a DLL is linked dynamically at run time. Dynamic linking permits all instances of a particular program to access the library functions, and eliminates the need for each instance of the program to contain the library function code. Dynamic link libraries reduce the amount of space required for programs running in the Windows environment, because they contain functions common to more than one program running in the Windows environment.

WLIBSOCK.LIB and TWLIBSOCK.LIB are import library modules. You link one of these import library modules with your other modules when you create your program. The installation procedure places this file in the \NET\TOOLKIT\LIB directory. It contains import definitions for all socket library functions contained in the dynamic link library module.

When a Windows program calls a dynamic link library function in the Windows socket library, it accesses modules in WLIBSOCK.DLL. When the program loads, the DLL is loaded if it has not already been loaded for another program. In order for Windows to load the dynamic link library functions, WLIBSOCK.DLL must be present in the current directory or in a directory in the PATH environment variable.

Compiling Programs

Programs can be compiled using the Microsoft C Compiler v5.1, v6.0, or v7.0; or Borland C++ v3.0 or later.

Although the socket library presents a large model interface, programs written for the DOS environment can be built using any of the standard memory models as long as the SOCKET.H function prototype file is included in each program source file, and the appropriate socket library is linked with the program. SOCKET.H defines necessary function prototypes and data structures.

Define the constant WINDOWS to compile your Windows source code.

You must define the constant WINDOWS with the /D WINDOWS command-line option or with a #define directive at the start of your source files when compiling programs that use the Windows socket library. This definition ensures that you get the correct function prototypes and data structure definitions in the header files.

NOTE: It is no longer a requirement for programs to include the following header files: SYS\IOCTL.H NETINET\IN.H RESOLV.H SYS\FILIO.H ARPA\NAMESER.H SYS\UIO.H SYS\SOCKIO.H NETDB.H The declarations provided in these files in previous versions of the TCP/IP for DOS Toolkit are now located in the SOCKET.H header file. Versions of these files provided in the v4. I toolkit simply include the SOCKET.H file. As a result, programs written for earlier versions of the toolkit continue to compile correctly.

Synchronous and Asynchronous Interfaces

The socket library supports synchronous blocking, synchronous nonblocking, and asynchronous interfaces to the transport system. The socket library processes function calls using each interface differently.

This section describes each of the interface types, and provides an overview of the processing for socket library function calls. It discusses how the program passes a socket library request to the TCP/IP Transport System and how the TCP/IP Transport System responds to the request.

Synchronous Blocking Interface

When the synchronous blocking interface is used, socket library functions block further execution of the program until the transport system completes the requested operation. The calling program regains control only after the function has terminated. Some functions have the potential to block indefinitely; for example, soread() blocks until network data becomes available for the socket. This operation is the default for the synchronous interface.

Synchronous Nonblocking Interface

When the synchronous nonblocking interface is used, socket library functions query the transport to determine whether the requested operation can be completed without delay. If so, the transport is allowed to complete the operation. Otherwise, an EWOULDBLOCK error is returned to the caller indicating that the transport is (temporarily) not in a state to complete the operation. In either case, synchronous nonblocking operations return quickly. The nonblocking interface is enabled individually for each socket with the FIONBIO option of the ioctl() function, as described in Appendix A.

Asynchronous Interface

The asynchronous interface behaves differently from the synchronous interfaces. The socket library initiates the requested operation and immediately returns control to the calling program. The program and the asynchronous socket function then execute concurrently. The asynchronous socket functions are described along with their synchronous counterparts in Appendix A.

NOTE: Asynchronous versions of functions cannot be used in programs that run under standard mode in Windows or under DESQview.

The calling sequence for asynchronous functions is the same as for synchronous functions (blocking and nonblocking), except that the asynchronous functions require three additional parameters:

Return Values from Asynchronous Functions

When you call an asynchronous socket function, it attempts to start the specified network operation. Once the operation is started, or fails to start, execution control is returned to the caller along with a status value. This value is the immediate return value. An immediate return value of 0 indicates that the network operation was successfully initiated. A -1 return value indicates that the socket library has aborted the requested operation.

When each asynchronous function terminates, it sets the value of the result parameter to reflect the result of the completed operation. If no error occurred, the result parameter points to a positive value (the number of bytes actually written, for instance), which is the same value that the synchronous version of the call would return. If the result parameter points to a value of -1, an error has occurred. You should declare the variable result as global so that you can examine it in post-completion functions, either to determine if there was an error, or if there was no error, what the result of the asynchronous operation was and what further action to take.

Some socket functions require updates to user-supplied data structures as well as data buffers and the result field. For example, the recvfrom_anr() function updates a user-supplied socket address structure with the address information of the message sender. It also fills the data buffer with the message received. The functions complete these updates before calling the application program's post-completion function.

Post-Completion Functions for Asynchronous Library Functions

When the transport system satisfies the request from an asynchronous function call, it calls a post-completion function defined in the application program. This call occurs while the transport system has interrupted the program to satisfy the asynchronous request. After the post-completion routine terminates, the transport system returns control to the application program.

The transport system makes a far call to the post-completion function that you have specified for that asynchronous function call. Because the transport system makes a far call, you must declare this function as a far function. (In Windows programs, you must declare it as a FAR PASCAL function.) At the completion of the asynchronous call, and before the call to the post-completion function, the asynchronous function updates the user-supplied result variable to point to the result value of the call. The asynchronous function updates any parameters specific to the function. In the case of the recvfrom anr() function, for instance, it fills the user buffers with the data received and puts the message sender address in the user-supplied socket address structure. In the case of the select anr() function, it updates the user-supplied file descriptors with the appropriate values.

Segment Register Values in Post-Completion Functions

Immediately before the socket library calls the user's post-completion function, it also sets the values of the data segment register (DS), the stack segment register (SS) and the stack offset register (SP). It sets DS to the value of DS at the time that your application called the asynchronous routine. In small and medium model programs, DS always equals DGROUP (see Figure 1-4). The transport system always sets SS equal to DGROUP to use a stack set up by the socket library.

Figure. Stack Location in Post-Completion Functions (Small Model)

<image>

When the socket library is linked to your application, it sets up a separate stack with a default length of 128 bytes in DGROUP. Asynchronous calls need a separate stack because you do not know when the post-completion function will be called after an asynchronous operation begins. Using a separate stack ensures that the stack does not overflow. The socket library sets up the separate stack so that you can be certain of the stack size at the time your post-completion function begins. The socket library sets the stack offset pointer (SP) equal to the value of the global variable _ASYNCHRONOUS_STACK plus the value of _ASYNC_ STACK_SIZE.

Turning Off Stack Checking

You should turn off stack checking in your application programs to prevent compilers from inserting a _chkstk() (check stack) function at the entry to the post-completion function. The check stack function ensures that the stack as referenced by SS and SP is within the segment STACK before proceeding. The check fails in the post-completion function because the stack segment and the stack pointers are not those set in the application, but those set by the socket library in DGROUP.

Programming Restrictions in Post-Completion Functions

Because of the actions on the part of the transport system, you can write your post-completion function entirely in C, with some exceptions. Because the socket library sets the values of SS and SP and those values fall inside DGROUP and not STACK, you cannot use runtime library functions that have stack checking enabled. (You cannot turn off the stack checking already embedded in runtime library functions.)

Do not call any C runtime library functions in your post-completion function unless you are certain that the runtime functions do not perform stack checking.

In v6.0 of the Microsoft C Compiler, the following C runtime functions have stack checking enabled:

execvp() execvpe() fprintf() fscanf() printf() scanf() sscanf()
spawnve() spawnvp() spawnvpe() sprintf() system() vprintf()

There is another restriction on the content of your post-completion routines: you cannot use any synchronous functions from the socket library in your post-completion function. If you use such a function, the function returns with the error ESYNCNOTSUPP. Under most circumstances, you would not want to use a synchronous function in an asynchronous post-completion function because it would defeat the purpose of the asynchronous function. In the normal case, you want to keep your post-completion function short and fast (for example, setting a flag) to avoid keeping the transport system waiting for your post-completion routine to terminate.

Changing the Asynchronous Stack Size

You can specify the size of the stack for asynchronous function calls by changing the size of the array specified by the global variable _ASYNCHRONOUS_STACK and linking the socket library with the /NOE switch.

The two variables that determine the stack have the following declarations in the socket library:

int _ASYNCHRONOUS_STACK[128]={0};int _ASYNC_STACK_SIZE=sizeof(_ASYNCHRONOUS_STACK);

To change the size of the stack for asynchronous functions, you need to declare these variables in your application. To set up a 256-byte stack, for instance, your application's code would need to include the following declarations:

int ASYNCHRONOUS STACK[256]={0};int _ASYNC_STACK_SIZE=sizeof( ASYNCHRONOUS_STAC

You then link your object modules with the /NOE switch. This switch prevents the linker from searching the library's extended dictionary and reporting a conflict between your declaration and the library declaration. For debugging purposes, you may want to fill the stack with the value of a printable character other than zero when it is initialized. A simple stack dump then indicates the stack usage.

Post-Completion Functions for DOS Programs

Post-completion functions for DOS socket library functions take two parameters as arguments: usercb and error code. These two parameters have the following declarations:

unsigned long usercb;

int error code;

You can use usercb to distinguish the current request from other outstanding requests. For instance, the post-completion function can set the value of a variable available to the rest of the program depending on the value of usercb. If the function call returns but the asynchronous operation fails, the call places the error code generated in error_code. The parameter error code contains a value from SOCKET.H that indicates the type of error if there was an error. If there was no error, it has the value EOK (0). The program can use these parameters to determine what further action to take in response to the error. A post-completion function for a DOS socket library function has the following prototype:

int far post routine (unsigned long, int)

Post-Completion Functions for Windows Programs

Post-completion functions for Windows socket library functions work in the same way as those for DOS, but they must use the Pascal calling convention.

A post-completion function for Windows has the following prototype:

int FAR PASCAL post routine (unsigned long, int)

In addition, the name of the post-completion function must appear in the EXPORTS statement in your program's module-definition file.

The Windows socket library function which calls the post-completion function takes the address of the post-completion function as a parameter. This address must be a procedure-instance address of the post-completion function.

You create the procedure-instance address by using the Windows MakeProclnstance() function. This address is declared as FARPROC. Before your application exits, or when a function call concludes, you should use the Windows FreeProclnstance() function to free the procedure-instance address.

Using the Stack with Asynchronous Functions

You should be careful when defining stack variables for result fields and user data structures. You must ensure that the content of the stack variables remains valid for the duration of the network operation. Stack variables are only valid for use with asynchronous operations when execution control remains in the calling function. If you initiate the operation in one function and wait for the result in another function, you must use global variables residing in either statically or dynamically allocated memory.

Interface Selection

In general, using the synchronous blocking interface leads to simpler programming. You could use the slightly more complex synchronous nonblocking interface whenever the program needs to handle two I/O streams at the same time, such as the network stream and the keyboard stream.

You should use the asynchronous interface when your program cannot afford to be blocked for even a very short time while waiting for a network response. The asynchronous interface is useful for multiuser programs with critical response requirements, such as network file servers.

Error Handling

Each program should check the return values from socket library function calls, and take appropriate actions depending on the values returned. The variable errno contains the error code indicating the specific cause of an error. The error codes and their associated symbolic names are found in the include file SOCKET.H provided in the \NET\TOOLKIT\INCLUDE\SYS directory.

The DOS socket library functions and the Windows socket library functions require different methods to determine which error occurred. In addition, the synchronous and asynchronous functions return different values and require different methods to determine which error occurred.

Error Handling for Synchronous Socket Functions

For synchronous socket functions, observe the following guidelines:

Errors for DOS Socket Library Functions

In a program written for DOS, the DOS socket library is linked into your program, and the variable errno is declared as a global variable. Programs can access the variable immediately, and after an error in a synchronous function, they can print the error message text by calling the soperror() function. The soperror() function uses the value of errno to determine which error occurred and which message to print. The call has the following format:

soperror (string)

The message prints in the following format:

string : message

The string is taken from the program. It is a word or words identifying the source of the error. Usually, it is the name of a command or library call. The message is the message text associated with the error code. The soperror() function uses the value of errno to determine which error occurred and which message to print.

For example, if the accept() function received an EWOULDBLOCK error, the soperror() function using accept as the string would print the following text:

accept : Operation would block

Errors for Windows Socket Library Functions

Programs written for the Windows environment use a dynamic link library (DLL). Because the programs and the DLL use different data space, it is not possible to declare global variables directly accessible by library users. This limitation affects error handling, because the user no longer has access to the global variable errno.

The Windows socket library addresses this problem by declaring a variable, errno, and providing the GetErrno() function to access this variable. Whenever a socket call returns an error, the internal variable errno in the DLL is updated. Windows programs that receive an error from a synchronous Windows socket library function can then call the GetErrno() function to determine which error occurred.

Error Handling for Asynchronous Socket Calls

Asynchronous functions have two types of return values: the return value of the function, and the value of a parameter passed as an argument to the function, which the function sets upon completion.

For instance, the DOS socket library function accept anr() has the form illustrated in Example l-5.

accept anr() Function Format

immediate result = accept anr(s, addr, addrlen, result, post routine, usercb)

The return value of the function is immediate_result. If the function returns a value of -1 for immediate_result, the asynchronous operation did not begin. If the function returns a value of 0 for immediate_result, the asynchronous operation is in progress.

If the asynchronous operation terminates successfully, it sets the value of the result parameter equal to the new socket descriptor. Even though the asynchronous operation began, however, it can fail. If it fails, the function sets the value of the result parameter to -1.

Thus, there are two cases of error conditions for asynchronous functions:

To handle the second case in the DOS environment, place your error-handling routines in the post-completion routine. The value of errno is immediately available to the post-completion routine, and you can use soperror() to print an error message, or take any other appropriate action. Alternatively, you can examine the parameter error code in the post-completion routine and take the appropriate action.

You need to examine the value of errno, and take the appropriate actions. The post_routine() function should have a section of code similar to Example 1-6.

Asynchronous Error Handling Routine

if (*result =- -1) { handle the error conditions } else { function body on

To handle the second case in the Windows environment, you must also put your error-handling routines in the post-completion function. If result has the value -l, you should examine the value of error_code, and display a message or take other appropriate action depending on its value.

The post routine() function should have a section of code similar to Example 1-7.

Windows Asynchronous Error Handling Routine

if (*result =- -1) { switch (error code) handle the error conditions } } el