| Page: | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | |
| 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | |
| 46 | 47 | 48 | 49 | 50 | 51 |
This chapter presents an overview of the Transport Interface (TLI) services that support the data transfer between two user processes. Figure 3-1 illustrates the Transport Interface.
![]() |
Figure 3-1 Transport Interface
The transport provider provides the services of the Transport Interface and the transport user requires these services. An example of a transport provider is the ISO transport protocol; a transport user can be a networking application or session layer protocol.
The transport user accesses the services of the transport provider by issuing the appropriate service requests. For example, the user could request to transfer data over a connection. Similarly, the transport provider notifies the user of various events, such as the arrival of data on a connection.
The Network Services Library of UNIX System V includes a set of functions that support the TLI services for user processes. See intro(3). These functions let a user initiate requests to the provider and process incoming events. Programs using TLI can link the appropriate routines as follows:
| Home |
|---|
Two service modes are provided by the Transport Interface, connection-mode and connectionless-mode.
-- involve short-term request/response interactions
-- exhibit a high level of redundancy
-- are dynamically reconfigurable
-- do not require guaranteed, in-sequence data delivery.
The connection-mode transport service is characterized by four phases: local management, connection establishment, data transfer, and connection release.
![]() |
Figure 3-2 Channel between User and Provider
| Home |
|---|
Another necessary local function for each user is to establish an identity with the transport provider. Each user is identified by a transport address. A transport address is associated with each transport endpoint, and one user process can manage several transport endpoints. In connection-mode service, one user requests a connection to another user by specifying that user's address. The structure of a transport address is defined by the address space of the transport provider. An address can be as simple as a random character string (for example, "file_server"), or as complex as an encoded bit pattern that specifies all information needed to route data through a network. Each transport provider defines its own mechanism for identifying users. Addresses can be assigned to each transport endpoint by t_bind(3N).
In addition to t_open and t_bind, several routines are available to support local operations. Table 3-1 lists all local management routines of the Transport Interface.
| Command | Description |
|---|---|
| t_alloc | Allocates Transport Interface data structures. |
| t_bind | Binds a transport address to a transport endpoint. |
| t_close | Closes a transport endpoint. |
| t_error | Prints a Transport Interface error message. |
| t_free | Frees structures allocated using t_alloc. |
| t_getinfo | Returns a set of parameters associated with a particular transport provider. |
| t_getstate | Returns the state of a transport endpoint. |
| t_look | Returns the current event on a transport endpoint. |
| t_open | Establishes a transport endpoint connected to a chosen transport provider. |
| t_optmgmt | Negotiates protocol-specific options with the transport provider. |
| t_sync | Synchronizes a transport endpoint with the transport provider. |
| t_unbind | Unbinds a transport address from a transport endpoint. |
![]() |
Figure 3-3 Transport Connection
| Home |
|---|
This phase is illustrated by a client-server relationship between two transport users. One user, the server, typically advertises some service to a group of users, and then listens for requests from those users. As each client requires the service, it attempts to connect to the server using the server's advertised transport address. The t_connect(3N) routine initiates the connect request. One argument to t_connect, the transport address, identifies the server that the client wants to access. The server is notified of each incoming request using t_listen(3N), and can call t_accept(3N) to accept the client's request for access to the service. If the request is accepted, the transport connection is established.
Table 3-2 summarizes all routines available for establishing a transport connection.
| Command | Description |
|---|---|
| t_accept | Accepts a request for a transport connection. |
| t_connect | Establishes a connection with the transport user at a specified destination. |
| t_listen | Retrieves an indication of a connect request from another transport user. |
| t_rcvconnect | Completes connection establishment if t_connect is called in asynchronous mode
(see Section 3.5). |
| Command | Description |
|---|---|
| t_rcv | Retrieves data that arrives over a transport connection. |
| t_snd | Sends data over an established transport connection. |
Some transport providers also support an orderly release facility that enables users to terminate communication with no data loss. The functions t_sndrel(3N) and t_rcvrel(3N) support this capability. Table 3-4 summarizes the connection release routines.
| Home |
|---|
| Command | Description |
|---|---|
| t_rcvdis | Returns an indication of an aborted connection, including a reason code and user data. |
| t_rcvrel | Returns an indication that the remote user requested an orderly connection release. |
| t_snddis | Aborts a connection or rejects a connect request. |
| t_sndrel | Requests the orderly release of a connection. |
The connectionless-mode transport service is characterized by two phases: local management and data transfer.
| Command | Description |
|---|---|
| t_rcvudata | Retrieves a message sent by another transport user. |
| t_rcvuderr | Retrieves error information associated with a previously sent message. |
| t_sndudata | Sends a message to the specified destination user. |
The Transport Interface has two components:
The state transition rules are presented in Section 3.6 as state tables. The state tables define the legal sequence of library calls based on state information and event handling. These events include user-generated library calls, as well as provider-generated event indications.
| Anyone using the Transport Interface must completely understand all possible state transitions before writing software using the interface. |
| Home |
|---|
This section describes the connection-mode service of the Transport Interface. The connection-mode service can be illustrated using a client-server paradigm. The important concepts of connection-mode service are presented using two programming examples. The first example illustrates how a client establishes a connection to a server and then communicates with the server. The second example shows the server's side of the interaction.
In the examples, the client establishes a connection with a server process. The server then transfers a file to the client. The client, in turn, receives the data from the server and writes it to its standard output file.
Before the client and server can establish a transport connection, each must first establish a local channel (the transport endpoint) to the transport provider using t_open, and establish its identity (or address) using t_bind.
The set of services supported by TLI cannot be implemented by all transport protocols. Each transport provider has a set of characteristics associated with it that determine the services it offers and the limits associated with those services. This information is returned to the user by t_open, and consists of the following.
| addr | maximum size of a transport address |
| options | maximum number of 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 the Transport Interface are:
Only one such service can be associated with the transport provider identified by t_open.
| Home |
|---|
| t_open returns the default provider characteristics associated with a transport endpoint. However, some characteristics can change after an endpoint is opened. This occurs if the characteristics are associated with negotiated options (option negotiation is described later in this chapter). For example, if the support of expedited data transfer is a negotiated option, the value of this characteristic can change. t_getinfo can be called to retrieve the current characteristics of a transport endpoint. |
Once a user establishes a transport endpoint with the chosen transport provider, it must establish its identity. As mentioned earlier, t_bind accomplishes this by binding a transport address to the transport endpoint. In addition, for servers, this routine informs the transport provider that the endpoint is used to listen for incoming connect requests, also called connect indications.
An optional facility, t_optmgmt(3N), is also available during the local management phase. It enables a user to negotiate the values of protocol options with the transport provider. Each transport protocol is expected to define its own set of negotiable protocol options, which can include such information as Quality-of-Service parameters. Because of the protocol-specific nature of options, only applications written for a particular protocol environment are expected to use this facility.
The local management requirements of the example client and server are used to discuss details of these facilities. The following are the definitions needed by the client program, followed by its necessary local management steps.
Example:
#include <stdio.h>
#include <tiuser.h>
#include <fcntl.h>
#define SRV_ADDR1 /* server's well known 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/tivc", O_RDWR, NULL)) < 0) {
t_error("t_open failed");
exit(1);
}
if (t_bind(fd, NULL, NULL) < 0) {
t_error("t_bind failed");
exit(2);
}
| Home |
|---|
The first argument to t_open is the pathname of a file system node that identifies the transport protocol that will supply the transport service. In this example, /dev/tivc is a STREAMS clone device node that identifies a generic, connection-based transport protocol. See clone(7). The clone device finds an available minor device of the transport provider for the user. It is opened for both reading and writing, as specified by the O_RDWR open flag. The third argument can be used to return the service characteristics of the transport provider to the user. This information is useful when writing protocol-independent software. For simplicity, the client and server in this example ignore this information and assume the transport provider has the following characteristics:
Because these characteristics are not needed by the user, NULL is specified in the third argument to t_open. If the user needed a service other than T_COTS_ORD, another transport provider is opened. An example of the T_CLTS service invocation is presented in Section 3.3.
The return value of t_open is an identifier for the transport endpoint to be used by all subsequent Transport Interface function calls. This identifier is actually a file descriptor obtained by opening the transport protocol file. See open(2). The significance of this is highlighted in Section 3.4.
After the transport endpoint is created, the client calls t_bind to assign an address to the endpoint. The first argument identifies the transport endpoint. The second argument describes the address the user would like to bind to the endpoint, and the third argument is set on return from t_bind to specify the address that the provider bound.
The address associated with a server's transport endpoint is important, because that is the address used by all clients to access the server. However, the typical client does not care what its own address is, because no other process will try to access it. That is the case in this example, where the second and third arguments to t_bind are set to NULL. A NULL second argument directs the transport provider to choose an address for the user. A NULL third argument indicates that the user does not care what address was assigned to the endpoint.
If either t_open or t_bind fail, the program calls t_error(3N) to print an appropriate error message to stderr. If any Transport Interface routine fails, the global integer t_errno is assigned an appropriate transport error value. A set of such error values is defined (in <tiuser.h>) for the Transport Interface, and t_error prints an error message corresponding to the value in t_errno. This routine is analogous to perror(3C), which prints an error message based on the value of errno. If the error associated with a transport function is a system error, t_errno is set to TSYSERR, and errno is set to the appropriate value.
The server in this example must take similar local management steps before communication can begin. The server must establish a transport endpoint through which it listens for connect indications. The necessary definitions and local management steps are shown in the following example.
| Home |
|---|
Example:
#include <tiuser.h>
#include <stropts.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
#define DISCONNECT -1
#define SRV_ADDR 1 /* server's well known 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/tivc", O_RDWR, NULL)) < 0) {
t_error("t_open failed for listen_fd");
exit(1);
}
/*
* By assuming that the address is an integer value,
* this program can not run over another protocol.
*/
if ((bind = (struct t_bind *)t_alloc(listen_fd, T_BIND, T_ALL) = = NULL) {
t_error("t_alloc of t_bind structure failed");
exit(2);
}
bind_>qlen = 1;
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);
}
/*
* Was the correct address bound?
*/
if (*(int *)bind_>addr.buf != SRV_ADDR) {
fprintf(stderr, "t_bind bound wrong address);
exit(4);
}
| Home |
|---|
As with the client, the first step is to call t_open to establish a transport endpoint with the desired transport provider. This endpoint, listen_fd, is used to listen for connect indications. Next, the server must bind its well-known address to the endpoint. This address is used by each client to access the server. The second argument to t_bind requests that a particular address be bound to the transport endpoint. This argument points to a t_bind structure with the following format:
struct t_bind {
struct netbuf addr;
unsigned qlen;
}
where addr describes the address to be bound, and qlen indicates the maximum outstanding connect indications that can arrive at this endpoint. All Transport Interface structure and constant definitions are found in <tiuser.h>.
The address is specified using a netbuf structure that contains the following members:
struct netbuf {
unsigned int maxlen;
unsigned int len;
char *buf;
}
buf points to a buffer containing the data, len specifies the bytes of data in the buffer, and maxlen indicates the maximum bytes the buffer can hold. maxlen only needs to be set when data is returned to the user by a Transport Interface routine. For the t_bind structure, the data pointed to by buf identifies a transport address. The structure of addresses may vary among each protocol implementation under the Transport Interface. The netbuf structure is intended to support any such structure.
If the value of qlen is greater than 0, the transport endpoint can be used to listen for connect indications. In such cases, t_bind directs the transport provider to immediately begin queuing connect indications destined for the bound address. Furthermore, the value of qlen indicates the maximum outstanding connect indications the server wishes to process. The server must respond to each connect indication, either accepting or rejecting the request for connection. An outstanding connect indication is one to which the server has not yet responded. Often, a server fully processes a single connect indication and responds to it before receiving the next indication. In this case, a value of 1 is appropriate for qlen. However, some servers can wish to retrieve several connect indications before responding to any of them. In such cases, qlen indicates the maximum number of such outstanding indications the server will process. An example of a server that manages multiple outstanding connect indications is presented in Section 3.5.
t_alloc(3N) is called to allocate the t_bind structure needed by t_bind. t_alloc takes three arguments. The first is a file descriptor that references a transport endpoint. This is used to access the characteristics of the transport provider. See t_open(3N). The second argument identifies the appropriate Transport Interface structure to be allocated. The third argument specifies which, if any, netbuf buffers should be allocated for that structure. T_ALL specifies that all netbuf buffers associated with the structure should be allocated and causes the addr buffer to be allocated in this example. The size of this buffer is determined from the transport provider characteristic that defines the maximum address size. The maxlen field of this netbuf structure is set to the size of the newly allocated buffer by t_alloc. The use of t_alloc helps ensure the compatibility of user programs with future releases of the Transport Interface.
The server in this example processes connect indications one at a time, so qlen is set to 1. The address information is then assigned to the newly allocated t_bind structure. This t_bind structure is used to pass information to t_bind in the second argument, and is also used to return information to the user in the third argument.
| Home |
|---|
On return, the t_bind structure contains the address that is bound to the transport endpoint. If the provider could not bind the requested address (perhaps because it was bound to another transport endpoint), it chooses another appropriate address.
| Each transport provider manages its address space differently. Some transport providers allow a single transport address to be bound to several transport endpoints. Others require a unique address per endpoint. The Transport Interface supports either choice. Based on its address management 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 server must check the bound address to ensure that it is the one previously advertised to clients. Otherwise, the clients are unable to reach the server.
If t_bind succeeds, the provider begins queuing connect indications. The next phase of communication, connection establishment, is entered.
Connection establishment procedures highlight the distinction between clients and servers. The Transport Interface imposes a different set of procedures in this phase for each type of transport user. The client initiates the connection establishment procedure by requesting a connection to a particular server using t_connect(3N). The server is then notified of the client's request by calling t_listen(3N). The server either accepts or rejects the client's request. It calls t_accept(3N) to establish the connection or t_snddis(3N) to reject the request. The client is notified of the server's decision when t_connect completes.
TLI supports two facilities during connection establishment that may not be supported by all transport providers. The first is the ability to transfer data between the client and server when establishing the connection. The client can send data to the server when it requests a connection. This data is passed to the server by t_listen. Similarly, the server can send data to the client when it accepts or rejects the connection. The connect characteristic returned by t_open determines how much data, if any, two users can transfer during connect establishment.
The second optional service supported by the Transport Interface during connection establishment is the negotiation of protocol options. The client can specify protocol options that it would like the transport provider and/or the remote user to use. The Transport Interface supports both local and remote option negotiation. Option negotiation is inherently a protocol-specific function. Using this facility is discouraged if protocol-independent software is a goal.
Continuing with the client/server example, the steps needed by the client to establish a connection are shown in the following example.
| Home |
|---|
Example:
/*
* By assuming that the address is an integer value,
* this program can not run over another protocol.
*/
if ((sndcall = (struct t_call *)t_alloc(fd, T_CALL, T_ADDR)) = = NULL) {
t_error("t_alloc failed");
exit(3);
}
sndcall-->addr.len = sizeof(int);
*(int *)sndcall_>addr.buf = SRV_ADDR;
if (t_connect(fd, sndcall, NULL) < 0) {
t_error("t_connect failed for fd");
exit(4);
}
t_connect establishes the connection with the server. The first argument to t_connect identifies the transport endpoint through which the connection is established, and the second argument identifies the destination server. This argument is a pointer to a t_call structure, which 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 that the client would like to associate with the connection, and udata identifies user data that can be sent with the connect request to the server. sequence has no meaning for t_connect.
t_alloc is called to allocate the t_call structure dynamically. Once allocated, the appropriate values are assigned. In this example, no options or user data are associated with the t_connect call, but the server's address must be set. The third argument to t_alloc is set to T_ADDR to indicate that an appropriate netbuf buffer should be allocated for the address. The server's address is then assigned to buf, and len is set accordingly.
The third argument to t_connect can be used to return information about the newly established connection to the user, and can retrieve any user data sent by the server in its response to the connect request. Here it is set to NULL by the client to indicate that this information is not needed. The connection is established on successful return of t_connect. If the server rejects the connect request, t_connect fails and sets t_errno to TLOOK.
The TLOOK error has special significance in the Transport Interface. Some Transport Interface routines can be interrupted by an unexpected asynchronous transport event on the given transport endpoint, and TLOOK notifies the user that an event has occurred. As such, TLOOK does not indicate an error with a Transport Interface routine, but the normal processing of that routine is not performed because of the pending event. The events defined by the Transport Interface are listed next.
| Home |
|---|
As described in the state tables of Section 3.6, it is possible in some states to receive one of several asynchronous events. The t_look(3N) routine enables a user to determine what event occurred if a TLOOK error is returned. The user can then process that event accordingly. In the example, if a connect request is rejected, the event passed to the client is a disconnect indication. The client exits if its request is rejected.
Returning to the example, when the client calls t_connect, a connect indication is generated on the server's listening transport endpoint. The steps required by the server to process the event are presented in the example that follows. For each client, the server accepts the connect request and spawns a server process to manage the connection.
Example:
if ((call = (struct t_call *)t_alloc(listen_fd, T_CALL, T_ALL) = = NULL) {
t_error("t_alloc of t_call structure failed");
exit(5);
}
while (1) {
if (t_listen(listen_fd, call) < 0) {
t_error("t_listen failed for listen_fd");
exit(6);
}
if ((conn_fd = accept_call(listen_fd, call)) !=DISCONNECT)
run_server(listen_fd);
}
}
| Home |
|---|
The server loops forever, processing each connect indication. First, the server calls t_listen to retrieve the next connect indication. When one 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 value of that endpoint. conn_fd is a global variable that identifies the transport endpoint where the connection is established. Because the connection is accepted on an alternate endpoint, the server can continue listening for connect indications on the endpoint that was bound for listening. If the call is accepted without error, run_server spawns a process to manage the connection.
The server allocates a t_call structure to be used by t_listen. The third argument to t_alloc, T_ALL, allocates all necessary buffers for retrieving the caller's address, options, and user data. The transport provider in this example does not support the transfer of user data during connection establishment, nor does it support any protocol options. Therefore, t_alloc does not allocate buffers for the user data and options. It must, however, allocate a buffer large enough to store the address of the caller. t_alloc determines the buffer size from the addr characteristic returned by t_open. The maxlen field of each netbuf structure is set to the size of the newly allocated buffer by t_alloc (maxlen is 0 for the user data and options buffers).
Using the t_call structure, the server calls t_listen to retrieve the next connect indication. If one is currently available, it is returned to the server immediately. Otherwise, t_listen blocks until a connect indication arrives.
| The Transport Interface supports an asynchronous mode for such routines that prevent a process from blocking. This feature is discussed in Section 3.5. |
When a connect indication arrives, the server calls accept_call to accept the client's request, as shown in the following example.
Example:
accept_call(listen_fd, call)
int listen_fd;
struct t_call *call;
{
int resfd;
if ((resfd = t_open("/dev/tive", O_RDWR, NULL)) < 0) {
t_error("t_open for responding fd failed");
exit(7);
}
if (t_bind(resfd, NULL, NULL) < 0) {
t_error("t_bind for responding fd failed");
exit(8);
}
if (t_accept(listen_fd, resfd, call) < 0) {
if (t_errno = = TLOOK) { /* must be a disconnect */
if (t_rcvdis(listen_fd, NULL) < 0) {
t_error("t_rcvdis failed for listen_fd");
exit(9);
}
if (t_close(resfd) < 0) {
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);
}
| Home |
|---|
accept_call takes two arguments. listen_fd identifies the transport endpoint where the connect indication arrived, and call is a pointer to a t_call structure that contains all information associated with the connect indication. The server first establishes another transport endpoint by opening the clone device node of the transport provider and binding an address. As with the client, a NULL value is passed to t_bind to specify that the user does not care what address is bound by the provider. The newly established transport endpoint, refds, is used to accept the client's connect request.
The first two arguments of t_accept specify the listening transport endpoint and the endpoint where the connection is accepted respectively. A connection can be accepted on the listening endpoint. However, this would prevent other clients from accessing the server for the duration of that connection.
The third argument of t_accept points to the t_call structure associated with the connect indication. This structure should contain the address of the calling user and the sequence number returned by t_listen. The value of sequence has particular significance if the server manages multiple outstanding connect indications. (Section 3.5 presents such an example.) Also, the t_call structure should identify protocol options the user wants to specify and user data that can be passed to the client. Because the transport provider in this example does not support protocol options or the transfer of user data during connection establishment, the t_call structure returned by t_listen can be passed without change to t_accept.
For simplicity in the example, the server exits if either the t_open or t_bind call fails. exit(2) closes the transport endpoint associated with listen_fd, causing the transport provider to pass a disconnect indication to the client that requested the connection. This disconnect indication notifies the client that the connection was not established; t_connect fails, setting t_errno to TLOOK.
t_accept may fail if an asynchronous event occurs on the listening transport endpoint before the connection is accepted, and t_errno is set to TLOOK. The state transition table in Section 3.6 shows that the only event that can occur in this state with only one outstanding connect indication is a disconnect indication. This event can occur if the client decides to undo the connect request it previously initiated. If a disconnect indication arrives, the server must retrieve the disconnect indication using t_rcvdis. This routine takes a pointer to a t_discon structure as an argument, which is used to retrieve information associated with a disconnect indication. In this example, however, the server does not care to retrieve this information, so it sets the argument to NULL. After receiving the disconnect indication, 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 indications.
Figure 3-4 illustrates how the server establishes connections.
| Home |
|---|
![]() |
Figure 3-4 Listening and Responding Transport Endpoints
The transport connection is established on the newly created responding endpoint, and the listening endpoint is freed to retrieve further connect indications.
Once the connection is established, both the client and server can begin transferring data over the connection using t_snd and t_rcv. In fact, the Transport Interface does not differentiate between the client and the server from this point on. Either user can send and receive data, or release the connection. The Transport Interface guarantees reliable, sequenced delivery of data over an existing connection.
Two data classes can be transferred over a transport connection, normal and expedited. Expedited data is typically associated with information of an urgent nature. The exact semantics of expedited data are subject to the interpretations of the transport provider. Furthermore, not all transport protocols support an expedited data class. See t_open(3N).
All transport protocols support the data transfer in byte stream mode, where "byte stream" implies no concept of message boundaries on data transferred over a connection. Some transport protocols support the preservation of message boundaries over a transport connection. This service is supported by the Transport Interface, but protocol-independent software must not rely on its existence.
The message interface for data transfer is supported by a special flag of t_snd and t_rcv called T_MORE. The messages, called Transport Service Data Units (TSDU), can be transferred between two transport users as distinct units. The maximum size of a TSDU is a characteristic of the underlying transport protocol. This information is available to the user from t_open and t_getinfo. Because the maximum TSDU size can be large (possibly unlimited), the Transport Interface enables a user to transmit a message in multiple units.
To send a message in multiple units over a transport connection, the user must set the T_MORE flag on every t_snd call except the last. This flag indicates that the user is going to send more data associated with the message in a subsequent call to t_snd. The last message unit should be transmitted with T_MORE turned off to indicate that this is the end of the TSDU.
| Home |
|---|
Similarly, a TSDU can be passed to the user on the receiving side in multiple units. Again, if t_rcv returns with the T_MORE flag set, the user should continue calling t_rcv to retrieve the remainder of the message. The last unit in the message is indicated by a call to t_rcv that does not set T_MORE.
| The T_MORE flag implies nothing about how the data
is packaged below the Transport Interface. Nor does it imply
how the data is delivered to the remote user. Each transport
protocol and each implementation of that protocol can package
and deliver the data differently.
For example, if a user sends a complete message in a single call to t_snd, there is no guarantee that the transport provider will deliver the data in a single unit to the remote transport user. Similarly, a TSDU transmitted in two message units can be delivered in a single unit to the remote transport user. The message boundaries are only preserved by noting the value of the T_MORE flag on t_snd and t_rcv. This guarantees that the receiving user sees a message with the same contents and boundaries sent by the remote user. |
Continuing with the client/server example, the server transfers a log file to the client over the transport connection. The client receives this data and writes it to its standard output file. A byte stream interface is used by the client and server, where message boundaries (that is, the T_MORE flag) are ignored. The client receives data using the following instructions:
while ((nbytes = t_rcv(fd, buf, 1024, &flags)) != -1) {
if (fwrite(buf, 1, nbytes, stdout) < 0) {
fprintf(stderr, "fwrite failed");
exit(5);
}
}
The client continuously calls t_rcv to process incoming data. If no data is currently available, t_rcv blocks until data arrives. t_rcv retrieves the available data up to 1024 bytes, which is the size of the client's input buffer, and returns the number of bytes that were received. The client then writes this data to standard output and continues. The data transfer phase is complete when t_rcv fails. t_rcv fails if an orderly release indication or disconnect indication arrives. If the fwrite(3S) call fails for any reason, the client exits, thereby closing the transport endpoint. If the transport endpoint is closed (either by exit or t_close) when it is in the data transfer phase, the connection is aborted and the remote user receives a disconnect indication.
Looking at the other side of the connection, the server manages its data transfer by spawning a child process to send the data to the client. The parent process then loops back to listen for further connect indications. run_server is called by the server to spawn this child process as follows.
| Home |
|---|
Example:
connrelease( )
{
/* conn_fd is global because needed here */
if (t_look(conn_fd) = = T_DISCONNECT) {
fprintf(stderr, "connection aborted");
exit(12);
}
/* else orderly release indication -- 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) < 0) {
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) < 0){
t_error("t_close failed for listen_fd");
exit(22);
}
if ((logfp = fopen("logfile", "r")) = = NULL){
perror("cannot open logfile");
exit(23);
}
signal(SIGPOLL, connrelease);
if (ioctl(conn_fd, I_SETSIG, S_INPUT) < 0){
perror("ioctl I_SETSIG failed");
exit(24);
}
if (t_look(conn_fd) != 0){ /* was disconnect already there? */
fprintf(stderr," t_look returned unexpected event" );
exit(25);
}
while ((nbytes = fread(buf, 1, 1024, logfp)) > 0){
if (t_snd(conn_fd, buf, nbytes, 0) < 0){
t_error("t_snd failed");
exit(26);
}
}
| Home |
|---|
After the fork, the parent process returns to the main processing loop and listens for further connect indications. Meanwhile, the child process manages the newly established transport connection. If the fork call fails, exit closes the transport endpoint associated with listen_fd. This passes a disconnect indication to the client, and the client's t_connect call fails.
The server process reads 1024 bytes of the log file at a time and sends that data to the client using t_snd. buf points to the start of the data buffer, and nbytes specifies the number of bytes to be transmitted. The fourth argument is used to specify optional flags. Two flags are currently supported: T_EXPEDITED can be set to indicate that the data is expedited, and T_MORE can be set to define message boundaries when transmitting messages over a connection. Neither flag is set by the server in this example.
If the user begins to flood the transport provider with data, the provider can exert back pressure to provide flow control. In such cases, t_snd blocks until the flow control is relieved and then resumes operation. t_snd does not complete until nbyte bytes are passed to the transport provider.
The t_snd routine does not look for a disconnect indication (signifying that the connection was broken) before passing data to the provider. Also, because the data traffic is flowing in one direction, the user never looks for incoming events. If, for some reason, the connection is aborted, the user should be notified because data can be lost. One option available to the user is to use t_look to check for incoming events before each t_snd call. A more efficient solution is the one presented in the example. The STREAMS I_SETSIG ioctl enables a user to request a signal when a given event occurs. See streamio(7) and signal(2). The STREAMS event of concern here is S_INPUT, which sends a signal to the user if any input arrives on the Stream referenced by conn_fd. If a disconnect indication arrives, the signal catching routine (connrelease) prints an appropriate error message and then exits.
If the data traffic flowed in both directions in this example, the user would not have to monitor the connection for disconnects. If the client alternated t_snd and t_rcv calls, it could rely on t_rcv to recognize an incoming disconnect indication.
At any point during data transfer, either user can release the transport connection and end the conversation. As mentioned earlier, two forms of connection release are supported by the Transport Interface. The first, abortive release, breaks a connection immediately and can result in the loss of any data that has not yet reached the destination user. t_snddis can be called by either user to generate an abortive release. Also, the transport provider can abort a connection if a problem occurs below the Transport Interface. t_snddis enables a user to send data to the remote user when aborting a connection. Although the abortive release is supported by all transport providers, the ability to send data when aborting a connection is not.
| Home |
|---|
When the remote user is notified of the aborted connection, t_rcvdis must be called to retrieve the disconnect indication. This call returns a reason code that indicates why the connection was aborted, and returns any user data that may have accompanied the disconnect indication (if the abortive release was initiated by the remote user). This reason code is specific to the underlying transport protocol and should not be interpreted by protocol-independent software.
The second form of connection release is orderly release, which gracefully terminates a connection and guarantees that no data is lost. All transport providers must support the abortive release procedure, but orderly release is an optional facility not supported by all transport protocols.
The client's view of connection release is similar to that of the server. The client continues to process incoming data until t_rcv fails. If the server releases the connection (using either t_snddis or t_sndrel), t_rcv 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) < 0){
t_error("t_rcvrel failed");
exit(6);
}
if (t_sndrel(fd) < 0){
t_error("t_sndrel failed");
exit(7);
}
exit(0);
}
t_error("t_rcv failed");
exit(8);
}
Under normal circumstances, the client terminates the transfer of data by calling t_sndrel to initiate the connection release. When the orderly release indication arrives at the client's side of the connection, the client checks to make sure the expected orderly release indication has arrived. If so, it proceeds with the release procedures by calling t_rcvrel to process the indication and t_sndrel to inform the server that it is also ready to release the connection. At this point the client exits, thereby closing its transport endpoint.
Because all transport providers do not support the orderly release facility just described, users can have to use the abortive release facility provided by t_snddis and t_rcvdis. However, steps must be taken by each user to prevent any loss of data. For example, a special byte pattern can be inserted in the data stream to indicate the end of a conversation. Many mechanisms are possible for preventing data loss. Each application and high-level protocol must choose an appropriate mechanism given the target protocol environment and requirements.
The client-server example presented assumes that the transport provider does support the orderly release of a connection. When all the data is transferred by the server, the connection can be released as follows:
if (t_sndrel(conn_fd) < 0){
t_error("t_sndrel failed");
exit(27);
}
pause( ); /* until orderly release indication arrives */
}
}
| Home |
|---|
The orderly release procedure consists of two steps by each user. The first user to complete data transfer can initiate a release using t_sndrel, as illustrated in the example. This routine informs the client that no more data is to be sent by the server. When the client receives such an indication, it can continue sending data back to the server if desired. When all data is transferred, however, the client must also call t_sndrel to indicate that it is ready to release the connection. The connection is released only after both users have requested an orderly release and received the corresponding indication from the other user.
In this example, data is transferred in one direction from the server to the client, so the server does not expect to receive data from the client after it has initiated the release procedure. Thus, the server simply calls pause(2) after initiating the release. Eventually, the remote user responds with its orderly release request, and the indication generates a signal that is caught by connrelease. Remember, the server earlier issued an I_SETSIG ioctl call to generate a signal on any incoming event. Since the only possible Transport Interface events that can occur in this situation are a disconnect indication or orderly release indication, connrelease terminates normally when the orderly release indication arrives. The exit call in connrelease closes the transport endpoint, thereby freeing the bound address for use by another user. If a user process wants to close a transport endpoint without existing, it can call t_close.
This section describes the TLI connectionless-mode service. Connectionless-mode service is appropriate for short-term request/response interactions, such as transaction processing applications. Data is transferred in self-contained units with no logical relationship required among multiple units.
The connectionless-mode services is described using a transaction server as an example. This server waits for incoming transaction queries, and processes and responds to each query.
As with connection-mode service, transport users must perform appropriate local management steps before data can be transferred. A user must choose the appropriate connectionless service provider using t_open and establish its identity using t_bind.
t_optmgmt can be used to negotiate protocol options that can be associated with the transfer of each data unit. As with the connection-mode service, each transport provider specifies the options, if any, that it supports. Option negotiation is therefore a protocol-specific activity.
In the example, the definitions and local management calls needed by the transaction server are as follows.
Example:
#include <stdio.h>
#include <fcntl.h>
#include <tiuser.h>
#define SRV_ADDR 2 /* server's well known address */
| Home |
|---|
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/tidg", O_RDWR, NULL)) < 0) {
t_error("unable to open /dev/provider");
exit(1);
}
if ((bind = (struct t_bind *)t_alloc(fd, T_BIND, T_ADDR)) = = 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) < 0) {
t_error("t_bind failed");
exit(3);
}
/*
* is the bound address correct?
*/
if (*(int *)bind_>addr.buf != SRV_ADDR) {
fprintf(stderr, "t_bind bound wrong address");
exit(4);
}
| Home |
|---|
The local management steps should look familiar by now. 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 in the same way as the users in the connection-mode example, setting the third argument to NULL. For simplicity, the transaction server assumes the transport provider has the following characteristics:
The connectionless server also binds a transport address to the endpoint, so that potential clients can identify and 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 important difference between the connection-mode server and this connectionless-mode server is that the qlen field of the t_bind structure has no meaning for connectionless-mode service. That is because all users are capable of receiving datagrams once they have bound an address. The Transport Interface defines an inherent client-server relationship between two users while establishing a transport connection in the connection-mode service. However, no such relationship exists in the connectionless-mode service. It is the context of this example, not the Transport Interface, that defines one user as a server and another as a client.
Because the address of the server is known by all potential clients, the server checks the bound address returned by t_bind to ensure it is correct.
Once a user binds an address to the transport endpoint, datagrams can be sent or received over that endpoint. Each outgoing message is accompanied by the address of the destination user. In addition, the Transport Interface enables a user to specify protocol options to be associated with the transfer of the data unit (for example, transit delay). Each transport provider defines the set of options, if any, that can accompany a datagram. When the datagram is passed to the destination user, the associated protocol options can be returned as well.
The following sequence of calls illustrates the data transfer phase of the connectionless-mode server.
Example:
if ((ud = (struct t_unitdata *)t_alloc(fd, T_UNITDATA, T_ALL)) = = NULL) {
t_error("t_alloc of t_unitdata structure failed");
exit(5);
}
if ((uderr = (struct t_uderr *)t_alloc(fd, T_UDERROR, T_ALL)) = = NULL) {
t_error("t_alloc of t_uderr structure failed");
exit(6);
}
while (1) {
if (t_rcvudata(fd, ud, &flags) < 0) {
if (t_errno = = TLOOK) {
/*
* Error on previously sent datagram
*/
if (t_rcvuderr(fd, uderr) < 0) {
exit(7);
}
fprintf(stderr, "bad datagram, error =%d",
uderr-->error);
continue;
}
t_error("t_rcvudata failed");
exit(8);
}
| Home |
|---|
/*
* Query( ) processes the request and places the
* response in ud-->udata.buf, setting ud-->udata.len
*/
query(ud);
if (t_sndudata(fd, ud, 0) < 0) {
t_error("t_sndudata failed");
exit(9);
}
}
}
query( )
{
/* Merely a stub for simplicity */
}
The server must first allocate a t_unitdata structure for storing datagrams, which has the following format:
struct t_unidata {
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 identifies protocol options associated with the transfer of the datagram, and udata holds the data itself. The addr, opt, and udata fields must all be allocated with buffers that are large enough to hold any possible incoming values. As described in the previous chapter, the T_ALL argument to t_alloc ensure this and set the maxlen field of each netbuf structure accordingly. Because the provider does not support protocol options in this example, no options buffer is allocated, and maxlen is set to zero in the netbuf structure for options. A t_uderr structure is also allocated by the server for processing any datagram errors (discussed later in this chapter).
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 retrieves the next available incoming datagram. If none is currently available, t_rcvudata blocks, waiting for a datagram to arrive. The second argument of t_rcvudata identifies the t_unitdata structure where the datagram should be stored.
The third argument, flags, must point to an integer variable and can be set to T_MORE on return from t_rcvudata to indicate that the user's udata buffer was not large enough to store the full datagram. In this case, subsequent calls to t_rcvudata retrieve the remainder of the datagram. Because t_alloc allocates a udata buffer large enough to store the maximum datagram size, the transaction server does not have to check the value of flags.
If a datagram is received successfully, the transaction server calls the query routine to process the request. This routine stores the response in the structure pointed to by ud, and sets ud>udata.len to indicate the number of bytes in the response. The source address returned by t_rcvudata in ud_>addr is used as the destination address by t_sndudata.
When the response is ready, t_sndudata is called to return the response to the client. The Transport Interface prevents a user from flooding the transport provider with datagrams using the same flow control mechanism described for the connection-mode service. In such cases, t_sndudata blocks until the flow control is relieved, and then resumes its operation.
| Home |
|---|
If the transport provider cannot process a datagram that was passed to it by t_sndudata, it returns a unit data error event, T_UDERR, to the user. This event includes the destination address and options associated with the datagram, plus a protocol-specific error value that describes what can be wrong with the datagram. The reason a datagram could not be processed is protocol-specific. One reason can be that the transport provider could not interpret the destination address or options. Each transport protocol is expected to specify all reasons for which it is unable to process a datagram.
| The unit data error indication is not necessarily intended to indicate success or failure in delivering the datagram to the specified destination. The transport protocol decides how the indication is used. Remember, the connectionless service does not guarantee reliable delivery of data. |
The transaction server is notified of this error event when it attempts 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 to 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;
long error;
}
addr and opt identify the destination address and protocol options as specified in the bad datagram, and error is a protocol-specific error code that indicates why the provider could not process the datagram. The transaction server prints the error code and then continues by entering the processing loop again.
A user can wish to establish a transport connection and then exec(2) an existing user program such as cat(1) to process data as it arrives over the connection. However, existing programs use read(2) and write(2) for their input/output. The Transport Interface does not directly support a read/write interface to a transport provider, but one is available with UNIX System V. This interface enables a user to issue read and write calls over a transport connection that is in the data transfer phase. This section describes the read/write interface to the connection-mode service of the Transport Interface. This interface is not available with the connectionless-mode service.
| Home |
|---|
The read/write interface is presented using the client example in Section 3.2 with some minor modifications. The clients are identical until the data transfer phase is reached. At that point, this client uses the read/write interface and cat(1) to process incoming data. cat can be run without changing over the transport connection. The differences between this client and that of the example in Section 3.2 are as follows.
#include <stropts.h>
.
. /*
. * Same local management and connection
. * establishment steps.
. */
.
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 client invokes the read/write interface by pushing the tirdwr(7) module onto the Stream associated with the transport endpoint where the connection was established. See I_PUSH in streamio(7). This module converts the Transport Interface above the transport provider into a pure read/write interface. With the module in place, the client calls close(2) and dup(2) to establish the transport endpoint as its standard input file, and uses /bin/cat to process the input. Because the transport endpoint identifier is a file descriptor, the facility for duping the endpoint is available to users.
Because the Transport Interface is implemented using STREAMS, the facilities of this character input/output mechanism can be used to provide enhanced user services. By pushing the tirdwr module above the transport provider, the user's interface is effectively changed. The semantics of read and write must be followed, and message boundaries are not preserved.
The tirdwr module can only be pushed onto a Stream when
the transport endpoint is in the data transfer phase. Once the
module is pushed, the user cannot call any Transport Interface
routines. If a Transport Interface routine is invoked,
tirdwr generates a fatal protocol error,
EPROTO, on that Stream, rendering it unusable. If the user pops the
tirdwr module off the Stream, the transport connection is aborted. See I_POP in
streamio(7).
|
The exact semantics of write, read, and close using tirdwr are described next. To summarize, tirdwr enables a user to send and receive data over a transport connection using read and write. This module translates all Transport Interface indications into the appropriate actions. The connection can be released with the close system call.
| Home |
|---|
The user can transmit data over the transport connection using write. The tirdwr module passes data through to the transport provider. However, if a user attempts to send a zero-length data packet, which the STREAMS mechanism allows, tirdwr discards the message. If for some reason the transport connection is aborted (for example the remote user aborts the connection using t_snddis), a STREAMS hangup condition is generated on that Stream, and further write calls fail and set errno to ENXIO. The user can still retrieve any available data after a hang-up, however.
read can be used to retrieve data that arrives over the transport connection. The tirdwr module passes data through to the user from the transport provider. However, any other event or indication passed to the user from the provider is processed by tirdwr as follows:
EPROTO, on that Stream. This error causes further system calls to fail.
Do not communicate with a process
that is sending expedited data.
EPROTO, on that Stream. This causes further system calls to fail.
If a user pushes tirdwr onto a Stream after the connection is established, such indications are not generated.
With tirdwr on a Stream, the user can send and receive data over a transport connection for the duration of that connection. Either user can terminate the connection 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:
A process cannot initiate an orderly release after tirdwr is pushed onto a Stream, but tirdwr handles an orderly release properly if it is initiated by the user on the other side of a transport connection. If the client in this section is communicating with the server program in Section 3.2, that server terminates the data transfer with an orderly release request. The server then waits for the corresponding indication from the client. At that point, the client exits and the transport endpoint is closed. As explained in the first bullet item above, when the file descriptor is closed, tirdwr initiates the orderly release request from the client's side of the connection. This generates the indication that the server is expecting, and the connection is released properly.
| Home |
|---|
This section presents some additional important concepts about TLI. First, an optional nonblocking (asynchronous) mode for some library calls is described. Then, an advanced programming example is presented that defines a server that supports multiple outstanding connect indications and operates in an event driven manner.
Many Transport Interface library routines can block waiting for an incoming event or the relaxation of flow control. However, some time-critical applications should not block for any reason. Similarly, an application can wish to do local processing while waiting for some asynchronous transport interface event.
Support for asynchronous processing of Transport Interface events is available to applications using a combination of the STREAMS asynchronous features and the nonblocking mode of the Transport Interface library routines. Earlier examples in this guide illustrated the use of the STREAMS poll system call and the I_SETSIG ioctl command for processing events in an asynchronous manner.
In addition, each Transport Interface routine that can block waiting for some event can be run in a special non-blocking mode. For example, t_listen normally blocks, waiting for a connect indication. However, a server can periodically poll a transport endpoint for existing connect indications by calling t_listen in the nonblocking (or asynchronous) mode. The asynchronous mode is enabled by setting O_NDELAY on the file descriptor. This can be set as a flag on t_open, or by calling fcntl(2) before calling the Transport Interface routine. fcntl can be used to enable or disable this mode at any time. All programming examples illustrated throughout this guide use the default, synchronous mode of processing.
O_NDELAY affects each Transport Interface routine in a different manner. To determine the exact semantics of O_NDELAY for a particular routine, see the appropriate 3N manual pages in the SUPER-UX Programmer's Reference Manual.
The following example demonstrates two important concepts. The first is a server's ability to manage multiple outstanding connect indications. The second is an illustration of the ability to write event-driven software using the Transport Interface and the STREAMS system call interface.
The server example in Section 3.2 supported only one outstanding connect indication, but the Transport Interface supports the ability to manage multiple outstanding connect indications. One reason a server might wish to receive several, simultaneous connect indications is to impose a priority scheme on each client. A server can retrieve several connect indications and then accept them in an order based on a priority associated with each client. A second reason for handling several outstanding connect indications is that the single-threaded scheme has some limitations. Depending on the implementation of the transport provider, it is possible that while the server is processing the current connect indication, other clients will find it busy.
| Home |
|---|
The server example is event-driven: the process polls a transport end-point for incoming Transport Interface events, and then takes the appropriate actions for the current event. The example demonstrates the ability to poll multiple transport endpoints for incoming events.
The definitions and local management functions needed by this example are similar to those of the server example in Section 3.2.
Example:
#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 */
struct t_call *calls[NUM_FDS][MAX_CONN_IND];/* holds connect indications */
extern int t_errno;
main( )
{
struct pollfd pollfds[NUM_FDS];
struct t_bind *bind;
int i;
/*
* Only opening and binding one transport endpoint,
* but more could be supported
*/
if ((pollfds[0].fd = t_open("/dev/tivc", O_RDWR, NULL)) < 0) {
t_error("t_open failed");
exit(1);
}
if ((bind = (struct t_bind *)t_alloc(pollfds[0].fd, T_BIND, T_ALL) = = NULL) {
t_error("t_alloc of t_bind structure failed");
exit(2);
}
bind-->qlen = MAX_CONN_IND;
bind-->addr.len = sizeof(int);
*(int *)bind-->addr.buf = SRV_ADDR;
if (t_bind(pollfds[0].fd, bind, bind) < 0) {
t_error("t_bind failed");
exit(3);
}
/*
* Was the correct address bound?
*/
if (*(int *)bind-->addr.buf != SRV_ADDR) {
fprintf(stderr, "t_bind bound wrong address");
exit(4);
}
| Home |
|---|
The file descriptor returned by t_open is stored in a pollfd structure that is used to poll the transport endpoint for incoming data. See poll(2) for more information. In this example, only one transport endpoint is established. However, the remainder of the example is written to manage multiple transport endpoints. Several endpoints could be supported with minor changes to the preceding code.
An important aspect of this server is that it sets qlen to a value greater than 1 for t_bind. This indicates that the server is willing to handle multiple outstanding connect indications. Remember that the earlier examples single-threaded the connect indications and responses. The server would accept the current connect indication before retrieving additional connect indications. This example, however, can retrieve up to MAX_CONN_IND connect indications at one time before responding to any of them. The transport provider can negotiate the value of qlen downward if it cannot support MAX_CONN_IND outstanding connect indications.
Once the server binds its address and is ready to process incoming connect requests, it does the following:
pollfds[0].events = POLLIN;
while (1) {
if (poll(pollfds, NUMFDS, -1) < 0) {
perror("poll failed");
exit(5);
}
for (i = 0; i < NUM_FDS; i++) {
switch (pollfds[i].revents) {
default:
perror("poll returned error event");
exit(6);
case 0:
continue;
case POLLIN:
do_event(i, pollfds[i].fd);
service_conn_ind(i, pollfds[i].fd);
}
}
}
}
The events field of the pollfd structure is set to POLLIN, which notifies the server of any incoming Transport Interface events. The server then enters an infinite loop, in which it polls the transport endpoint(s) for events and then process those events as they occur.
| Home |
|---|
The poll call blocks indefinitely, waiting for an incoming event. On return, each entry (corresponding to each transport endpoint) is checked for an existing event. If revents is set to 0, no event occurred on that endpoint. In this case, the server continues to the next transport endpoint. If revents is set to POLLIN, an event does exist on the endpoint. In this case, do_event is called to process the event. If revents contains any other value, an error must have occurred on the transport endpoint and the server exits.
For each iteration of the loop, if any event is found on the transport endpoint, service_conn_ind is called to process any outstanding connect indications. However, if another connect indication is pending, service_conn_ind saves the current connect indication and respond to it later. This routine is explained a little later.
If an incoming event is discovered, the following routine is called to process it:
do_event(slot, fd)
{
struct t_discon *discon;
int i;
switch (t_look(fd)) {
default:
fprintf(stderr,"t_look returned an unexpected event");
exit(7);
case T_ERROR:
fprintf(stderr,"t_look returned T_ERROR even");
exit(8);
case -1:
t_error("t_look failed");
exit(9);
case 0:
/* since POLLIN returned, this should not happen */
fprintf(stderr,"t_look returned no event");
exit(10);
case T_LISTEN:
/*
* find free element in calls array
*/
for (i = 0; i < MAX_CONN_IND; i++) {
if (calls[slot][i] = = NULL)
break;
}
if ((calls[slot][i] = (struct tcall *)t_alloc(fd, T_CALL, T_ALL)) = = NULL) {
t_error("t_alloc of t_call structure failed");
exit(11);
}
if (t_listen(fd, calls[slot][i]) < 0) {
t_error("t_listen failed");
exit(12);
}
break;
| Home |
|---|
case TDISCONNECT:
discon = (struct t_discon *)t_alloc(fd, T_DIS, T_ALL);
if (t_rcvdis(fd, discon) < 0) {
t_error("t_rcvdis failed");
exit(13);
}
/*
* 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] = NULL;
}
}
t_free(discon, T_DIS);
break;
}
}
This routine takes a number, slot, and a file descriptor, fd, as arguments. slot is used as an index into the global array calls. This array contains an entry for each polled transport endpoint, where each entry consists of an array of t_call structures that hold incoming connect indications for that transport endpoint. The value of slot is used to identify the transport endpoint of interest.
do_event calls t_look to determine the Transport Interface event that occurred on the transport endpoint referenced by fd. If a connect indication (T_LISTEN event) or disconnect indication (T_DISCONNECT event) arrives, the event is processed. Otherwise, the server prints an appropriate error message and exits.
For connect indications, do_event scans the array of outstanding connect indications looking for the first free entry. A t_call structure is then allocated for that entry, and the connect indication is retrieved using t_listen. There must always be at least one free entry in the connect indication array, because the array is large enough to hold the maximum number of outstanding connect indications as negotiated by t_bind. The processing of the connect indication is deferred until later.
If a disconnect indication arrives, it must correspond to a previously received connect indication. This scenario arises if a client attempts to undo a previous connect request. In this case, do_event allocates a t_discon structure to retrieve the relevant disconnect information. This structure has the following members:
struct t_discon {
struct netbuf udata;
int reason;
int sequence;
}
udata identifies any user data that might have been sent with the disconnect indication, reason contains a protocol-specific disconnect reason code, and sequence identifies the outstanding connect indication that matches this disconnect indication.
Next, t_rcvdis is called to retrieve the disconnect indication. The array of connect indications for slot is then scanned for one that contains a sequence number that matches the sequence number in the disconnect indication. When the connect indication is found, it is freed and the corresponding entry is set to NULL.
| Home |
|---|
If any event is found on a transport endpoint, service_conn_ind is called to process all currently outstanding connect indications associated with that endpoint as follows:
service_conn_ind(slot, fd)
{
int i;
for (i = 0; i < MAX_CONN_IND; i++) {
if (calls[slot][i] = = NULL)
continue;
if ((conn_fd = t_open("/dev/tivc", O_RDWR, NULL)) < 0) {
t_error("open failed");
exit(14);
}
if (t_bind(conn_fd, NULL, NULL) < 0) {
t_error("t_bind failed");
exit(15);
}
if (t_accept(fd, conn_fd, calls[slot][i]) < 0) {
if (t_errno = = TLOOK) {
t_close(conn_fd);
return;
}
t_error("t_accept failed");
exit(16);
}
t_free(calls[slot][i], T_CALL);
calls[slot][i] = NULL;
run_server(fd);
}
}
For the given slot (the transport endpoint), the array of outstanding connect indications is scanned. For each indication, the server opens a responding transport endpoint, binds an address to the endpoint, and then accepts the connection on that endpoint. If another event (connect indication or disconnect indication) arrives before the current indication is accepted, t_accept fails and sets t_errno to TLOOK.
| The user cannot accept an outstanding connect indication if any pending connect indication events or disconnect indication events exist on that transport endpoint. |
If this error occurs, the responding transport endpoint is closed and service_conn_ind returns immediately (saving the current connect indication 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. In this way, multiple connect indications can be queued by the user.
| Home |
|---|
Eventually, all events are processed, and service_conn_ind is able to accept each connect indication in turn. Once the connection is established, the run_server routine used by the server in Section 3.2 is called to manage the data transfer.
The tables in this section describe all state transitions associated with the Transport Interface. First, however, the states and events are described.
Table 3-6 defines the states used to describe the Transport Interface state transitions.
| State | Description | Service Type |
|---|---|---|
| T_UNINIT | uninitialized -- initial
and final state of interface | T_COTS, T_COTS_ORD, T_CLTS |
| T_UNBND | initialized but not bound | T_COTS, T_COTS_ORD, T_CLTS |
| T_IDLE | no connection established | T_COTS, T_COTS_ORD, T_CLTS |
| T_OUTCON | outgoing connection pending for client | T_COTS, T_COTS_ORD |
| T_INCON | incoming connection pending for server | T_COTS, T_COTS_ORD |
| T_DATAXFER | data transfer | T_COTS, T_COTS_ORD |
| T_OUTREL | outgoing orderly release (waiting for orderly release indication) | T_COTS_ORD |
| T_INREL | incoming orderly release (waiting to send orderly release request) | T_COTS_ORD |
The outgoing events described in Table 3-7 correspond to the return of the specified transport routines, where these routines send a request or response to the transport provider.
In the table, some events (such as acceptN) are distinguished by the context in which they occur. The context is based on the values of the following variables:
| Home |
|---|
| Event | Description | Service Type |
|---|---|---|
| opened | successful return of t_open | T_COTS, T_COTS_ORD, T_CLTS |
| bind | successful return of t_bind | T_COTS, T_COTS_ORD, T_CLTS |
| optmgmt | successful return of t_optmgmt | T_COTS, T_COTS_ORD, T_CLTS |
| unbind | successful return of t_unbind | T_COTS, T_COTS_ORD, T_CLTS |
| closed | successful return of t_close | T_COTS, T_COTS_ORD, T_CLTS |
| connect1 | successful return of t_connect in synchronous mode | T_COTS, T_COTS_ORD |
| connect2 | TNODATA error on t_connect in asynchronous mode, or TLOOK error due to a dis-connect indication arriving on the transport endpoint | T_COTS, T_COTS_ORD |
| accept1 | successful return of t_accept with ocnt == 1, fd == resfd | T_COTS, T_COTS_ORD |
| accept2 | successful return of t_accept with ocnt == 1, fd ! = resfd | T_COTS, T_COTS_ORD |
| accept3 | successful return of t_accept with ocnt >1 | T_COTS, T_COTS_ORD |
| snd | successful return of t_snd | T_COTS, T_COTS_ORD |
| snddis1 | successful return of t_snddis with ocnt < =1 | T_COTS, T_COTS_ORD |
| snddis2 | successful return of t_snddis with ocnt > 1 | T_COTS, T_COTS_ORD |
| sndrel | successful return of t_sndrel | T_COTS_ORD |
| sndudata | successful return of t_sndudata | T_CLTS |
The incoming events correspond to the successful return of the specified routines, where these routines retrieve data or event information from the transport provider. The only incoming event not associated directly with the return of a routine is pass_conn, which occurs when a user transfers a connection to another transport endpoint. This event occurs on the endpoint that is being passed the connection, despite the fact that no Transport Interface routine is issued on that endpoint. pass_conn is included in the state tables to describe the behavior when a user accepts a connection on another transport endpoint.
In Table 3-8, the rcvdis events are distinguished by the context in which they occur. The context is based on the value of ocnt, which is the count of outstanding connect indications on the transport endpoint.
| Home |
|---|
| Incoming Event | Description | Service Type |
|---|---|---|
| listen | successful return of t_listen | T_COTS, T_COTS_ORD |
| rcvconnect | successful return of t_rcvconnect | T_COTS, T_COTS_ORD |
| rcv | successful return of t_rcv | T_COTS, T_COTS_ORD |
| rcvdis1 | successful return of t_rcvdis with ocnt <=0 | T_COTS, T_COTS_ORD |
| rcvdis2 | successful return of t_rcvdis with ocnt = = 1 | T_COTS, T_COTS_ORD |
| rcvdis3 | successful return of t_rcvdis with ocnt > 1 | T_COTS, T_COTS_ORD |
| rcvrel | successful return of t_rcvrel | T_COTS_ORD |
| rcvudata | successful return of t_rcvudata | T_CLTS |
| rcvuderr | successful return of t_rcvuderr | T_CLTS |
| pass_conn | receive a passed connection | T_COTS, T_COTS_ORD |
In the state tables that follow, some state transitions are accompanied by a list of actions the transport user must take. These actions are represented by the notation [n], where n is the number of the specific action as follows.
The following tables describe the Transport Interface state transitions. Given a current state and an event, the transition to the next state is shown, as well as any actions that must be taken by the transport user (indicated by [n]). The state is that of the transport provider as seen by the transport user.
The contents of each entry represent the next state, given the current state (column) and the current incoming or outgoing event (row). An empty entry represents a state/event combination that is invalid. Along with the next state, each entry can include an action list (as specified in the previous section). The transport user must take the specific actions in the order specified in the state table.
| Home |
|---|
The following should be understood when studying the state tables:
A separate table is shown for common local management steps, data transfer in connectionless-mode, and connection-establishment/connection-release/data-transfer in connection-mode.
| state
event | T_UNINIT | T_UNBND | T_IDLE |
|---|---|---|---|
| opened | T_UNBND | ||
| bind | T_IDLE [1] | ||
| optmgmt | T_IDLE | ||
| unbind | T_UNBND | ||
| closed | T_UNINIT |
| state
event | T_IDLE |
|---|---|
| sndudata | T_IDLE |
| rcvudata | T_IDLE |
| rcvuderr | T_IDLE |
| Home |
|---|
| state event | T_IDLE | T-OUTCON | T_INCON | T_DATAXFER | T_OUTREL | T_INREL |
|---|---|---|---|---|---|---|
| connect1 | T_DATAXFER | |||||
| connect2 | T_OUTCON | |||||
| rcvconnect | T_DATAXFER | |||||
| listen | T_INCON [2] | T_INCON [2] | <||||
| accept1 | T_DATAXFER [3] | |||||
| accept2 | T_IDLE [3][4] | |||||
| accept3 | T_INCON [3][4] | |||||
| snd | T_DATAXFER | T_INREL | ||||
| rcv | T_DATAXFER | T_OUTREL | ||||
| snddis1 | T_IDLE | T_IDLE [3] | T_IDLE | T_IDLE | T_IDLE | |
| snddis2 | T_INCON [3] | |||||
| rcvdis1 | T_IDLE | T_IDLE | T_IDLE | T_IDLE | ||
| rcvdis2 | T_IDLE [3] | |||||
| rcvdis3 | T_INCON [3] | |||||
| sndrel | T_OUTREL | T_IDLE | ||||
| rcvrel | T_INREL | T_IDLE | ||||
| pass_conn | T_DATAXFER |
By defining a set of services common to many transport protocols, the Transport Interface offers protocol independence for user software. However, all transport protocols do not support all the services supported by the Transport Interface. If software must be run in a variety of protocol environments, only the common services should be accessed. The following guidelines highlight services that can not be common to all transport protocols.
| Home |
|---|
The examples presented throughout this chapter are shown in their entirety in this section.
The following code represents the connection-mode client program described in Section 3.2. This client establishes a transport connection with a server, and then receives data from the server and writes it to its standard output. The connection is released using the orderly release facility of the Transport Interface. This client communicates with each of the connection-mode servers presented in the guide.
#include <stdio.h>
#include <tiuser.h>
#include <fcntl.h>
#define SRV_ADDR 1 /* server's well known address */
main( )
{
int fd;
in