Advanced Topics

A program example of a server supporting multiple outstanding connect requests and operating in an event-driven manner

Asynchronous Execution Mode

Many XTI/TLI library routines block to wait for an incoming event. However, some time-critical applications should not block for any reason. An application can do local processing while waiting for some asynchronous XTI/TLI event.

Asynchronous processing of XTI/TLI events is available to applications through the combination of asynchronous features and the non-blocking mode of XTI/TLI library routines. Use of the poll(2) system call and the I_SETSIGioctl(2) command to process events asynchronously is
described in ONC+ Developer's Guide.

Each XTI/TLI routine that blocks for an event can be run in a special non-blocking mode. For example, t_listen(3NSL) normally blocks for
a connect request. A server can periodically poll a transport endpoint for queued connect requests by calling t_listen(3NSL) in the non-blocking
(or asynchronous) mode. The asynchronous mode is enabled by setting O_NDELAY or O_NONBLOCK in the file descriptor. These modes can be set as a flag through t_open(3NSL), or by calling fcntl(2) before calling the XTI/TLI routine. fcntl(2) enables or disables this mode at any time. All program examples in this chapter use the default synchronous processing mode.

O_NDELAY or O_NONBLOCK affect each XTI/TLI routine differently. You will need to determine the exact semantics of O_NDELAY or O_NONBLOCK for a particular routine.

Advanced Programming Example

The following example demonstrates two important concepts. The first is a server's ability to manage multiple outstanding connect requests. The second is event-driven use of XTI/TLI and the system call interface.

The server example in Example 3-4 supports only one outstanding connect request, but XTI/TLI lets a server manage multiple outstanding connect requests. One reason to receive several simultaneous connect requests is to prioritize the clients.
A server can receive several connect requests, and accept them in an order based on the priority of each client.

The second reason for handling several outstanding connect requests is the limits of single-threaded processing. Depending on the transport provider, while a server processes one connect request, other clients find it busy. If multiple connect requests are processed simultaneously, the server will
be found busy only if more than the maximum number of clients try to call the server simultaneously.

The server example is event-driven: the process polls a transport endpoint for incoming XTI/TLI events, and takes the appropriate actions for the event received. The example demonstrates the ability to poll multiple transport endpoints for incoming events.

The definitions and endpoint establishment functions of Example 3-9 are similar to those of the server example in Example 3-4.

The file descriptor returned by t_open(3NSL) is stored in a pollfd structure that controls polling the transport endpoints
for incoming data. See poll(2). Only one transport endpoint is established in this example. However, the remainder of the example is written to manage
multiple transport endpoints. Several endpoints could be supported with minor changes to Example 3-9.

This server sets qlen to a value greater than 1 for t_bind(3NSL). This specifies that the server queues
multiple outstanding connect requests. The server accepts the current connect request before accepting additional connect requests. This example can queue up to MAX_CONN_IND connect requests. The transport provider can negotiate the value of qlen smaller if it cannot
support MAX_CONN_IND outstanding connect requests.

After the server has bound its address and is ready to process connect requests, it behaves as shown in Example 3-10.

The events field of the pollfd structure is set to POLLIN, which notifies the server of any incoming XTI/TLI events. The server then enters an infinite loop in which it polls the transport endpoint(s) for events, and processes events as they
occur.

The poll(2) call blocks indefinitely for an incoming event. On return, each entry (one per transport endpoint) is checked for a new event. If revents is 0, no event has occurred on the endpoint and the server continues to the next endpoint. If revents is POLLIN, there is an event on the endpoint. do_event is called to process the event. Any other value in revents indicates an error on the endpoint, and the server exits. With multiple endpoints, it is better for the server to close this descriptor and continue.

For each iteration of the loop, service_conn_ind is called to process any outstanding connect requests. If another connect request is pending, service_conn_ind saves the new connect request and responds to it later.

The arguments are a number (slot) and a file descriptor (fd). slot is the index into the global array calls which has an entry for each transport endpoint. Each entry is an array of t_call structures that hold incoming connect requests for the endpoint.

do_event calls t_look(3NSL) to identify the XTI/TLI event on the endpoint specified by fd. If
the event is a connect request (T_LISTEN event) or disconnect request (T_DISCONNECT event), the event is processed. Otherwise, the server prints an error message and exits.

For connect requests, do_event scans the array of outstanding connect requests for the first free entry. A t_call structure is allocated for the entry, and the connect request is received by t_listen(3NSL). The array is large enough to hold the maximum number of outstanding connect requests. The processing of the connect request is deferred.

A disconnect request must correspond to an earlier connect request. do_event allocates a t_discon structure to receive the request. This structure has the following fields:

t_rcvdis(3NSL) is called to receive the disconnect request. The array of connect requests is scanned for one that contains the sequence
number that matches the sequence number in the disconnect request. When the connect request is found, its structure is freed and the entry is set to NULL.

When an event is found on a transport endpoint, service_conn_ind is called to process all queued connect requests on the endpoint, as Example 3-12 shows.

For each transport endpoint, the array of outstanding connect requests is scanned. For each request, the server opens a responding transport endpoint, binds an address to the endpoint, and accepts the connection on the endpoint. If another event (connect request or disconnect request) arrives before
the current request is accepted, t_accept(3NSL) fails and sets t_errno to TLOOK. (You cannot accept an outstanding
connect request if any pending connect request events or disconnect request events exist on the transport endpoint.)

If this error occurs, the responding transport endpoint is closed and service_conn_ind returns immediately (saving the current connect request for later processing). This causes the server's main processing loop to be entered, and the new event is discovered by the next call to poll(2). In this way, multiple connect requests can be queued by the user.

Eventually, all events are processed, and service_conn_ind is able to accept each connect request in turn. After the connection has been established, the run_server routine used by the server in the Example 3-5 is called to manage the data transfer.