Advanced XTI/TLI Programming Example
Example 8-2 demonstrates two important concepts. The first is a server's ability to manage multiple outstanding connect requests. The second is event-driven use of XTI/TLI and the system call interface.
By using XTI/TLI, a server can manage multiple outstanding connect requests. One reason to receive several simultaneous connect requests is to prioritize the clients. A server can receive several connect requests, and accept them in an order based on the priority of each client.
The second reason for handling several outstanding connect requests is to overcome the limits of single-threaded processing. Depending on the transport provider, while a server is processing one connect request, other clients see the server as busy. If multiple connect requests are processed simultaneously, the server is busy only if more than the maximum number of clients try to call the server simultaneously.
The server example is event-driven: the process polls a transport endpoint for incoming XTI/TLI events and takes the appropriate actions for the event received. The example following demonstrates the ability to poll multiple transport endpoints for incoming events.
Example 8-2 Establishing an Endpoint (Convertible to Multiple Connections)
#include <tiuser.h> #include <fcntl.h> #include <stdio.h> #include <poll.h> #include <stropts.h> #include <signal.h> #define NUM_FDS 1 #define MAX_CONN_IND 4 #define SRV_ADDR 1 /* server's well known address */ int conn_fd; /* server connection here */ extern int t_errno; /* holds connect requests */ struct t_call *calls[NUM_FDS][MAX_CONN_IND]; main() { struct pollfd pollfds[NUM_FDS]; struct t_bind *bind; int i; /* * Only opening and binding one transport endpoint, but more can * be supported */ if ((pollfds[0].fd = t_open("/dev/tivc", O_RDWR, (struct t_info *) NULL)) == -1) { t_error("t_open failed"); exit(1); } if ((bind = (struct t_bind *) t_alloc(pollfds[0].fd, T_BIND, T_ALL)) == (struct t_bind *) NULL) { t_error("t_alloc of t_bind structure failed"); 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) == -1) { t_error("t_bind failed"); exit(3); } /* Was the correct address bound? */ if (bind->addr.len != sizeof(int) || *(int *)bind->addr.buf != SRV_ADDR) { fprintf(stderr, "t_bind bound wrong address\n"); exit(4); } }
The file descriptor returned by t_open
()
is stored in a pollfd
structure that controls
polling of the transport endpoints for incoming data. For more
information, see the
poll
(2) and
t_open
(3C) man pages. Only one transport endpoint is
established in this example. However, the remainder of the example is
written to manage multiple transport endpoints. Several endpoints could
be supported with minor changes to Example 8-2.
This server sets qlen
to a value greater than
1
for t_bind
(). This value specifies that the
server should queue multiple outstanding connect requests. The server accepts the
current connect request before accepting additional connect requests. This example can
queue up to MAX_CONN_IND
connect requests. The transport provider can
negotiate the value of qlen
to be smaller if the provider cannot
support MAX_CONN_IND
outstanding connect requests.
After the server binds its address and is ready to process connect requests, it behaves as shown in the following example.
Example 8-3 Processing Connection Requests
pollfds[0].events = POLLIN; while (TRUE) { if (poll(pollfds, NUM_FDS, -1) == -1) { 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); } } }
In the pollfd
structure, the
events
field is set to POLLIN
,
which notifies the server of any incoming XTI/TLI events. The server
then enters an infinite loop in which it polls the transport endpoints
for events and processes the events as they occur.
The poll
()
call blocks indefinitely for an incoming event. On return, the
server checks the value of revents
for each entry, one per transport
endpoint, for new events. If revents
is 0
, the
endpoint has generated no events and the server continues to the next endpoint. If
revents
is POLLIN
, there is an event on the
endpoint. The server calls do_event
to process the event. Any other
value in revents
indicates an error on the endpoint, and the server
exits. With multiple endpoints, the server should close this descriptor and continue.
Each time the server iterates the loop, it calls service_conn_ind
to
process any outstanding connect requests. If another connect request is pending, service_conn_ind
saves the new connect request and responds to it
later.
The server calls do_event
in the following example
to process an incoming event.
Example 8-4 Creating an Event Processing Routine
do_event( slot, fd) int slot; int fd; { struct t_discon *discon; int i; switch (t_look(fd)) { default: fprintf(stderr, "t_look: unexpected event\n"); exit(7); case T_ERROR: fprintf(stderr, "t_look returned T_ERROR event\n"); 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\n"); exit(10); case T_LISTEN: /* find free element in calls array */ for (i = 0; i < MAX_CONN_IND; i++) { if (calls[slot][i] == (struct t_call *) NULL) break; } if ((calls[slot][i] = (struct t_call *) t_alloc( fd, T_CALL, T_ALL)) == (struct t_call *) NULL) { t_error("t_alloc of t_call structure failed"); exit(11); } if (t_listen(fd, calls[slot][i] ) == -1) { t_error("t_listen failed"); exit(12); } break; case T_DISCONNECT: discon = (struct t_discon *) t_alloc(fd, T_DIS, T_ALL); if (discon == (struct t_discon *) NULL) { t_error("t_alloc of t_discon structure failed"); exit(13) } if(t_rcvdis( fd, discon) == -1) { t_error("t_rcvdis failed"); exit(14); } /* find call ind in array and delete it */ for (i = 0; i < MAX_CONN_IND; i++) { if (discon->sequence == calls[slot][i]->sequence) { t_free(calls[slot][i], T_CALL); calls[slot][i] = (struct t_call *) NULL; } } t_free(discon, T_DIS); break; } }
The arguments in Example 8-4
are a number (slot) and a file descriptor
(fd). A slot is the index into
the global array calls
, which has an entry for each transport
endpoint. Each entry is an array of t_call
structures that hold
incoming connect requests for the endpoint.
The do_event
module calls t_look
()
to identify the XTI/TLI
event on the endpoint specified by fd. If the event is a
connect request (T_LISTEN
event) or disconnect request
(T_DISCONNECT
event), the event is processed. Otherwise, the
server prints an error message and exits. For more information, see the
t_look
(3C) man page.
For connect requests, do_event
scans the array of outstanding connect
requests for the first free entry. A t_call
structure is allocated
for the entry, and the connect request is received by t_listen
(). The
array is large enough to hold the maximum number of outstanding connect requests. The
processing of the connect request is deferred. For more information, see the
t_listen
(3C) man page.
A disconnect request must correspond to an earlier connect request.
The do_event
module allocates a t_discon
structure
to receive the request. This structure has the following fields:
struct t_discon { struct netbuf udata; int reason; int sequence; }
The udata
structure contains any user data sent with
the disconnect request. The value of reason
contains a
protocol-specific disconnect reason code. The value of sequence
identifies
the connect request that matches the disconnect request.
The server calls t_rcvdis
()
to receive the disconnect request. The array of
connect requests is scanned for one that contains the sequence number that matches the
sequence
number in the disconnect request. When the connect
request is found, its structure is freed and the entry is set to
NULL
. For more information, see the
t_rcvdis
(3C) man page.
When an event is found on a transport endpoint, service_conn_ind
is
called to process all queued connect requests on the endpoint, as the following
example shows.
Example 8-5 Processing All Connect Requests
service_conn_ind(slot, fd) { int i; for (i = 0; i < MAX_CONN_IND; i++) { if (calls[slot][i] == (struct t_call *) NULL) continue; if((conn_fd = t_open( "/dev/tivc", O_RDWR, (struct t_info *) NULL)) == -1) { t_error("open failed"); exit(15); } if (t_bind(conn_fd, (struct t_bind *) NULL, (struct t_bind *) NULL) == -1) { t_error("t_bind failed"); exit(16); } if (t_accept(fd, conn_fd, calls[slot][i]) == -1) { if (t_errno == TLOOK) { t_close(conn_fd); return; } t_error("t_accept failed"); exit(167); } t_free(calls[slot][i], T_CALL); calls[slot][i] = (struct t_call *) NULL; run_server(fd); } }
For each transport endpoint, the array of outstanding connect requests is scanned. For each
request, the server opens a responding transport endpoint, binds an address to the
endpoint, and accepts the connection on the endpoint. If another connect or disconnect
request arrives before the current request is accepted, t_accept
()
fails and sets t_errno
to TLOOK
. You cannot accept
an outstanding connect request if any pending connect request events or disconnect
request events exist on the transport endpoint.
If this error occurs, the responding transport endpoint is closed and
service_conn_ind
returns immediately, saving the current connect
request for later processing. This activity causes the server's main processing loop to
be entered, and the new event is discovered by the next call to poll
().
In this way, the user can queue multiple connect requests.
Eventually, all events are processed, and service_conn_ind
is
able to accept each connect request in turn.