Go to main content

Oracle® Solaris 11.4 Programming Interfaces Guide

Exit Print View

Updated: November 2020

Advanced XTI/TLI Topics

    This section presents additional XTI/TLI concepts:

  • Asynchronous Execution Mode describes optional nonblocking (asynchronous) mode for some library calls.

  • Advanced XTI/TLI Programming Example is 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.

Applications can access asynchronous processing of XTI/TLI events through the combination of asynchronous features and the non-blocking mode of XTI/TLI library routines. See the ONC+ RPC Developer’s Guide for information about use of the poll() system call and the I_SETSIG ioctl() ioctl(2) command to process events asynchronously.

You can run each XTI/TLI routine that blocks for an event in a special non-blocking mode. For example, t_listen() normally blocks for a connect request. A server can periodically poll a transport endpoint for queued connect requests by calling t_listen() in the non-blocking (or asynchronous) mode. For more information, see the t_listen(3C) ma page. You enable the asynchronous mode by setting O_NDELAY or O_NONBLOCK in the file descriptor. Set these modes as a flag through t_open(), or by calling fcntl() before calling the XTI/TLI routine. Use fcntl() to enable or disable this mode at any time. All program examples in this chapter use the default synchronous processing mode. For more information, see the t_open(3C) and fcntl(2) man pages.

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

Advanced XTI/TLI Programming Example

Example 41, Establishing an Endpoint (Convertible to Multiple Connections) 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.

By using XTI/TLI, a server can 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 to overcome the limits of single-threaded processing. Depending on the transport provider, while a server is processing one connect request, other clients see the server as busy. If multiple connect requests are processed simultaneously, the server is 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 following demonstrates the ability to poll multiple transport endpoints for incoming events.

Example 41  Establishing an Endpoint (Convertible to Multiple Connections)
#include <tiuser.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <stropts.h>
#include <signal.h>

#define NUM_FDS 1
#define MAX_CONN_IND 4
#define SRV_ADDR 1                 /* server's well known address */

int conn_fd;                       /* server connection here */
extern int t_errno;
/* holds connect requests */
struct t_call *calls[NUM_FDS][MAX_CONN_IND];

   struct pollfd pollfds[NUM_FDS];
   struct t_bind *bind;
   int i;

    * Only opening and binding one transport endpoint, but more can
    * be supported
   if ((pollfds[0].fd = t_open("/dev/tivc", O_RDWR,
         (struct t_info *) NULL)) == -1) {
      t_error("t_open failed");
   if ((bind = (struct t_bind *) t_alloc(pollfds[0].fd, T_BIND,
         T_ALL)) == (struct t_bind *) NULL) {
      t_error("t_alloc of t_bind structure failed");
   bind->qlen = MAX_CONN_IND;
   bind->addr.len = sizeof(int);
   *(int *) bind->addr.buf = SRV_ADDR;
   if (t_bind(pollfds[0].fd, bind, bind) == -1) {
      t_error("t_bind failed");
   /* Was the correct address bound? */
   if (bind->addr.len != sizeof(int) ||
      *(int *)bind->addr.buf != SRV_ADDR) {
      fprintf(stderr, "t_bind bound wrong address\n");

The file descriptor returned by t_open() is stored in a pollfd structure that controls polling of the transport endpoints for incoming data. For more information, see the poll(2) and t_open(3C) man pages. 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 41, Establishing an Endpoint (Convertible to Multiple Connections).

This server sets qlen to a value greater than 1 for t_bind(). This value specifies that the server should queue 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 to be smaller if the provider cannot support MAX_CONN_IND outstanding connect requests.

After the server binds its address and is ready to process connect requests, it behaves as shown in the following example.

Example 42  Processing Connection Requests
pollfds[0].events = POLLIN;

while (TRUE) {
    if (poll(pollfds, NUM_FDS, -1) == -1) {
        perror("poll failed");
    for (i = 0; i < NUM_FDS; i++) {
        switch (pollfds[i].revents) {
                perror("poll returned error event");
            case 0:
            case POLLIN:
                do_event(i, pollfds[i].fd);
                service_conn_ind(i, pollfds[i].fd);

In the pollfd structure, the events field 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 endpoints for events and processes the events as they occur.

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

Each time the server iterates the loop, it calls service_conn_ind 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 server calls do_event in the following example to process an incoming event.

Example 43  Creating an Event Processing Routine
do_event( slot, fd)
int slot;
int fd;
   struct t_discon *discon;
   int i;

   switch (t_look(fd)) {
      fprintf(stderr, "t_look: unexpected event\n");
   case T_ERROR:
      fprintf(stderr, "t_look returned T_ERROR event\n");
   case -1:
      t_error("t_look failed");
   case 0:
      /* since POLLIN returned, this should not happen */
      fprintf(stderr,"t_look returned no event\n");
   case T_LISTEN:
      /* find free element in calls array */
      for (i = 0; i < MAX_CONN_IND; i++) {
         if (calls[slot][i] == (struct t_call *) NULL)
      if ((calls[slot][i] = (struct t_call *) t_alloc( fd, T_CALL,
               T_ALL)) == (struct t_call *) NULL) {
         t_error("t_alloc of t_call structure failed");
      if (t_listen(fd, calls[slot][i] ) == -1) {
         t_error("t_listen failed");
      discon = (struct t_discon *) t_alloc(fd, T_DIS, T_ALL);
      if (discon == (struct t_discon *) NULL) {
         t_error("t_alloc of t_discon structure failed");
      if(t_rcvdis( fd, discon) == -1) {
         t_error("t_rcvdis failed");
      /* find call ind in array and delete it */
      for (i = 0; i < MAX_CONN_IND; i++) {
         if (discon->sequence == calls[slot][i]->sequence) {
            t_free(calls[slot][i], T_CALL);
            calls[slot][i] = (struct t_call *) NULL;
      t_free(discon, T_DIS);

The arguments in Example 43, Creating an Event Processing Routine are a number (slot) and a file descriptor (fd). A 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.

The do_event module calls t_look() 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 more information, see the t_look(3C) man page.

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(). The array is large enough to hold the maximum number of outstanding connect requests. The processing of the connect request is deferred. For more information, see the t_listen(3C) man page.

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

struct t_discon {
    struct    netbuf    udata;
    int       reason;
    int       sequence;

The udata structure contains any user data sent with the disconnect request. The value of reason contains a protocol-specific disconnect reason code. The value of sequence identifies the connect request that matches the disconnect request.

The server calls t_rcvdis() 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. For more information, see the t_rcvdis(3C) man page.

When an event is found on a transport endpoint, service_conn_ind is called to process all queued connect requests on the endpoint, as the following example shows.

Example 44  Processing All Connect Requests
service_conn_ind(slot, fd)
   int i;

	for (i = 0; i < MAX_CONN_IND; i++) {
      if (calls[slot][i] == (struct t_call *) NULL)
      if((conn_fd = t_open( "/dev/tivc", O_RDWR,
            (struct t_info *) NULL)) == -1) {
         t_error("open failed");
      if (t_bind(conn_fd, (struct t_bind *) NULL,
            (struct t_bind *) NULL) == -1) {
         t_error("t_bind failed");
      if (t_accept(fd, conn_fd, calls[slot][i]) == -1) {
         if (t_errno == TLOOK) {
         t_error("t_accept failed");
      t_free(calls[slot][i], T_CALL);
      calls[slot][i] = (struct t_call *) NULL;

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 connect or disconnect request arrives before the current request is accepted, t_accept() 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 activity causes the server's main processing loop to be entered, and the new event is discovered by the next call to poll(). In this way, the user can queue multiple connect requests.

Eventually, all events are processed, and service_conn_ind is able to accept each connect request in turn.