Chapter 2: Developing applications using XTI or TLI

Table of contents

Chapter 2

Developing applications using XTI or TLI

XTI (the X/Open Transport Interface) is a library of functions used to write networked applications over a variety of transports. This chapter provides an overview of XTI, and describes the concepts that underlie writing programs that use it. This chapter also discusses the differences between XTI and two other APIs (application program interfaces) that are also often used for writing network applications: TLI (the Transport Level Interface) and Berkeley sockets. Both TLI and sockets are available on SCO systems.

See also:

Why use XTI?

Use XTI to create C language applications that communicate over a network. By calling XTI functions, a running program (process) can establish a communication link between itself and another process running on a different computer. The two processes can then send data back and forth to each other.

The communication link between the two processes is made possible by software called a ``transport provider.'' The transport provider is responsible for moving data over the network between processes running on different computers. It is also responsible for monitoring error conditions and reporting them appropriately. Examples of transport providers are TCP/IP, IPX/SPX, NetBEUI, and OSI. 

One advantage to using XTI is that this interface is not tied to a single transport provider. You can write programs that will run over any one of the transport providers listed above, then port the programs to run over one of the other transport providers with only modest modifications. See ``Transport-specific issues'' for information about those areas that affect how a network program is written that are outside the scope of XTI.

Note that for two processes on two different computers to communicate, compatible implementations of the same transport provider must be running on both systems.

What XTI can do

XTI allows a process to perform a variety of functions associated with network communication. All of these functions operate on a ``transport endpoint.'' A transport endpoint is a link between a transport provider and the process that wants to communicate over the network using that provider. The transport endpoint is always identified by a file descriptor (an integer index into the system file descriptor table).

Specifically, a process can call XTI functions (listed in parentheses) to 

Network programming concepts

This section describes some of the basic concepts that underly network application programming with XTI.

Client-Server computing

The essence of network programming is to manage the communication between processes executing on different computers. The model that underlies network programming with XTI is called ``client-server.'' In this model, a process on one computer (the server) waits for other processes on other computers (the clients) to contact it. If the server accepts the connection from a given client, it can then perform some service or computation (or both) on behalf of the client.

For example, a file server is a process that offers a service to clients, namely, it transfers files from the server to the client machine. A time-of-day server, on the other hand, performs a computation, namely, it determines the current time of day and sends the result back to the client.

The above usage is more precise than the common practice of confusing a server or client process with the computer on which it is running. For example, the term ``file server'' is often used to refer to the computer on which the file server process is running. However, the same computer can run not only the file server process, but several other kinds of servers (such as a time-of-day server) as well.

Similarly, a computer on which one kind of client is running can also run many other clients. In fact, a server process on one computer can, in order to perform its task on behalf of the client that contacted it, enlist the assistance of yet another server on a different computer. It does this using the same mechanisms that the client on the first computer used. The result is that, while the first server is communicating with the second server, it is actually functioning as a client.

In this model, server processes can be running on many different computers that are attached to many different networks. Whenever a process needs some service or computation performed that it either cannot, should not, or need not do, it simply contacts the appropriate server (wherever it is on the network). Moreover, servers can connect to each other to request services or computations. The entire network then becomes an interconnected set of building blocks that can be called upon in an organized fashion to accomplish complex tasks.

How clients and servers communicate

To contact a particular server, a client first creates a transport endpoint by calling t_open. In this call, the client specifies the transport provider it will use. Each transport provider available is designated by the pathname of a device in the UNIX filesystem. For example, the TCP transport is identified by the pathname /dev/inet/tcp.

The client then calls t_alloc to allocate a data structure to contain address information. The client places into the data structure the address information needed by the transport provider to locate the desired server. The client then calls t_connect to contact the server, passing the data structure to the function as an argument. The next section, ``Transport addresses'', has more information about the address information required.

Before clients can successfully connect to a server, however, the server must be waiting for an incoming connection request. The server first calls t_alloc to allocate a data structure to contain address information. It then calls t_listen (with the allocated data structure as one of its arguments), and waits for a client to contact it. When a connection request arrives, t_listen returns with the client address information in the allocated data structure. The server can either reject the request by calling t_snddis (send disconnect) or accept the request by calling t_accept. 

When a client is finished communicating over the network, it calls t_snddis to abort the connection, t_unbind to disassociate address information from the transport endpoint, then t_close to delete the transport endpoint. If the client is simply finished with the one server and wants to connect to another, it calls t_snddis to abort the connection, followed by t_connect to send a connection request to the new server.

Transport addresses

A network consists of multiple computers (also called ``hosts'') connected to each other in a way that allows one host to communicate with any of the other hosts on the network. Furthermore, a network may be connected to other networks, either directly or indirectly. Note that a single host can be directly connected to more than one network.

The transport provider running on a given host must be able to identify both that host and the network to which the host is attached. Generically, this information is referred to as the ``network address'' and the ``host id.'' Because each kind of transport provider has its own scheme for what network addresses and host ids look like, refer to the chapter in this Guide that discusses the specific transport provider you will be using in your application for more information.

Being able to identify a particular host and the network to which it is attached is not enough, however. With a multi-tasking operating system such as UNIX, many processes (whether clients, servers, or both) can be running on the same host simultaneously. As a result, the transport provider must be able to uniquely identify every process on the host that communicates with the transport provider. Each kind of transport provider defines its own identifier (the ``local process id'') for this purpose. Refer to the chapter that talks specifically about the transport provider you will be using for more information.

Once a process (whether client or server) has opened a transport endpoint with a specific transport provider, it needs to establish the network address and host id of the machine it is running on, as well as the local process id by which it will be known. A process associates this information with the transport endpoint by calling the t_bind function.

The process can either select the network address, host id, and local process id to be used, or it can allow the transport provider to select them. Even if the process makes the selection, the transport provider can override it. In either case, the process can, if it wants, find out the network address, host id, and local process id selected by the transport provider. It does this by providing the appropriate argument to t_bind.

Usually, servers specify their local process id and clients do not. This is because clients are written to contact a particular server using the predetermined local process id of that server. It is crucial, therefore, that the server use the predetermined local process id and no other. On the other hand, when a client connects to a server, the local process id of the client is passed to the server when the server accepts the connection. The actual value of the local process id being used by the client is of no importance.

Modes of service

A transport provider typically offers two different modes of service: ``connection-oriented'' and ``connectionless.'' 

Connection-oriented service

With ``connection-oriented'' service, a client and a server establish a communication path over which they send and receive data. This kind of service is useful when a client and server expect to have an extended dialogue with each other.

One of the characteristics of connection-oriented service is that the communication is sequenced, reliable, and error-free. This means that all data sent by one process is guaranteed to be received by the destination process, that the data arrives exactly in the order it was sent, and that the data arrives without having been altered or corrupted.

A client and a server must cooperate to set up a connection. The server prepares to receive an incoming connection request by calling t_listen. The client then calls t_connect to request a connection with that server. Finally, the server accepts the connection request by calling t_accept. 

Connectionless service

With ``connectionless'' service, the client and server exchange individual messages. This is often appropriate when the service or computation provided by the server requires very little interaction between the server and the client.

For example, a time-of-day server can compute the current time of day and return the result in a single message. Using the connectionless service, the client and server avoid the extra processing performed by the transport provider at both the server and the client to set up a connection.

When no connection is established, each message sent must contain the address of the destination network, host id, and local process id. Similarly, each message received must contain the address of the network, the host id, and local process id of the process that sent the message.

In a typical scenario, the client and server begin by calling t_open and t_bind. The client then calls t_alloc to allocate space for the message to be sent, fills in the data area with the message, then sends the message to the server by calling t_sndudata (send unit data). Meanwhile, the server awaits the incoming message by calling t_rcvudata (receive unit data). After the client sends its message, it calls t_rcvudata and waits for the server to respond. When the client's message arrives at the server, the server's call to t_rcvudata completes. The server can then examine the message, perform whatever computation it was designed to do, and send its reply by first calling t_alloc, filling the data area with its reply, then sending it off with t_sndudata.

The client and server continue to exchange messages this way until they are finished.

Unlike the connection-oriented service, a connectionless service does not guarantee delivery of messages, does not ensure that messages arrive at their destination in the same order in which they were sent, and does not ensure that data arrives error-free.

Synchronous and asynchronous operation

Some of the functions in XTI can operate either synchronously or asynchronously. In synchronous mode, a function call does not return until the operation can be completed (or until an error is detected). For example, if a process calls t_rcv in synchronous mode and no data is available, the call blocks until data arrives at the transport endpoint. On the other hand, if the process calls t_rcv in asynchronous mode and no data is available, the call returns immediately with a value of -1 and the global variable t_errno is set to TNODATA. It is then up to the process to decide when and how to try again and call t_rcv later.

The functions that can operate either synchronously or asynchronously are:

A process usually indicates whether these functions will operate synchronously or asynchronously be setting the oflag argument to t_open to O_NONBLOCK. See the t_open manual page for for more information.

Flow of transmitted data

One of the characteristics of both connection-oriented and connectionless service is flow control. This means that a process cannot send data through a transport endpoint if the transport provider already has all the data it can currently handle.

If a transport endpoint is operating in synchronous mode and flow control is in effect, a call to t_snd or t_sndudata will block until the transport provider has freed up enough internal storage (buffers) to accept more data. The transport provider frees buffers by transmitting data to the destination transport endpoint.

If a transport endpoint is operating in asynchronous mode and flow control is in effect, a call to t_snd or t_sndudata returns -1 and the global variable t_errno is set to TFLOW. The process can either call the t_look function to detect when it can send more data (t_look returns either T_GODATA or T_GOEXDATA when flow control has been lifted) or it can use an event management facility to be notified when flow control has been lifted. For information about using an event management facility, see ``Event management''.

Structure of transmitted data

When a process reads data from a transport endpoint in connection-oriented mode, the data can appear to be either a continuous stream of bytes (stream-oriented input) or a sequence of messages with message boundaries preserved (record-oriented input).

In the case of a continuous byte stream, the reading process cannot tell how many individual messages were sent to it, nor where one message stops and the next one begins. If message boundaries are preserved, however, the reading process can detect the end of one message and the start of another. Depending on the transport provider being used, a process reading incoming data on a transport endpoint will see one or the other of these two forms.

When a process reads data from a transport endpoint in connectionless mode, it always receives a datagram (also known as a ``message''). A datagram is a self-contained unit with a start and an end indicator for the data. When a process sends datagrams (by calling t_sndudata), each datagram is sent individually to the remote process. The receiving process then retrieves each datagram (by calling t_rcvudata) one at a time.

This is different from sending data over a connection. In connection-oriented mode, the sending process calls t_snd to transmit data. A process may call t_snd several times before it turns around and waits to receive data from the remote process (it does this by calling t_rcv). At the other end, the receiving process may get all of the data that was sent to it with a single call to t_rcv. Because the receiving process sees a byte stream as it reads data, it has no idea how many times the sending process called t_snd, nor what data was sent with each of those calls.

Priority of transmitted data

Some transport providers support the idea of ``expedited data'' (also called ``urgent'' or ``out-of-band'' data). In general, expedited data is to be sent immediately to the remote process. This can be important if, for example, the client process is interactive, and the user hits a special key (such as ``quit'' or ``interrupt''). In this case, the server process needs to get this data as quickly as possible so it can respond promptly.

Expedited data is useful because transport providers operating in connection-oriented mode typically don't send data in small chunks. To improve efficiency, the transport provider waits until the sending process has written enough data (by collecting the data passed through the transport endpoint over several calls to t_snd) before actually transmitting it over the network. This widely used technique is called ``buffering.'' 

To make sure that a piece of data is not buffered for some unknown period of time, the sending process can mark it as ``expedited.'' This is done by calling t_snd and passing in the value T_EXPEDITED as the flags argument. In this case, the transport provider transmits the expedited data ahead of all data currently being buffered without delay.

There are a few problems with expedited data, however. One is that not all transport providers support this. Another is that the way expedited data actually works on transport providers that do support it (that is, the semantics of expedited data) is not exactly the same for all transports. Consequently, X/Open recommends that programs seeking to be as independent as possible of the underlying transport provider should avoid sending expedited data.

Transport provider states

XTI defines various states that a transport provider can be in. It also defines how calling the different functions in XTI can change the state of the transport provider.

XTI defines the following states: 

T_UNINIT
This is the ``uninitialized'' state. In this state no transport endpoint exists. The only function that can be called from this state (without generating an error) is t_open.

T_UNBND
This is the ``unbound'' state. This state results from a successful call to t_open. In this state a transport endpoint exists, but no network address, host id, and local process id has been associated with it.

T_IDLE
This is the ``idle'' state. This state results from a successful call to t_bind. In this state a transport endpoint exists, and a network address, host id, and local process id have been associated with it. However, no connection has been established between this transport endpoint and a transport endpoint on another host.

T_OUTCON
This is the ``outgoing connection request'' state. In this state the transport provider has sent an outgoing connection request to another transport endpoint in response to a client's call to t_connect.

T_INCON
This is the ``incoming connection request'' state. an incoming connection request is pending. In this state the transport provider has received a connection request from a client.

T_DATAXFER
This is the ``data transfer'' state. This state is defined for connection-oriented service only. The transport provider must be in this state before a process (client or server) can send data over a connection. On the client, this state results from completion of a successful call to t_connect or t_rcvconnect. On the server, this state results from completion of a successful call to t_accept.

T_OUTREL
This is the ``outgoing orderly release'' state. This state is defined for connection-oriented service only. In this state, the process is waiting to receive an orderly release indication from the remote process. The transport provider enters this state after a call to t_sndrel.

T_INREL
This is the ``incoming orderly release'' state. This state is defined for connection-oriented service only. In this state, the process is waiting to send an orderly release request. The transport provider enters this state after a call to t_rcvrel.

Orderly release of a connection means that all data ``in the pipeline'' is transmitted to the destination transport endpoint before the connection is terminated. Note that some transport providers do not support orderly release. If you want to maximize the portability of your programs across different transport providers, you should not make use of orderly release. If you need the functionality of orderly release, you should establish your own protocol between the client and the server to verify that all data has been sent before performing a standard (abortive) release.

All transports support abortive release. With abortive release, the connection is terminated immediately. Consequently, data still ``in the pipeline'' may not be delivered to the destination transport endpoint.

Error conditions

Most of the XTI functions return a value of -1 if they encounter an error. In that case the global variable t_errno is set to one of errors defined for the function. These errors are all defined in the header file xti.h. The manual page for each function also lists the errors that the function can return.

One of the special errors defined is TLOOK. When a function returns this ``error'', an asynchronous event has occurred that the process should investigate and respond to. An asynchronous event occurs when the transport provider has detected something that is unrelated to what the process is doing at the moment. The process can determine what event has occurred by calling the t_look function. This function returns one of the events listed at the end of this section.

For example, assume a process attempts to read data over an established connection with the t_rcv call. Normally, this call returns with data sent by a remote process. However, instead of data, the transport provider may have received a signal from the remote process to disconnect. In this case, the t_rcv call returns with a value of -1. The process should now examine the variable t_errno, which contains the value TLOOK. The process can then call the function t_look, which returns the name of the event that occurred, namely T_DISCONNECT. At this point, the process can perform actions appropriate to this event (such as calling t_rcvdis to acknowledge the disconnect request).

One other error to note is TSYSERR. This means that a UNIX system error has occurred. In this case, the process should examine the global variable errno for the precise error.

The events defined in XTI are:

T_LISTEN
The transport provider at the server has received a request from a client to connect.

T_CONNECT
The transport provider at the client has received a confirmation from the server that a connection has been established.

T_DATA
The transport provider has received data.

T_EXDATA
The transport provider has received expedited data.

T_DISCONNECT
The transport provider has received a request to disconnect.

T_UDERR
The transport provider has received an indication that there was an error associated with the most recently sent datagram.

T_ORDREL
The transport provider has received a request to disconnect with orderly release.

T_GODATA
Flow control has been lifted, and the process can now send data through the transport endpoint.

T_GOEXDATA
Flow control has been lifted, and the process can now send expedited data through the transport endpoint.

Options management

XTI (and TLI) allow a process to negotiate various options with the transport provider by calling t_optmgmt. These options can affect how the transport provider operates. Some of the options require negotiation with the remote process, because both the client and the server must agree on the value of the option. The options available depend on the kind of transport provider, and can even vary between different implementations of the same kind of transport provider. To make an application as portable as possible, both across different transport providers and different implementations of the same kind of transport provider, avoid setting or negotiating options. At the very least, limit negotiation to only those options that are truly important to how the application must function. This minimizes the work needed to port the program to work over a different transport provider or a different implementation of the same transport provider.

Check whether the options negotiated are supported on the particular implementation of the transport provider being used by examining the error code returned by t_optmgmt. 

For more information on the use of options, read the t_optmgmt manual page.

See also:

Event management

XTI allows a process to manage multiple transport endpoints in a fully asynchronous manner using an event-driven design. In this kind of design, a process waits for any one of several events to occur. When an event occurs, the process discovers which event it is, then transfers control to the code written to handle that event. This code can be executed by the process that waited for the event, or the process can fork(S) a child process to execute the code. While the child process is executing, the parent process can either wait(S) for the child to complete, or it can proceed to process the next event.

XTI does not define its own portable event management facility. Instead, a process must make use of either the poll(S) or select(S) system calls. The process uses these system calls to list the transport endpoints and the events to be monitored. The process can then simply wait until the transport provider notifies it that an event has occurred on one of those transport endpoints. The process checks to see on which endpoint the event has occurred, and executes the code written to handle that kind of event.

See the CAE Specification: X/Open Transport Interface for a fuller discussion of event management. The CAE Specification also includes two code samples, one using poll and the other using select, to illustrate the use of these two system calls as the heart of an event-driven server.

Compiling and linking with XTI

To compile and link a program that uses XTI, do the following:

  1. Include the xti.h header file at the beginning of your source files. The syntax of the include preprocessor directive to use is
       #include <xti.h>
    

  2. Specify the xti library as one of the libraries to be searched for unresolved references when the cc(CP) command is invoked. The syntax of the cc command to use is

    cc option file -lxti

XTI and TLI

XTI is derived from TLI, which was first introduced with System V Release 3.0 of UNIX in 1986. Both XTI and TLI are Application Program Interfaces (APIs) that allow user processes to access transport providers in a (mostly) transport-independent fashion.

From the standpoint of syntax and semantics, the two libraries are nearly identical. The name of the header file used for TLI is tiuser.h. The syntax of the include preprocessor directive to use is

   #include <sys/tiuser.h>

The name of the library to be searched when compiling and linking a program that uses TLI is nsl (Network Services Library). The syntax of the cc command to use is

cc option file -lnsl

Applications written to use TLI can be ported relatively easily to XTI. You should note the points listed below when porting your application.

The CAE Specification has more information on the differences between XTI and TLI.

XTI and sockets

The socket interface, made popular by the Berkeley version of UNIX, is another API that programs can use to access transport providers. A socket closely resembles an XTI transport endpoint. In general, both XTI and the sockets library provide similar functionality.

The list below calls out some of the differences between XTI and sockets. This information may be useful to those who are already familiar with socket programming or who have programs they wish to port from sockets to XTI. Note that a sockets library is available for use on SCO systems with the TCP and UDP protocols.

The above list is not exhaustive. Compare the manual pages for both the sockets library (SSC and SLIB) and XTI for more detailed information.

Transport-specific issues

Although XTI is a transport-independent programming interface, some areas that a network program must confront lie outside the scope of XTI. Two such areas are transport addresses and options management. Address formats and available options vary from transport provider to transport provider. While XTI provides a generic mechanism for assigning and retrieving address and options information, it is up to the software developer to code the specific details appropriate to each transport provider. These details will have to be recoded when the program is ported to use a different transport provider.

Similarly, each transport provider protocol structures internal information differently. For example, a transport provider stores network addresses, host ids, and local process ids as integers. The byte order used by the transport protocol need not be the same as that used by the host machine. Consequently, the transport provider may support transport-specific library routines to convert integers from the host format to the transport provider's format and back again. The socket library functions htons and ntohs are two examples of this.

The transport provider may also support various utility functions that are specific to that transport protocol. For example, the TCP/IP protocol encodes network addresses and host ids as four bytes, where each byte is an unsigned integer. However, programs typically denote these addresses as dotted quads (four decimal numbers separated by periods). A utility function is available for use with TCP/IP to convert a dotted quad into the integer encoding.

Each transport provider can also define its own mechanism to allow programs to refer to host ids or local process ids with symbolic names, rather than numbers. The defined mechanism converts the symbolic name into the appropriate numeric value. For example, the TCP/IP protocol defines a file called /etc/services. A server process that is to be known on the network by a certain name can look up that name in this file to determine the local process id (in this case, the port number) it should specify when calling t_bind. Again, a utility function exists to facilitate this lookup.

It is the responsibility of the software developer to include the appropriate transport-specific header files and libraries. These files and libraries define transport-specific address formats and utility functions. To obtain this kind of information, read the appropriate chapter in this Guide that describes how to use XTI or TLI over the specific transport provider you will use in your application

Pseudo-code examples

This section presents a basic client and server in pseudo-code to illustrate the overall structure of each.


NOTE: The example client and server do not illustrate a matching pair. The client expects to exchange data repeatedly with a suitable server, whereas the server expects to be contacted by a client that makes only a single request.

Client pseudo-code

This client sends data to and receives data from the selected server.

#include <xti.h>
#include other needed header files

extern int t_errno;

main (int argc, char *argv[]) {

/* Declare data structures needed for operations on the transport provider endpoint. */

/* Declare the data structure that contains the well-known address of the server. Both the type and the value of "server_address" must be transport-specific. This example assumes that they have already been defined appropriately (using "typedef" and "#define", for example). TRANSPORT_ADDRESS server_address = WELL_KNOWN_SERVER_ADDRESS;

/* Declare other data structures. */

/* Open the transport provider, using the appropriate device name, and receive a file descriptor in return that denotes that endpoint. */ fd = t_open(device_name, ... )

/* Have the transport provider bind an arbitrary transport address (which is transport-specific) to the endpoint. */ t_bind(fd, NULL, NULL);

/* Allocate the data structure needed in the call to connect to the server. */ connection_info = t_alloc(fd, T_CALL, T_ADDR);

/* Fill in the data structure with the well-known address of the server. */ connection_info->addr.len = sizeof(server_address); connection_info->addr.buf = &server_address;

/* Connect to the server, passing in its well-known address. */ t_connect(fd, connection_info, NULL);

while (true) {

/* Send data to the server. */ t_snd(fd, &data_to_send, sizeof(data_to_send), &flags);

/* Receive data from the server. */ t_rcv(fd, &data_to_send, sizeof(data_to_send), &flags);

/* Time to exit? */ if (time_to_exit == /* some suitable expression that returns zero if it's not time to disconnect and non-zero otherwise */) break; }

/* Disconnect */ t_snddis(fd, NULL);

/* Close the transport endpoint. */ t_close(fd); }

Server pseudo-code

This server listens for connection requests from arbitrary clients. When it receives a connection request, it spawns a child process to respond to the client. The parent process continues to listen for additional connection requests.

#include <xti.h>
#include other needed header files

extern int t_errno;

main (int argc, char *argv[]) {

/* Declare data structures needed for operations on the transport provider endpoint. */

/* Declare the data structure that contains the well-known address of the server. Both the type and the value of "server_address" must be transport-specific. This example assumes that they have already been defined appropriately (using "typedef" and "#define", for example). TRANSPORT_ADDRESS server_address = WELL_KNOWN_SERVER_ADDRESS;

/* Declare other data structures. */

/* Open the transport provider, using the device name assigned to that transport provider, and receive in return a file descriptor denoting that endpoint. */ fd = t_open(device_name, ... );

/* Allocate the bind data structure that will contain the well-known transport-specific address of the server. */ requested_binding = t_alloc(fd, T_BIND, T_ADDR);

/* Allocate the bind data structure that will contain the actual address assigned by the transport provider. */ actual_binding = t_alloc(fd, T_BIND, T_ADDR);

/* Fill in the bind data structure with the well-known transport-specific address of the server, and the length of the queue to hold incoming connection requests. */ requested_binding->addr.len = sizeof(server_address); requested_binding->addr.buf = &server_address; requested_binding->qlen = MAX_QUEUE_LENGTH;

/* Bind the well-known transport-specific address of the server to the transport endpoint. */ t_bind(fd, requested_binding, actual_binding);

/* Compare the requested address in "requested_binding" against the actual assigned address in "actual binding". If they don't match, issue an error and exit. */ if (memcmp(requested_binding->addr.buf, actual_binding->addr.buf, ADDRESS_LENGTH) != 0 ) { perror("Unable to bind correct server address."); exit(1); }

/* Allocate the data structure needed to record the address of the next client requesting a connection. */ call = t_alloc(fd, T_CALL, T_ADDR);

/* This server runs forever. */ while (true) {

/* Listen forever for the next connection request from a client. */ t_listen(fd, call);

/* Open a new file descriptor through which the server will complete the connection from the client. By completing the connection through "resfd", the server leaves "fd" available to receive additional connection requests from other clients. */ resfd = t_open(device_name, ... );

/* Let the transport provider select an arbitrary transport address for the new file descriptor. */ t_bind(resfd, NULL, NULL);

/* Accept the connection request that arrived at "fd" and attach the server-side of the connection to the responding file descriptor "resfd". The file descriptor "fd" continues to be available to the server to listen for new incoming connection requests from clients (see "t_listen" at the top of the loop). The "call" data structure has the information that the child server process (see the "switch" statement below) needs to communicate with the client at the other end of the connection. */ t_accept(fd, resfd, call);

/* Spawn a child server process. The parent process will continue to listen on the original file descriptor "fd" (which is still bound to the server's well-known address) for additional connection requests. The child process will communicate with the client using the "resfd" file descriptor. */ switch (fork()) {

case -1: perror("Fork of server process to respond to client request has failed. Server aborting..."); exit(1);

default: /* This is the code executed by the parent process that continues to listen on the well-known transport address for additional incoming connection requests from clients. The parent process has no use for the file descriptor to be used to communicate with the client (resfd), so it closes it. Doing so does not close this file descriptor for the child process, however, which can still use it. */ t_close(resfd);

case 0: /* This is the code executed by the child process that services the request of the client. The child process has no further use for the original file descriptor (fd) on which the connection request arrived, so it closes it. This does not close the file descriptor for the parent process, however, which will continue to listen on the "fd" file descriptor for new connection requests from clients. t_close(fd);

/* The child process can now perform some useful service for the client. In this example, the server has a very accurate clock, so it returns the correct time of day to the client, then exits. */ gettimeofday(&time_value, &timezone); t_snd(resfd, &time_value, sizeof(struct timeval), flags); t_close(resfd); exit(0); } /* end switch */

} /* end while */

} /* end main */

Implementing a read/write interface

Sometimes you may want to establish a transport connection and then exec(S) an existing user program such as cat(C) to process the data as it arrives over the connection. However, most existing programs use read(S) and write(S) to perform character I/O. XTI and TLI do not directly support a read/write interface to a transport provider, but one may be provided using the tirdwr(M) STREAMS module. (This module is present in the kernel by default.) Such a connection can be released with the close(S) system call. This interface enables an application to issue read and write calls over a transport connection that has been established by the server's call to t_accept(NET).


NOTE: This interface is not available with the connectionless-mode service.

In the following example, a server first pushes tirdwr onto a stream before running cat(C) so that a client can read from or write to it over the transport connection.

   #include <stropts.h>
   

. /* . * connection requested and accepted . */

if (ioctl(fd, I_PUSH, "tirdwr") < 0) { perror("I_PUSH of tirdwr failed"); exit(5); } close(0); dup(fd); execl("/bin/cat", "/bin/cat", 0); perror("execl of /bin/cat failed"); exit(6);

The server invokes the read/write interface by pushing the tirdwr module onto the stream head associated with the transport endpoint created when the connection was established. For a description of I_PUSH, see streamio(M). With tirdwr in place, the server calls close and dup(S) to establish the transport endpoint as its standard input, and uses cat to process the input.

Because the transport layer is implemented using STREAMS, the facilities of this character I/O mechanism can be used to provide enhanced user services. Note the following limitations on the use of this interface:

If tirdwr receives any other XTI or TLI indication, it normally generates a fatal protocol error, EPROTO, on the stream. This causes further system calls to fail. However, if tirdwr is pushed onto a stream after a connection has been established, such indications are not generated.

With tirdwr pushed onto a stream, an application can send and receive data over the transport connection for the duration of the connection. Either end of a connection can terminate it by closing the file descriptor associated with the transport endpoint or by popping the tirdwr module off the stream. In either case, tirdwr takes the following actions:


For more about XTI and TLI

For tutorial information on network programming, read:

For the specifics of the XTI API implemented in the XTI libraries provided with the SCO OpenServer Development System, read the X/Open Developer's Specification (1990), Revised XTI (X/Open Transport Interface), ISBN 1-872630-05-7, and the X/Open Addendum (August 1991), Addendum to Revised XTI, ISBN 1-872630-21-9.

For the specifics of the TLI API implemented in the TLI libraries provided with the SCO OpenServer Development System, read the AT&T SVID Issue 3.