Transport Interfaces Programming Guide

Connectionless Mode Service

Connectionless mode service is appropriate for short-term request/response interactions, such as transaction-processing applications. Data are transferred in self-contained units with no logical relationship required among multiple units.

Endpoint Initiation

Transport users must initialize XTI/TLI endpoints before transferring data. They must choose the appropriate connectionless service provider using t_open() and establish its identity using t_bind().

Use t_optmgmt() to negotiate protocol options. Like connection mode service, each transport provider specifies the options, if any, it supports. Option negotiation is a protocol-specific activity. In Example 3-1, the server waits for incoming queries, and processes and responds to each query. The example also shows the definitions and initiation sequence of the server.


Example 3-1 CLTS Server


#include <stdio.h>
#include <fcntl.h>
#include <xti.h>	/* TLI applications use <tiuser.h>  */
#define SRV_ADDR 2	/* server's well known address */

main()
{
   int fd;
   int flags;
   struct t_bind *bind;
   struct t_unitdata *ud;
   struct t_uderr *uderr;
   extern int t_errno;

	if ((fd = t_open("/dev/exmp", O_RDWR, (struct t_info *) NULL))
	        == -1) {
      t_error("unable to open /dev/exmp");
      exit(1);
   }
 	if ((bind = (struct t_bind *)t_alloc(fd, T_BIND, T_ADDR))
         == (struct t_bind *) NULL) {
      t_error("t_alloc of t_bind structure failed");
      exit(2);
   }
   bind->addr.len = sizeof(int);
   *(int *)bind->addr.buf = SRV_ADDR;
   bind->qlen = 0;
   if (t_bind(fd, bind, bind) == -1) {
      t_error("t_bind failed");
      exit(3);
   }
   /*
    * TLI interface applications need the following code which
    * is no longer needed for XTI interface applications.
    * -------------------------------------
    * Verify if the bound address correct?
    *
    * if (bind -> addr.len != sizeof(int) ||
    *      *(int *)bind->addr.buf != SRV_ADDR) {
    *	fprintf(stderr, "t_bind bound wrong address\n");
    *	exit(4);
    * }
    * ---------------------------------------
    */

The server establishes a transport endpoint with the desired transport provider using t_open(). Each provider has an associated service type, so the user can choose a particular service by opening the appropriate transport provider file. This connectionless mode server ignores the characteristics of the provider returned by t_open() by setting the third argument to NULL. The transaction server assumes the transport provider has the following characteristics:

The connectionless server binds a transport address to the endpoint so that potential clients can access the server. A t_bind structure is allocated using t_alloc() and the buf and len fields of the address are set accordingly.

One difference between a connection mode server and a connectionless mode server is that the qlen field of the t_bind structure is 0 for connectionless mode service. There are no connection requests to queue.

XTI/TLI interfaces define an inherent client-server relationship between two users while establishing a transport connection in the connection mode service. No such relationship exists in connectionless mode service.

TLI requires that the server check the bound address returned by t_bind() to ensure that it is the same as the one supplied. t_bind() can also bind the endpoint to a separate, free address if the one requested is busy.

Data Transfer

After a user has bound an address to the transport endpoint, datagrams can be sent or received over the endpoint. Each outgoing message carries the address of the destination user. XTI/TLI also lets you specify protocol options to the transfer of the data unit (for example, transit delay). Each transport provider defines the set of options on a datagram. When the datagram is passed to the destination user, the associated protocol options can be passed, too.

Example 3-2 illustrates the data transfer phase of the connectionless mode server.


Example 3-2 Data Transfer Routine


	if ((ud = (struct t_unitdata *) t_alloc(fd, T_UNITDATA,T_ALL))
         == (struct t_unitdata *) NULL) {
      t_error("t_alloc of t_unitdata struct failed");
      exit(5);
   }
   if ((uderr = (struct t_uderr *) t_alloc(fd, T_UDERROR, T_ALL))
         == (struct t_uderr *) NULL) {
      t_error("t_alloc of t_uderr struct failed");
      exit(6);
   }
   while(1) {
      if (t_rcvudata(fd, ud, &flags) == -1) {
         if (t_errno == TLOOK) {
               /* Error on previously sent datagram */
               if(t_rcvuderr(fd, uderr) == -1) {
                  exit(7);
               }
            fprintf(stderr, "bad datagram, error=%d\n",
               uderr->error);
            continue;
         }
         t_error("t_rcvudata failed");
         exit(8);
      }
      /*
       * Query() processes the request and places the response in
       * ud->udata.buf, setting ud->udata.len
       */
      query(ud);
      if (t_sndudata(fd, ud) == -1) {
         t_error("t_sndudata failed");
         exit(9);
      }
   }
}

/* ARGS USED */
void
query(ud)
struct t_unitdate *ud;
{
   /* Merely a stub for simplicity */
}

To buffer datagrams, the server first allocates a t_unitdata structure, which has the following format:


struct t_unitdata {
 	struct netbuf addr;
 	struct netbuf opt;
 	struct netbuf udata;
}

addr holds the source address of incoming datagrams and the destination address of outgoing datagrams. opt holds any protocol options on the datagram. udata holds the data. The addr, opt, and udata fields must all be allocated with buffers large enough to hold any possible incoming values. The T_ALL argument of t_alloc() ensures this and sets the maxlen field of each netbuf structure accordingly. The provider does not support protocol options in this example, so maxlen is set to 0 in the opt netbuf structure. The server also allocates a t_uderr structure for datagram errors.

The transaction server loops forever, receiving queries, processing the queries, and responding to the clients. It first calls t_rcvudata() to receive the next query. t_rcvudata() blocks until a datagram arrives, and returns it.

The second argument of t_rcvudata() identifies the t_unitdata structure in which to buffer the datagram.

The third argument, flags, points to an integer variable and can be set to T_MORE on return from t_rcvudata() to indicate that the user's udata buffer is too small to store the full datagram.

If this happens, the next call to t_rcvudata() retrieves the rest of the datagram. Because t_alloc() allocates a udata buffer large enough to store the maximum size datagram, this transaction server does not have to check flags. This is true only of t_rcvudata() and not of any other receive primitives.

When a datagram is received, the transaction server calls its query routine to process the request. This routine stores a response in the structure pointed to by ud, and sets ud->udata.len to the number of bytes in the response. The source address returned by t_rcvudata() in ud->addr is the destination address for t_sndudata(). When the response is ready, t_sndudata() is called to send the response to the client.

Datagram Errors

If the transport provider cannot process a datagram sent by t_sndudata(), it returns a unit data error event, T_UDERR, to the user. This event includes the destination address and options of the datagram, and a protocol-specific error value that identifies the error. Datagram errors are protocol specific.


Note -

A unit data error event does not always indicate success or failure in delivering the datagram to the specified destination. Remember, connectionless service does not guarantee reliable delivery of data.


The transaction server is notified of an error when it tries to receive another datagram. In this case, t_rcvudata() fails, setting t_errno to TLOOK. If TLOOK is set, the only possible event is T_UDERR, so the server calls t_rcvuderr() to retrieve the event. The second argument of t_rcvuderr() is the t_uderr structure that was allocated earlier. This structure is filled in by t_rcvuderr() and has the following format:


struct t_uderr {
 	struct netbuf addr;
 	struct netbuf opt;
 	t_scalar_t error;
}

where addr and opt identify the destination address and protocol options specified in the bad datagram, and error is a protocol-specific error code. The transaction server prints the error code, then continues.