Network Interface Guide

Connection Mode Service

The main concepts of connection mode service are illustrated through a client program and its server. The examples are presented in segments.

In the examples, the client establishes a connection to a server process. The server transfers a file to the client. The client receives the file contents and writes them to standard output.

Endpoint Initiation

Before a client and server can connect, each must first open a local connection to the transport provider (the transport endpoint) through t_open(3NSL), and establish its identity (or address) through t_bind(3NSL).

Many protocols perform a subset of the services defined in XTI/TLI. Each transport provider has characteristics that determine the services it provides and limit the services. Data defining the transport characteristics are returned by t_open(3NSL) in a t_info structure. Table 3-6 shows the fields in a t_info structure.

Table 3-6 t_info Structure

Field 

Content 

addr

Maximum size of a transport address  

options

Maximum bytes of protocol-specific options that can be passed between the transport user and transport provider  

tsdu

Maximum message size that can be transmitted in either connection mode or connectionless mode  

etsdu

Maximum expedited data message size that can be sent over a transport connection  

connect

Maximum number of bytes of user data that can be passed between users during connection establishment  

discon

Maximum bytes of user data that can be passed between users during the abortive release of a connection  

servtype

The type of service supported by the transport provider  

The three service types defined by XTI/TLI are:

  1. T_COTS -- The transport provider supports connection mode service but does not provide the orderly release facility. Connection termination is abortive, and any data not already delivered is lost.

  2. T_COTS_ORD -- The transport provider supports connection mode service with the orderly release facility.

  3. T_CLTS -- The transport provider supports connectionless mode service.

Only one such service can be associated with the transport provider identified by t_open(3NSL).

t_open(3NSL) returns the default provider characteristics of a transport endpoint. Some characteristics can change after an endpoint has been opened. This happens with negotiated options (option negotiation is described later in this section). t_getinfo(3NSL) returns the current characteristics of a transport endpoint.

After a user establishes an endpoint with the chosen transport provider, the client and server must establish their identities. t_bind(3NSL) does this by binding a transport address to the transport endpoint. For servers, this routine informs the transport provider that the endpoint is used to listen for incoming connect requests.

t_optmgmt(3NSL) can be used during the local management phase. It lets a user negotiate the values of protocol options with the transport provider. Each transport protocol defines its own set of negotiable protocol options, such as quality-of-service parameters. Because the options are protocol-specific, only applications written for a specific protocol use this function.

Client

The local management requirements of the example client and server are used to discuss details of these facilities. Example 3-3 shows the definitions needed by the client program, followed by its necessary local management steps.


Example 3-3 Client Implementation of Open and Bind

#include <stdio.h>
#include <tiuser.h>
#include <fcntl.h>
#define SRV_ADDR 1 									/* server's address */

main()
{
   int fd;
   int nbytes;
   int flags = 0;
   char buf[1024];
   struct t_call *sndcall;
   extern int t_errno;

   if ((fd = t_open("/dev/exmp", O_RDWR, (struct t_info *),NULL))
         == -1) {
      t_error("t_open failed");
      exit(1);
   }
   if (t_bind(fd, (struct t_bind *) NULL, (struct t_bind *) NULL)
         == -1) {
      t_error("t_bind failed");
      exit(2);
   }

The first argument of t_open(3NSL) is the path of a file system object that identifies the transport protocol. /dev/exmp is the example name of a special file that identifies a generic, connection-based transport protocol. The second argument, O_RDWR, specifies to open for both reading and writing. The third argument points to a t_info structure in which to return the service characteristics of the transport.

This data is useful to write protocol-independent software (see "Guidelines to Protocol Independence"). In this example, a NULL pointer is passed. For Example 3-3, the transport provider must have the following characteristics:

If the user needs a service other than T_COTS_ORD, another transport provider can be opened. An example of the T_CLTS service invocation is shown in the section "Read/Write Interface".

t_open(3NSL) returns the transport endpoint file handle that is used by all subsequent XTI/TLI function calls. The identifier is a file descriptor from opening the transport protocol file. See open(2).

The client then calls t_bind(3NSL) to assign an address to the endpoint. The first argument of t_bind(3NSL) is the transport endpoint handle. The second argument points to a t_bind structure that describes the address to bind to the endpoint. The third argument points to a t_bind structure that describes the address that the provider has bound.

The address of a client is rarely important because no other process tries to access it. That is why the second and third arguments to t_bind(3NSL) are NULL. The second NULL argument directs the transport provider to choose an address for the user.

If t_open(3NSL) or t_bind(3NSL) fails, the program calls t_error(3NSL) to display an appropriate error message by stderr. The global integer t_error(3NSL) is assigned an error value. A set of error values is defined in tiuser.h.

t_error(3NSL) is analogous to perror(3C). If the transport function error is a system error, t_errno(3NSL) is set to TSYSERR, and errno is set to the appropriate value.

Server

The server example must also establish a transport endpoint at which to listen for connection requests. Example 3-4 shows the definitions and local management steps.


Example 3-4 Server Implementation of Open and Bind

#include <tiuser.h>
#include <stropts.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>

#define DISCONNECT -1
#define SRV_ADDR 1								/* server's address */
int conn_fd;			/* connection established here */
extern int t_errno;

main()
{
   int listen_fd;							/* listening transport endpoint */
   struct t_bind *bind;
   struct t_call *call;

   if ((listen_fd = t_open("/dev/exmp", O_RDWR,
      (struct t_info *) NULL)) == -1) {
      t_error("t_open failed for listen_fd");
      exit(1);
   }
   if ((bind = (struct t_bind *)t_alloc( listen_fd, T_BIND, T_ALL))
         == (struct t_bind *) NULL) {
      t_error("t_alloc of t_bind structure failed");
      exit(2);
   }
   bind->qlen = 1;
   
   /*
    * Because it assumes the format of the provider's address,
    * this program is transport-dependent
    */
    bind->addr.len = sizeof(int);
   *(int *) bind->addr.buf = SRV_ADDR;
   if (t_bind (listen_fd, bind, bind) < 0 ) {
      t_error("t_bind failed for listen_fd");
      exit(3);
   }

   #if (!defined(_XOPEN_SOURCE) ||(_XOPEN_SOURCE_EXTENDED -0 != 1))
   /* 
    * Was the correct address bound? 
    * 
    * When using XTI, this test is unnecessary 
    */

   if (bind->addr.len != sizeof(int) ||
      *(int *)bind->addr.buf != SRV_ADDR) {
      fprintf(stderr, "t_bind bound wrong address\n");
      exit(4);
    }
    #endif

Like the client, the server first calls t_open(3NSL) to establish a transport endpoint with the desired transport provider. The endpoint, listen_fd, is used to listen for connect requests.

Next, the server binds its address to the endpoint. This address is used by each client to access the server. The second argument points to a t_bind structure that specifies the address to bind to the endpoint. The t_bind structure has the following format:

struct t_bind {
 	struct netbuf addr;
 	unsigned qlen;
}

Where addr describes the address to be bound, and qlen specifies the maximum number of outstanding connect requests. All XTI structure and constant definitions made visible for use by applications programs through xti.h. All TLI structure and constant definitions are in tiuser.h.

The address is specified in the netbuf structure with the following format:

struct netbuf {
 	unsigned int maxlen;
 	unsigned int len;
 	char *buf;
}

Where maxlen specifies the maximum length of the buffer in bytes, len specifies the bytes of data in the buffer, and buf points to the buffer that contains the data.

In the t_bind structure, the data identifies a transport address. qlen specifies the maximum number of connect requests that can be queued. If the value of qlen is positive, the endpoint can be used to listen for connect requests. t_bind(3NSL) directs the transport provider to queue connect requests for the bound address immediately. The server must dequeue each connect request and accept or reject it. For a server that fully processes a single connect request and responds to it before receiving the next request, a value of 1 is appropriate for qlen. Servers that dequeue several connect requests before responding to any should specify a longer queue. The server in this example processes connect requests one at a time, so qlen is set to 1.

t_alloc(3NSL) is called to allocate the t_bind structure. t_alloc(3NSL) has three arguments: a file descriptor of a transport endpoint; the identifier of the structure to allocate; and a flag that specifies which, if any, netbuf buffers to allocate. T_ALL specifies to allocate all netbuf buffers, and causes the addr buffer to be allocated in this example. Buffer size is determined automatically and stored in the maxlen field.

Each transport provider manages its address space differently. Some transport providers allow a single transport address to be bound to several transport endpoints, while others require a unique address per endpoint. XTI and TLI differ in some significant ways in providing the address binding.

In TLI, based on its rules, a provider determines if it can bind the requested address. If not, it chooses another valid address from its address space and binds it to the transport endpoint. The application program must check the bound address to ensure that it is the one previously advertised to clients. In XTI, if the provider determines it cannot bind to the requested address, it fails the t_bind(3NSL) request with an error.

If t_bind(3NSL) succeeds, the provider begins queueing connect requests, entering the next phase of communication.

Connection Establishment

XTI/TLI imposes different procedures in this phase for clients and servers. The client starts connection establishment by requesting a connection to a specified server using t_connect(3NSL). The server receives a client's request by calling t_listen(3NSL). The server must accept or reject the client's request. It calls t_accept(3NSL) to establish the connection, or t_snddis(3NSL) to reject the request. The client is notified of the result when t_connect(3NSL) returns.

TLI supports two facilities during connection establishment that might not be supported by all transport providers:

These facilities produce protocol-dependent software (see "Guidelines to Protocol Independence").

Client

The steps for the client to establish a connection are shown in Example 3-5.


Example 3-5 Client-to-Server Connection

if ((sndcall = (struct t_call *) t_alloc(fd, T_CALL, T_ADDR))
      == (struct t_call *) NULL) {
   t_error("t_alloc failed");
   exit(3);
}

/*
 * Because it assumes it knows the format of the provider's
 * address, this program is transport-dependent
 */
sndcall->addr.len = sizeof(int);
*(int *) sndcall->addr.buf = SRV_ADDR;
if (t_connect( fd, sndcall, (struct t_call *) NULL) == -1 ) {
   t_error("t_connect failed for fd");
   exit(4);
}

The t_connect(3NSL) call connects to the server. The first argument of t_connect(3NSL) identifies the client's endpoint, and the second argument points to a t_call structure that identifies the destination server. This structure has the following format:

struct t_call {
 	struct netbuf addr;
 	struct netbuf opt;
 	struct netbuf udata;
 	int sequence;
}

addr identifies the address of the server, opt specifies protocol-specific options to the connection, and udata identifies user data that can be sent with the connect request to the server. The sequence field has no meaning for t_connect(3NSL). In this example, only the server's address is passed.

t_alloc(3NSL) allocates the t_call structure dynamically. The third argument of t_alloc(3NSL) is T_ADDR, which specifies that the system needs to allocate a netbuf buffer. The server's address is then copied to buf, and len is set accordingly.

The third argument of t_connect(3NSL) can be used to return information about the newly established connection, and can return any user data sent by the server in its response to the connect request. The third argument here is set to NULL by the client. The connection is established on successful return of t_connect(3NSL). If the server rejects the connect request, t_connect(3NSL) sets t_errnoto TLOOK.

Event Handling

The TLOOK error has special significance. TLOOK is set if an XTI/TLI routine is interrupted by an unexpected asynchronous transport event on the endpoint. TLOOK does not report an error with an XTI/TLI routine, but the normal processing of the routine is not done because of the pending event. The events defined by XTI/TLI are listed in Table 3-7.

Table 3-7 Asynchronous Endpoint Events

Name 

Description 

T_LISTEN

Connection request arrived at the transport endpoint 

T_CONNECT

Confirmation of a previous connect request arrived (generated when a server accepts a connect request)  

T_DATA

User data has arrived 

T_EXDATA

Expedited user data arrived 

T_DISCONNECT

Notice that an aborted connection or a rejected connect request arrived 

T_ORDREL

A request for orderly release of a connection arrived 

T_UDERR

Notice of an error in a previous datagram arrived. (See "Read/Write Interface".)

The state table in "State Transitions" shows which events can happen in each state. t_look(3NSL) lets a user determine what event has occurred if a TLOOK error is returned. In the example, if a connect request is rejected, the client exits.

Server

When the client calls t_connect(3NSL), a connect request is sent at the server's transport endpoint. For each client, the server accepts the connect request and spawns a process to service the connection.

if ((call = (struct t_call *) t_alloc(listen_fd, T_CALL, T_ALL))
      == (struct t_call *) NULL) {
   t_error("t_alloc of t_call structure failed");
   exit(5);
}
while(1) {
   if (t_listen( listen_fd, call) == -1) {
      t_error("t_listen failed for listen_fd");
      exit(6);
   }
   if ((conn_fd = accept_call(listen_fd, call)) != DISCONNECT)
      run_server(listen_fd);
}

The server allocates a t_call structure, then does a closed loop. The loop blocks on t_listen(3NSL) for a connect request. When a request arrives, the server calls accept_call() to accept the connect request. accept_call accepts the connection on an alternate transport endpoint (as discussed below) and returns the handle of that endpoint. (conn_fd is a global variable.) Because the connection is accepted on an alternate endpoint, the server can continue to listen on the original endpoint. If the call is accepted without error, run_server spawns a process to service the connection.

XTI/TLI supports an asynchronous mode for these routines that prevents a process from blocking. See "Advanced Topics".

When a connect request arrives, the server calls accept_call to accept the client's request, as Example 3-6 shows.


Note -

It is implicitly assumed that this server only needs to handle a single connection request at a time. This is not normally true of a server. The code required to handle multiple simultaneous connection requests is complicated because of XTI/TLI event mechanisms. See "Advanced Programming Example" for such a server.



Example 3-6 accept_call Function

accept_call(listen_fd, call)
int listen_fd;
struct t_call *call;
{
   int resfd;

   if ((resfd = t_open("/dev/exmp", O_RDWR, (struct t_info *) NULL))
         == -1) {
      t_error("t_open for responding fd failed");
      exit(7);
 	}
   if (t_bind(resfd,(struct t_bind *) NULL, (struct t_bind *NULL))
         == -1) {
      t_error("t_bind for responding fd failed");
      exit(8);
   }
   if (t_accept(listen_fd, resfd, call) == -1) {
      if (t_errno == TLOOK) {								/* must be a disconnect */
         if (t_rcvdis(listen_fd,(struct t_discon *) NULL) == -1) {
            t_error("t_rcvdis failed for listen_fd");
            exit(9);
         }
         if (t_close(resfd) == -1) {
            t_error("t_close failed for responding fd");
            exit(10);
         }
         /* go back up and listen for other calls */
         return(DISCONNECT);
      }
      t_error("t_accept failed");
      exit(11);
   }
   return(resfd);
}

accept_call() has two arguments:

listen_fd The file handle of the transport endpoint where the connect request arrived.
callPoints to a t_call structure that contains all information associated with the connect request

The server first opens another transport endpoint by opening the clone device special file of the transport provider and binding an address. A NULL specifies not to return the address bound by the provider. The new transport endpoint, resfd, accepts the client's connect request.

The first two arguments of t_accept(3NSL) specify the listening transport endpoint and the endpoint where the connection is accepted, respectively. Accepting a connection on the listening endpoint prevents other clients from accessing the server for the duration of the connection.

The third argument of t_accept(3NSL) points to the t_call structure containing the connect request. This structure should contain the address of the calling user and the sequence number returned by t_listen(3NSL). The sequence number is significant if the server queues multiple connect requests. The "Advanced Topics" shows an example of this. The t_call structure also identifies protocol options and user data to pass to the client. Because this transport provider does not support protocol options or the transfer of user data during connection, the t_call structure returned by t_listen(3NSL) is passed without change to t_accept(3NSL).

The example is simplified. The server exits if either the t_open(3NSL) or t_bind(3NSL) call fails. exit(2) closes the transport endpoint of listen_fd, causing a disconnect request to be sent to the client. The client's t_connect(3NSL) call fails, setting t_errno to TLOOK.

t_accept(3NSL) can fail if an asynchronous event occurs on the listening endpoint before the connection is accepted, and t_errno is set to TLOOK. Table 3-8 shows that only a disconnect request can be sent in this state with only one queued connect request. This event can happen if the client undoes a previous connect request. If a disconnect request arrives, the server must respond by calling t_rcvdis(3NSL). This routine argument is a pointer to a t_discon structure, which is used to retrieve the data of the disconnect request. In this example, the server passes a NULL.

After receiving a disconnect request, accept_call closes the responding transport endpoint and returns DISCONNECT, which informs the server that the connection was disconnected by the client. The server then listens for further connect requests.

Figure 3-4 illustrates how the server establishes connections:

Figure 3-4 Listening and Responding Transport Endpoints

Graphic

The transport connection is established on the new responding endpoint, and the listening endpoint is freed to retrieve further connect requests.

Data Transfer

After the connection is established, both the client and the server can transfer data through the connection using t_snd(3NSL) and t_rcv(3NSL). XTI/TLI does not differentiate the client from the server from this point on. Either user can send data, receive data, or release the connection.

The two classes of data on a transport connection are:

  1. Normal data

  2. Expedited data

Expedited data is for urgent data. The exact semantics of expedited data vary between transport providers. Not all transport protocols support expedited data (see t_open(3NSL)).

Most connection-oriented mode protocols transfer data in byte streams. "Byte stream" implies no message boundaries in data sent over a connection. Some transport protocols preserve message boundaries over a transport connection. This service is supported by XTI/TLI, but protocol-independent software must not rely on it.

The message boundaries are invoked by the T_MORE flag of t_snd(3NSL) and t_rcv(3NSL). The messages, called transport service data units (TSDU), can be transferred between two transport users as distinct units. The maximum message size is defined by the underlying transport protocol. Get the message size through t_open(3NSL) or t_getinfo(3NSL).

You can send a message in multiple units. Set the T_MORE flag on every t_snd(3NSL) call, except the last to send a message in multiple units. The flag specifies that the data in the current and the next t_snd(3NSL) calls are a logical unit. Send the last message unit with T_MORE turned off to specify the end of the logical unit.

Similarly, a logical unit can be sent in multiple units. If t_rcvv(3NSL) returns with the T_MORE flag set, the user must call t_rcvv(3NSL) again to receive the rest of the message. The last unit in the message is identified by a call to t_rcvv(3NSL) that does not set T_MORE.

The T_MORE flag implies nothing about how the data is packaged below XTI/TLI or how the data is delivered to the remote user. Each transport protocol, and each implementation of a protocol, can package and deliver the data differently.

For example, if a user sends a complete message in a single call to t_snd(3NSL)t_snd, there is no guarantee that the transport provider delivers the data in a single unit to the receiving user. Similarly, a message transmitted in two units can be delivered in a single unit to the remote transport user.

If supported by the transport, the message boundaries are preserved only by setting the value of T_MORE for t_snd(3NSL) and testing it after t_rcvv(3NSL). This guarantees that the receiver sees a message with the same contents and message boundaries as was sent.

Client

The example server transfers a log file to the client over the transport connection. The client receives the data and writes it to its standard output file. A byte stream interface is used by the client and server, with no message boundaries. The client receives data by the following:

while ((nbytes = t_rcv(fd, buf, nbytes, &flags))!= -1){
   if (fwrite(buf, 1, nbytes, stdout) == -1) {
      fprintf(stderr, "fwrite failed\n");
      exit(5);
   }
}

The client repeatedly calls t_rcvv(3NSL) to receive incoming data. t_rcvv(3NSL) blocks until data arrives. t_rcvv(3NSL) writes up to nbytes of the data available into buf and returns the number of bytes buffered. The client writes the data to standard output and continues. The data transfer loop ends when t_rcvv(3NSL) fails. t_rcvv(3NSL) fails when an orderly release or disconnect request arrives. If fwrite(3C) fails for any reason, the client exits, which closes the transport endpoint. If the transport endpoint is closed (either by exit(2) or t_close(3NSL)) during data transfer, the connection is aborted and the remote user receives a disconnect request.

Server

The server manages its data transfer by spawning a child process to send the data to the client. The parent process continues the loop to listen for more connect requests. run_server is called by the server to spawn this child process, as shown in Example 3-7.


Example 3-7 Spawning Child Process to Loopback and Listen

connrelease()
{
   /* conn_fd is global because needed here */
   if (t_look(conn_fd) == T_DISCONNECT) {
      fprintf(stderr, "connection aborted\n");
      exit(12);
   }
   /* else orderly release request - normal exit */
   exit(0);
}
run_server(listen_fd)
int listen_fd;
{
   int nbytes;
   FILE *logfp;                    /* file pointer to log file */
   char buf[1024];

   switch(fork()) {
   case -1:
      perror("fork failed");
      exit(20);
   default:									/* parent */
      /* close conn_fd and then go up and listen again*/
      if (t_close(conn_fd) == -1) {
         t_error("t_close failed for conn_fd");
         exit(21);
      }
      return;
   case 0:                        /* child */
      /* close listen_fd and do service */
      if (t_close(listen_fd) == -1) {
         t_error("t_close failed for listen_fd");
         exit(22);
      }
      if ((logfp = fopen("logfile", "r")) == (FILE *) NULL) {
         perror("cannot open logfile");
         exit(23);
      }
      signal(SIGPOLL, connrelease);
      if (ioctl(conn_fd, I_SETSIG, S_INPUT) == -1) {
         perror("ioctl I_SETSIG failed");
         exit(24);
      }
      if (t_look(conn_fd) != 0){      /*disconnect there?*/
         fprintf(stderr, "t_look: unexpected event\n");
         exit(25);
      }
      while ((nbytes = fread(buf, 1, 1024, logfp)) > 0)
         if (t_snd(conn_fd, buf, nbytes, 0) == -1) {
            t_error("t_snd failed");
            exit(26);
         }

After the fork, the parent process returns to the main listening loop. The child process manages the newly established transport connection. If the fork fails, exit(2) closes both transport endpoints, sending a disconnect request to the client, and the client's t_connect(3NSL) call fails.

The server process reads 1024 bytes of the log file at a time and sends the data to the client using t_snd(3NSL). buf points to the start of the data buffer, and nbytes specifies the number of bytes to transmit. The fourth argument can be zero or one of the two optional flags below:

Neither flag is set by the server in this example.

If the user floods the transport provider with data, t_snd(3NSL) blocks until enough data is removed from the transport.

t_snd(3NSL) does not look for a disconnect request (showing that the connection was broken). If the connection is aborted, the server should be notified, since data can be lost. One solution is to call t_look(3NSL) to check for incoming events before each t_snd(3NSL) call or after a t_snd(3NSL) failure. The example has a cleaner solution. The I_SETSIG ioctl(2) lets a user request a signal when a specified event occurs. See the streamio(7I) manpage. S_INPUT causes a signal to be sent to the user process when any input arrives at the endpoint conn_fd. If a disconnect request arrives, the signal-catching routine (connrelease) prints an error message and exits.

If the server alternates t_snd(3NSL) and t_rcv(3NSL) calls, it can use t_rcv(3NSL) to recognize an incoming disconnect request.

Connection Release

At any time during data transfer, either user can release the transport connection and end the conversation. There are two forms of connection release.

See "Transport Selection" for information on how to select a transport that supports orderly release.

Server

This example assumes that the transport provider supports orderly release. When all the data has been sent by the server, the connection is released as follows:

if (t_sndrel(conn_fd) == -1) {
   t_error("t_sndrel failed");
   exit(27);
}
pause(); /* until orderly release request arrives */

Orderly release requires two steps by each user. The server can call t_sndrel(3NSL). This routine sends a disconnect request. When the client receives the request, it can continue sending data back to the server. When all data have been sent, the client calls t_sndrel(3NSL) to send a disconnect request back. The connection is released only after both users have received a disconnect request.

In this example, data is transferred only from the server to the client. So there is no provision to receive data from the client after the server initiates release. The server calls pause(2) after initiating the release.

The client responds with its orderly release request, which generates a signal caught by connrelease(). (In Example 3-7, the server issued an I_SETSIG ioctl(2) to generate a signal on any incoming event.) The only XTI/TLI event possible in this state is a disconnect request or an orderly release request, so connrelease exits normally when the orderly release request arrives. exit(2) from connrelease closes the transport endpoint and frees the bound address. To close a transport endpoint without exiting, call t_close(3NSL).

Client

The client releases the connection similar to the way the server releases it. The client processes incoming data until t_rcv(3NSL) fails. When the server releases the connection (using either t_snddis(3NSL) or t_sndrel(3NSL)), t_rcv(3NSL) fails and sets t_errno to TLOOK. The client then processes the connection release as follows:

if ((t_errno == TLOOK) && (t_look(fd) == T_ORDREL)) {
   if (t_rcvrel(fd) == -1) {
      t_error("t_rcvrel failed");
      exit(6);
 	}
 	if (t_sndrel(fd) == -1) {
      t_error("t_sndrel failed");
      exit(7);
 	}
 	exit(0);
 }

Each event on the client's transport endpoint is checked for an orderly release request. When one is received, the client calls t_rcvrel(3NSL) to process the request and t_sndrel(3NSL) to send the response release request. The client then exits, closing its transport endpoint.

If a transport provider does not support the orderly release, use abortive release with t_snddis(3NSL) and t_rcvdis(3NSL). Each user must take steps to prevent data loss. For example, use a special byte pattern in the data stream to indicate the end of a conversation.