Your Browser is not longer supported

Please use Google Chrome, Mozilla Firefox or Microsoft Edge to view the page correctly
Loading...

{{viewport.spaceProperty.prod}}

Managing multiple connections simultaneously and event-controlled operation

&pagelevel(3)&pagelevel

An example is used in the following section to illustrate two important concepts:

  • Simultaneous management of multiple connections by the server:

    The server application shown in section "XTI(POSIX) basics" can only process one connection request at a time. However, the transport interface also allows several connections to be processed simultaneously. This is, for example, meaningful in the following cases:

    • The server wishes to assign a priority to each client.

    • Several clients wish to set up a connection to a server which is currently processing a connection request. If the server can only process one connection request at any one time, the clients find the server in an occupied state. However, if the server can process several connections simultaneously, the clients will only find the server in an occupied state if the server is already processing the maximum possible number of clients requests.

  • Programming event-controlled operation:

    The programmer can write event-controlled programs using the transport interface. With an event-controlled server, the process continuously polls a transport endpoint to check if events have been reported by the transport interface. The server then calls the interface function appropriate to the reported event.

The following example program uses the same definitions and calls for the local management as the example server in section "Connection-oriented service". The program code used in the example is shown completely and coherently in section "Event-controlled server”".

#include <xti.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <stropts.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define FILENAME "/etc/services"
#define NUM_FDS 1
#define MAX_CONN_IND 4
#define SRV_ADDR 0x7F000001
#define SRV_PORT 8888
int conn_fd;/* Server transport endpoint */
/* For storing the connections */
struct t_call *calls[NUM_FDS][MAX_CONN_IND];
main()
{
   struct pollfd pollfds[NUM_FDS];
   struct t_bind *bind;
   struct sockaddr_in *sin;
   int i;
   /*
    * Open a transport endpoint and bind the address. 
    * However, multiple endpoints are supported.
    */
   if ((pollfds[0].fd = t_open("/dev/tcp", 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(struct sockaddr_in);
   sin=(struct sockaddr_in *)bind->addr.buf;
   sin->sin_family=AF_INET;
   sin->sin_port=htons(SRV_PORT);
   sin->sin_addr.s_addr=htonl(SRV_ADDR);
   if (t_bind(pollfds[0].fd, bind, bind) < 0) {
      t_error("t_bind() failed");
      exit(3);
   }

The file ID returned by t_open() is stored in the first member of the struct pollfd vector pollfds. The pollfds vector is used later when calling the POSIX poll() function to process incoming events. poll() is a general C library function which is described in section "poll() - multiplex input/output". It must be noted that just one transport endpoint is set up in this example. However, since the remaining part of the example is laid out for multiple connections, only minor changes have to be made to manage multiple communications connections with this program.

An important point for this example is that the server sets bind->qlen to a value >1 and passes it with the t_bind() call. This makes it possible for the server to receive multiple connection requests on one transport endpoint. In the examples in section "XTI(POSIX) basics", the server always accepts and processes just one connection at a time. In contrast to this, the server can accept up to MAX_CONN_IND requests simultaneously in this example. However, the transport provider may reduce the value of bind->qlen if he cannot process the number of connections required by the server.

The server proceeds as follows after making its address known:

   pollfds[0].events = POLLIN;
   for(;;) {
      if (poll(pollfds, NUM_FDS, -1) < 0) {
         perror("poll() failed");
         exit(5);
      }
      for (i = 0; i < NUM_FDS; i++) {
         switch (pollfds[i].revents) {
         default:
                 perror("Poll returns an error message");
                 exit(6);
                 break;
         case 0:
                 continue;
         case POLLIN:
                 do_event(i, pollfds[i].fd);
                 service_conn_ind(i, pollfds[i].fd);
         }
      }
   }
}

The events component of the first member of the pollfd vector pollfds sets the server to POLLIN so that it is informed about all events arriving at the transport interface. The server then goes into an endless loop, waits for events at the transport endpoints with poll() and processes these events accordingly.

The poll() call blocks the process until an event arrives. After the end of the call, the server checks each entry (corresponding to one transport endpoint) to see if an event has occurred there. If revents is set to 0, no events arrived at this endpoint. If revents is set to POLLIN, an event has arrived at this endpoint. In this case, the server calls do_event() to process the event. If revents has a value other than POLLIN, an error has occurred at this endpoint and the program is terminated.

In each loop run in which an event is found, the server calls service_conn_ind() to process any pending requests. If a further request is pending while one is being processed, service_conn_ind is exited immediately, whereby the current request is stored for later processing with the do_event() function.

The do_event() function is called to process an incoming event and is defined as follows:

do_event(slot, fd)
{
   struct t_discon *discon;
   int i;
   switch (t_look(fd)) {
   default:
      fprintf(stderr,"t_look: unexpected event \n");
      exit(7);
      break;
   case -1:
      t_error("t_look() failed");
      exit(9);
      break;
   case 0:
      /* This should never happen if POLLIN is reported */
      fprintf(stderr,"t_look() reports no event\n");
      exit(10);
   case T_LISTEN:
      /*
       * Search for a free member in the calls field
       */
      for (i = 0; i < MAX_CONN_IND; i++) {
         if (calls[slot][i] == NULL)
                 break;
      }
      if ((calls[slot][i] = (struct t_call *)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;
   case T_DISCONNECT:
      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 and delete request in calls field
       */
      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;
   }
}

The do_event() function has two parameters: a number slot and a file ID fd. slot indexes the vectors (submatrixes) of the global calls matrix whose members are pointers to t_call objects. Each transport endpoint to be interrogated is represented by a vector in the calls matrix. The value of slot therefore specifies the transport endpoint to be processed. The vector members point to the t_call objects in which the incoming requests of the transport endpoint concerned are stored.

The t_look() call gets the event which occurred on the transport endpoint identified by fd. If a connection request (T_LISTEN) or an abort request (T_DISCONNECT) has arrived, it is processed accordingly. With other events, an appropriate error message is output and the program is terminated.

With a connection request, do_event() searches for a free entry in the calls field. A t_call object is now requested for this entry. The request is received with t_listen(). This field must always contain at least one free field as t_bind() specified the maximum number of requests than could be processed simultaneously when it created the field. The request is processed later.

An incoming abort request must belong to a connection request that arrived earlier. This is true if a client sends a connection request and then aborts it immediately. do_event() creates a t_discon structure to receive the information for the abort.

The t_discon structure is declared in <xti.h> as follows:

struct t_discon {
   struct netbuf udata;
   int reason;
   int sequence;
};

reason specifies the protocol-specific reason for the connection shutdown. sequence specifies the number of the connection request that is to be aborted.

The t_rcvdis() function is called to receive the above information. *calls of the program in which the requests are managed is then searched for the request specified in the sequence component. Once the request is found, the memory is released and the entry set to NULL.

If any event has occurred at the transport endpoint, the service_conn_ind() function is called to process all requests pending on this 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/tcp", O_RDWR, NULL)) < 0) {
         t_error("t_open() failed");
         exit(14);
      }
      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_service(fd);
   }
}

The field is searched for requests for the specified endpoint (slot). For each request, the server opens a transport endpoint and accepts the request. If, in the meantime, another event (connection request or connection shutdown) has arrived, the t_accept() call fails and t_errno is set to TLOOK.

A user cannot accept any requests if other connection or abort requests are pending on this transport endpoint.

When this error occurs, conn_fd is closed immediately and the function is exited. The request remains intact in the field and can therefore be processed at a later time. The server process is now back in the main loop and the event can be handled with the next poll() call. This method allows multiple requests to be processed simultaneously.

Once all events have been processed, the service_conn_ind() function can set up the connections and call the run_service() function to transfer the data. The run_service() function is described in section "Connection-oriented client/server model".