DOC HOME SITE MAP MAN PAGES GNU INFO SEARCH PRINT BOOK
 
Programming with the X/Open Transport Interface (XTI)

Data transfer

Once the connection has been established, both the client and server may begin transferring data over the connection using t_snd and t_rcv. The Transport Interface does not differentiate the client from the server from this point on. Either user may send and receive data, or release the connection. The Transport Interface guarantees reliable, sequenced delivery of data over an existing connection.

Two classes of data may be transferred over a transport connection:

Expedited data is typically associated with urgent information. The exact semantics of expedited data is subject to the interpretations of the transport provider. Furthermore, not all transport protocols support the notion of an expedited data class (see t_open(S)).

All transport protocols support the transfer of data in byte stream mode, where ``byte stream'' implies no concept of message boundaries on data that are transferred over a connection. However, 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), may 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 allows a user to transmit or receive 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 specifies that the user will 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 specify that this is the end of the TSDU.

Similarly, a TSDU may be passed in multiple units to the receiving user. 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 will be identified by a call to t_rcv that does not set T_MORE.


CAUTION: The T_MORE flag implies nothing about how the data may be packaged below the Transport Interface or how the data may be delivered to the remote user. Each transport protocol, and each implementation of that protocol, may 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 may be delivered in a single unit to the remote transport user. The message boundaries may only be preserved by noting the value of the T_MORE flag on t_snd and t_rcv. This will guarantee that the receiving user will see a message with the same contents and message boundaries as was sent by the remote user.


The client

Continuing with the client/server example, the server will transfer 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\n");
   		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 received. The client then writes this data to standard output and continues. The data transfer phase will complete when t_rcv fails. t_rcv will fail if an orderly release or disconnect indication arrives, as discussed later in this section. If the fwrite() call (see fread(S) fails for any reason, the client will exit, closing the transport endpoint. If the transport endpoint is closed (either by exit or t_close) during the data transfer phase, the connection will be aborted and the remote user will receive a disconnect indication.

The server

Looking now 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:

   void
   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 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 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) < 0) { t_error("t_snd failed"); exit(26); }

After the fork, the parent process returns to the main processing loop and listens for further connect indications. Meanwhile, the child process will manage the newly established transport connection. If the fork call fails, exit closes the transport endpoint associated with listen_fd, sending a disconnect indication to the client, and the client's t_connect call will fail.

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 can contain 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, the provider may exert back pressure to provide flow control. In such cases, t_snd will block until the flow control is relieved, and will then resume its operation. t_snd will not complete until nbyte bytes have been passed to the transport provider.

The t_snd routine does not look for a disconnect indication (showing that the connection was broken) before passing data to the provider. Also, because the data traffic flows in one direction, the user will never look for incoming events. If the connection is aborted, the user should be notified since data may be lost. The user can invoke t_look, which checks for incoming events before each t_snd call. A more efficient solution is presented in the example. The STREAMS I_SETSIG ioctl enables a user to request a signal when a given event occurs (see streamio(M) and signal(S)). S_INPUT causes a signal to be sent 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 error message and then exits.


NOTE: If your application is multithreaded, see the discussions in ``Multithreaded network programming'' and ``Threads and signals'' for ideas about how to deal with the SIGPOLL signal generated by the arrival of the disconnect indication.

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.


© 2005 The SCO Group, Inc. All rights reserved.
SCO OpenServer Release 6.0.0 -- 02 June 2005