THE TRANSPORT LAYER INTERFACE
 
 
     + Interface into the transport provider
 
          - Level 4 of the ISO model
 
          - TCP and UDP of the TCP/IP model
 
          - Based on the OSI Transport Service Specification (ISO
            8072)
 
     + Common interface, regardless of transport provider
 
 
                                 MOVING TO TLI
 
 
     + TLI is endorsed by X/Open (XTI)
 
     + OSF/1 has TLI
 
     + Quote from SunOS Manual:
 
       WARNING:  Socket-based interprocess communication (IPC), while
       still supported, is no longer the preferred framework for
       transport-level programming.  Socket-based IPC has been
       superseded as the ``standard'' method of accessing network
       protocols by a set of OSI-compatible transport mechanisms
       based upon STREAMS and accessed by way of a Transport Layer
       Interface (TLI).
 
 
                                     TLI
                           CONNECTION MODE SERVICE
 
                               GENERAL CONCEPTS

     + The client machine requests a service from the server machine

     + Server machine actions:

          - Open the network device

          - ``Bind'' to a transport address

               + Using TCP/IP, this is a combination of host address
                 and port number

               + Check if bound to the correct one!

          - Listen for connections

          - Accept a connection when one arrives

          - Communicate with the client

          - Release the connection


			GENERAL CONCEPTS

     + Client machine actions:

          - Open the network device

          - Create a ``transport endpoint''

          - Call the server process (giving transport address of
            service)

          - Communicate with the server process

          - Release the connection

               + Abrupt or orderly


                                AN APPLICATION

     + Let's create an application:

          - The server will read a user name, determine if the user
            is on the system, and write a message to the client

          - The client will connect to the server, send a user name,
            and read whether that user is on the system


                                  SERVER CODE


       Routines used by the server setup:

          + t_open( )

               - Opens the transport provider

               - Enables the application to access the network

          + t_alloc( )

               - Allocates space to hold data structures

          + t_bind( )

               - Allows the server to attach itself to its address

          + t_listen( )

               - Listen for incoming connection requests


          + t_accept( )
     
               - Accept an incoming connection
     
          + t_snd( )
     
               - Sends data to the client
     
          + t_rcv( )
     
               - Receives data from the client
     


                                  CLIENT CODE

       Routines used by the client setup:

          + t_open( )

               - Opens the transport provider

          + t_bind( )

               - Binds to an arbitrary return port

          + t_connect( )

               - Connects to the server address

          + t_snd( )

               - Sends messages to the server

          + t_rcv( )

               - Receives data from the server


                            TRANSPORT INDEPENDENCE

     + TLI uses the t_open() routine to gain initial access to a
       transport provider

     + The t_open() routine takes a device name as a parameter

          - Use /dev/tcp to run over TCP

          - Use /dev/ticots to run over the SVR4 Loopback Transport
            Provider

          - Use /dev/ticotsord to run over the SVR4 Loopback
            Transport Provider that supports orderly release

     + This can be passed to your program as an argument



                            TRANSPORT INDEPENDENCE


     + Each transport provider usrs addresses specific to that
       transport provider

          - In TCP/IP, and address is 16 bytes, of the form:

            Two-byte Family name (always 2)
            Two-byte Port Number
            Four-byte IP Address
            Eight one-byte 0 values


          - In SVR4 Loopback Transport Provider, address is an
            arbitrary string

     + This can be passed to your program as an argument, or your
       program can look it up in a table.

     + You can also use transport-specific name-to-address
       translation routines, but these make your applications
       transport-dependent


                                  SERVER CODE
       #include <tiuser.h>
       #include <stropts.h>
       #include <stdio.h>
       #include <fcntl.h>
       #include <signal.h>
       #include <sys/types.h>
       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <netdb.h>
       extern int t_errno;
       int current_fd = 0;
       #define SERV_PORT 5134

       main()
       {
               int listen_fd;       /* file descriptor into transport */
               int recfd;           /* file descriptor to accept on   */
               struct t_bind *req;  /* requested address to bind to   */
               struct t_bind *ret;  /* actual address that was bound  */
               struct t_call *call; /* used to listen for connection  */
               struct sockaddr_in myaddr; /* holds the address of     */
                                          /* this service             */

               signal(SIGCHLD, SIG_IGN);

               if ((listen_fd = t_open("/dev/tcp", O_RDWR, NULL)) < 0) {
                       t_error("t_open failed");
                       exit(1);
               }
               /* get space to hold the address you want to bind to */
               if ((req = (struct t_bind *)t_alloc(listen_fd, T_BIND,
                                                     T_ALL)) == NULL) {
                       t_error("t_alloc failed");
                       exit(1);
               }
               /* get space to hold the address you actually bind to */
               if ((ret = (struct t_bind *)t_alloc(listen_fd, T_BIND,
                                                    T_ALL)) == NULL) {
                       t_error("t_alloc failed");
                       exit(1);
               }

               /*
                *  Set qlen to 1 to signify that we will only receive
                *  one request at a time (and respond to it before
                *  getting another request).
                */

               req->qlen = 1;
               req->addr.len = sizeof(struct sockaddr_in);

               memset((void *)&myaddr, 0, sizeof(struct sockaddr_in));
               myaddr.sin_family = AF_INET;
               myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
               myaddr.sin_port = htons(SERV_PORT);

               (void)memcpy(req->addr.buf, &myaddr, sizeof(struct sockaddr_in));

               /*
                *  bind to the address the service will be offered
                *  over.
                */

               if (t_bind(listen_fd, req, ret) < 0) {
                       t_error("t_bind failed");
                       exit(1);
               }

               /*
                *  Make sure the address you got was the one you wanted!
                */

               if ((req->addr.len != ret->addr.len) ||
                   (memcmp(req->addr.buf, ret->addr.buf, req->addr.len))) {
                       fprintf(stderr, "Did not bind to correct address!\n");
                       exit(1);
               }


                                     NOTES

     + The t_bind structure is defined as follows:

       struct t_bind {
               struct netbuf addr;
               unsigned qlen;
       }

     + netbuf holds the address

     + qlen specifies how many outstanding connections you want to
       process

          - An outstanding connection is one that you've received but
            haven't responded to

          - Setting qlen greater than 1 complicates your code!


                                     NOTES

     + The netbuf structure is defined as follows:

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

     + buf is a buffer containing the address

     + len is the  number of bytes in the address

     + maxlen is the number of bytes the buffer can hold

     + t_alloc( ) allocates buf based on what the transport provider
       specifies maxlen is


                            TRANSPORT INDEPENDENCE

     + SVR4 provides routines that let you figure out the transport
       providers that exist on the system.

          - These routines also return the device name

          - No need to hard-code the information into the system

          - Use the getnetconfigent() routine:

            #include <netdir.h>
              .
              .
              .
              struct netconfig * netcfp;
 
              .
              .
              .
              if (!(netcfp = getnetconfigent("tcp")))
              {
                nc_perror("No TCP transport service");
                exit(1);
              }
              .
              .
              .
              t_open(netcfp->nc_device, O_RDWR, NULL);



                            TRANSPORT INDEPENDENCE

     + SVR4 also has routines to get addresses for a gives transport
       provider

          - No need to hard-code the information into the system

          - Use the netdir_getbyname() routine:

            #include <netconfig.h>
            #include <netdir.h>

              struct nd_hostserv nd_hostserv;
              struct nd_addrlist *nd_addrlistp;
               .
               .
               .
              nd_hostserv.h_host = HOST_SELF;
              nd_hostserv.h_serv = service_name;

               .
               .
               .
              if (netdir_getbyname(netcfp, &nd_hostserv,
                                   &nd_addrlistp)) {
                netdir_perror("getbyname");
                exit(1);
              }

              netbufp = nd_addrlistp->n_addrs;

              req->addr.len = netbufp->len;
              (void)memcpy(req->addr.buf, netbufp->buf, netbufp->len);
              req->qlen = 1;


                              SERVER CODE (cont.)

               if ((call = (struct t_call *)t_alloc(listen_fd, T_CALL,
                                                      T_ALL)) == NULL) {
                       t_error("could not allocate space for t_call!");
                       exit(1);
               }

               /*
                *  Loop continuously, listening for connection requests,
                *  accepting the call, and performing the service.
                */

               while (1) {
                       if (t_listen(listen_fd, call) < 0) {
                               t_error("could not listen!");
                               exit(1);
                       }

                       if ((recfd = t_open("/dev/tcp", O_RDWR, NULL)) < 0) {
                               t_error("could not open device!");
                               exit(1);
                       }

                       if (t_bind(recfd, NULL, NULL) < 0) {
                               t_error("bind for responding failed!");
                               exit(1);
                       }

                       if (t_accept(listen_fd, recfd, call) < 0) {

                               /*
                                *  Did it fail because client disconnected?
                                *  t_errno is set to T_LOOK if it did.
                                */

                               if (t_errno == TLOOK) {
                                       if (t_rcvdis(listen_fd, NULL) < 0) {
                                               t_error("could not disconnect");
                                               exit(1);
                                       }
                                       (void)t_close(recfd);
                                       continue;
                               } else {
                                       t_error("could not accept call");
                                       exit(1);
                               }
                       }
                       (void) run_server(listen_fd, recfd);
               }
       }

       void
       conrelease()
       {
               /*
                *  If the event is a disconnect, then exit.
                */
               if (t_look(current_fd) == T_DISCONNECT)
                       exit(1);
               /*
                *  Otherwise, it must be T_ORDREL, specifying the
                *  client wants to do an orderly release
                */
               if (t_rcvrel(current_fd) < 0) {
                       t_error("t_rcvrel failed!");
                       exit(1);
               }
               if (t_sndrel(current_fd) < 0) {
                       t_error("t_sndrel failed!");
                       exit(1);
               }
               exit(0);
       }

       int
       run_server(listen_fd, resfd)
       int listen_fd;
       int resfd;
       {
               switch (fork()) {
               case -1:
                       perror("fork failed!\n");
                       exit(1);

               default: /* parent */

                       t_close(resfd);
                       return;

               case 0:  /* child */

                       t_close(listen_fd);

                       perform_actions(resfd);

                       /*
                        *  Set to global variable so conrelease can
                        *  see it...
                        */

                       current_fd = resfd;

                       /*
                        *  Inform the system to let us know when an
                        *  orderly release (or other event such as a
                        *  disconnect) happens...
                        */

                       signal(SIGPOLL, conrelease);
                       if (ioctl(resfd, I_SETSIG, S_INPUT) < 0) {
                               perror("ioctl I_SETSIG failed");
                               exit(1);
                       }

                       /*
                        *  Has the orderly release already happened?
                        */

                       if (t_look(resfd) != 0) {
                               conrelease();
                               exit(0);
                       }

                       /*
                        *  wait for the orderly release indication
                        */

                       pause();
               }
       }

       int
       perform_actions(conn_fd)
       int conn_fd;
       {
               int done = 0;    /* indicates all data is read        */
               int where = 0;   /* points to where we are in buffer  */
               int nbytes;      /* the number of bytes read          */
               int flags = 0;   /* info returned from t_rcv()        */
               char reply;      /* a 'y' or a 'n' to reply to client */
               char buf[BUFSIZ];/* buffer to hold the name           */

               char cmd[BUFSIZ];     /* used to figure out if the  */
               char junkbuf[BUFSIZ]; /* specified user is logged   */
               FILE *fp;             /* onto the system            */

               /*
                *  Read the user name from the client.
                *  The flags argument would contain information like
                *  whether the data was "expedited", but it is ignored
                *  in this example.
                */

               do {
                       if ((nbytes = t_rcv(conn_fd, &buf[where],
                                       BUFSIZ - where, &flags)) < 0) {
                               t_error("read of data failed!");
                               exit(1);
                       }
                       where += nbytes;
                       if (buf[where - 1] == '\0') {
                               done = 1;
                       }
               } while (!done);

               /*
                *  Determine if the user is logged on...
                */

               sprintf(cmd, "who | grep '^%s '", buf);
               if ((fp = popen(cmd, "r")) == NULL
                         || fgets(junkbuf, BUFSIZ, fp) == NULL) {
                       reply = 'n';
               } else {
                       reply = 'y';
               }

               /*
                *  Send a "y" of a "n" to the client.
                *  The last argument is 0 specifying that no
                *  flags are sent.
                */

               if (t_snd(conn_fd, &reply, 1, 0) < 0) {
                       t_error("t_snd failed!");
                       exit(1);
               }
       }


                                  CLIENT CODE

       #include <stdio.h>
       #include <tiuser.h>
       #include <fcntl.h>
       #include <sys/types.h>
       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <netdb.h>
       #define SERV_PORT 5134
       struct hostent *gethostbyname();

       main(argc, argv)
       int argc;
       char **argv;
       {
               int fd;                   /* fd into transport provider */
               int flags = 0;            /* used to receive info       */
               char buf[BUFSIZ];         /* holds message from server  */
               struct t_call *sndcall;   /* used to call server        */
               struct hostent *hp;       /* holds IP address of server */
               struct sockaddr_in servaddr; /* the server's full addr  */

               /* Check for proper usage */
               if (argc != 3) {
                       fprintf(stderr,"Usage: %s machine username\n", argv[0]);
                       exit(2);
               }
               /* Open the transport provider */
               if ((fd = t_open("/dev/tcp", O_RDWR, NULL)) < 0) {
                       t_error("t_open failed!");
                       exit(1);
               }
               /* Bind to an arbitrary return address */
               if (t_bind(fd, NULL, NULL) < 0) {
                       t_error("t_bind failed!");
                       exit(1);
               }

               /*
                *  Get memory to hold call structure, copy in
                *  the server's address, and connect to the service
                */

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

               sndcall->addr.len = sizeof(struct sockaddr_in);
               memset((void *)&servaddr, 0, sizeof(struct sockaddr_in));
               servaddr.sin_family = AF_INET;
               servaddr.sin_port = htons(SERV_PORT);

               hp = gethostbyname(argv[1]);
               if (hp == 0) {
                       fprintf(stderr, "could not obtain address of %s\n", argv[1]);
                       exit(1);
               }

               (void)memcpy((caddr_t)&servaddr.sin_addr, hp->h_addr_list[0],
                                       hp->h_length);
               (void)memcpy(sndcall->addr.buf, &servaddr, sizeof(servaddr));

               if (t_connect(fd, sndcall, NULL) < 0) {
                       t_error("t_connect failed!");
                       exit(1);
               }


                            NOTES 

     + The t_call structure is defined as follows:

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

     + addr contains the address of the server

     + opt can be used to specify protocol specific options that may
       be needed by transport provider

     + udata can be used to pass user data to the server on the
       connection

     + sequence is not used on the client side

          - Used on server t_listen( ) call to identify the
            connection

          - Used if listening for multiple connect indications before
            responding to them

     + The third argument to t_connect( ) is a pointer to a t_call
       structure

          - Can be ignored in most cases

          - Contains information about the newly established
            connection

          - May have data sent by the server in response to
            connection request


                            TRANSPORT INDEPENDENCE

     + As with the server side of the application, the client side
       can use getnetconfigent() if running on a SVR4 system.

       #include <netconfig.h>
       #include <netdir.h>

         .
         .
         .
         struct netconfig * netcfp;

         .
         .
         .
         if (!(netcfp = getnetconfigent("tcp")))
         {
           nc_perror("No TCP transport service");
           exit(1);
         }

         .
         .
         .
         t_open(netcfp->nc_device, O_RDWR, NULL);


     + The client can also use the netdir_getbyname() routine,
       specifying the server name:


       #include <netconfig.h>
       #include <netdir.h>

         struct nd_hostserv nd_hostserv;
         struct nd_addrlist *nd_addrlistp;

          .
          .
          .
         nd_hostserv.h_host = server_name;
         nd_hostserv.h_serv = service_name;

          .
          .
          .
         if (netdir_getbyname(netcfp, &nd_hostserv,
                              &nd_addrlistp)) {
           netdir_perror("getbyname");
           exit(1);
         }

         netbufp = nd_addrlistp->n_addrs;

         sndcall->addr.len = netbufp->len;
         (void)memcpy(sndcall->addr.buf, netbufp->buf, netbufp->len);


                              CLIENT CODE (cont.)

               if (t_snd(fd, argv[2], strlen(argv[2]) + 1, 0) == -1) {
                       t_error("t_snd failed!\n");
                       exit(1);
               }

               if (t_rcv(fd, buf, 1, &flags) == -1) {
                       t_error("t_rcv failed!\n");
                       exit(1);
               }

               if (buf[0] == 'y') {
                       printf("%s is logged on server\n", argv[2]);
               } else {
                       printf("%s is not logged on server\n", argv[2]);
               }

               /*
                *  Perform the orderly release...
                */

               if (t_sndrel(fd) < 0) {
                       t_error("t_sndrel failed!\n");
                       exit(1);
               }

               /*
                *  Wait for server to respond with an orderly release.
                *  We can do this the way we did it with the server, but
                *  another way is to do a t_rcv().  t_rcv() will return
                *  (with an error) when an orderly release message is seen
                *  on the transport provider.
                */

               t_rcv(fd, buf, BUFSIZ, &flags);

               exit(0);
       }


                                      TLI
                              CONNECTIONLESS MODE


                               GENERAL CONCEPTS

     + The client machine sends a message to the server machine

     + Server machine actions:

          - Open a network device

          - ``Bind'' to a well-known address

               + Check if bound to correct one!

          - Get a message from the client

               + Message has return address in it

     + Client machine actions:

          - Open the network device

          - Create a ``transport endpoint''

          - Send data to the server address

          - Get a reply, if server sends one


                                 ROUTINES USED

       Extra routines for connectionless communication:

          + t_sndudata( )

               - Sends a unit of data to a specified address

          + t_rcvudata( )

               - Receives a data unit

               - Sender's address is within the message


                                 SERVER CODE
      #include <tiuser.h>
      #include <stropts.h>
      #include <stdio.h>
      #include <fcntl.h>
      #include <signal.h>
      #include <sys/types.h>
      #include <sys/socket.h>
      #include <netinet/in.h>
      #include <netdb.h>
      extern int t_errno;

      #define SERV_PORT 5134

      main()
      {
              int dg_fd;           /* file descriptor into transport */
              struct t_bind *req;  /* requested address to bind to   */
              struct t_bind *ret;  /* actual address that was bound  */
              int flags;           /* holds info when reading        */
              struct t_unitdata *datap;  /* the actual datagram      */
              struct sockaddr_in myaddr; /* address of server        */
              char cmd[BUFSIZ];     /* used to figure out if the     */
              char junkbuf[BUFSIZ]; /* specified user is logged      */
              FILE *fp;             /* onto the system               */

              if ((dg_fd = t_open("/dev/udp", O_RDWR, NULL)) < 0) {
                      t_error("t_open failed");
                      exit(1);
              }

              /* get space to hold the address you want to bind to */
              if ((req = (struct t_bind *)t_alloc(dg_fd, T_BIND,
                                                   T_ALL)) == NULL) {
                      t_error("t_alloc failed");
                      exit(1);
              }

              /* get space to hold the address you actually bind to */
              if ((ret = (struct t_bind *)t_alloc(dg_fd, T_BIND,
                                                   T_ALL)) == NULL) {
                      t_error("t_alloc failed");
                      exit(1);
              }

              /*
               *  Set qlen to 0 since qlen has no meaning for
               *  connectionless transport providers
               */

              req->qlen = 0;
              req->addr.len = sizeof(struct sockaddr_in);

              memset((void *)&myaddr, 0, sizeof(struct sockaddr_in));
              myaddr.sin_family = AF_INET;
              myaddr.sin_port = htons(SERV_PORT);
              myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
              (void)memcpy(req->addr.buf, &myaddr, sizeof(struct sockaddr_in));

              /*
               *  bind to the address the service will be offered
               *  over.
               */
              if (t_bind(dg_fd, req, ret) < 0) {
                      t_error("t_bind failed");
                      exit(1);
              }

              /*
               *  Make sure the address you got was the one you wanted!
               */
              if ((req->addr.len != ret->addr.len) ||
                  (memcmp(req->addr.buf, ret->addr.buf, req->addr.len))) {
                      fprintf(stderr, "Did not bind to correct address!\n");
                      exit(1);
              }


                           TRANSPORT INDEPENDENCE


    + On SVR4, no need to hard-code the information into the system

    + Use the getnetconfigent() routine:

      #include <netconfig.h>
      #include <netdir.h>

        .
        .
        .
        struct netconfig * netcfp;

        .
        .
        .
        if (!(netcfp = getnetconfigent("udp")))
        {
          nc_perror("No UDP transport service");
          exit(1);
        }

        .
        .
        .
        t_open(netcfp->nc_device, O_RDWR, NULL);


    + SVR4 also has routines to get addresses for a gives transport
      provider

         - No need to hard-code the information into the system

         - Use the netdir_getbyname() routine:

           #include <netconfig.h>
           #include <netdir.h>
 
             struct nd_hostserv nd_hostserv;
             struct nd_addrlist *nd_addrlistp;
 
              .
              .
              .
             nd_hostserv.h_host = HOST_SELF;
             nd_hostserv.h_serv = service_name;

              .
              .
              .
             if (netdir_getbyname(netcfp, &nd_hostserv,
                                  &nd_addrlistp)) {
               netdir_perror("getbyname");
               exit(1);
             }

             netbufp = nd_addrlistp->n_addrs;

             req->addr.len = netbufp->len;
             (void)memcpy(req->addr.buf, netbufp->buf, netbufp->len);
             req->qlen = 0;


                             SERVER CODE (cont.)


              if ((datap = (struct t_unitdata *)t_alloc(dg_fd, T_UNITDATA,
                                                     T_ALL)) == NULL) {
                      t_error("could not allocate space for t_unitdata!");
                      exit(1);
              }

              /*  Loop continuously, processing datagrams */
              while (1) {
                      if (t_rcvudata(dg_fd, datap, &flags) < 0) {
                              /*
                               *  If the t_rcvudata() routine fails, check if the
                               *  reason was because an error occurred on a
                               *  previous datagram we sent (t_errno is set to
                               *  TLOOK if that is the case).  If that's so,
                               *  clear the error event with t_rcvuderr().
                               */
                              if (t_errno == TLOOK) {
                                      if (t_rcvuderr(dg_fd, NULL) < 0) {
                                        t_error("could not clear error event!");
                                      }
                              } else {
                                      t_error("could not read datagram!");
                              }
                              continue;
                      }
                      sprintf(cmd, "who | grep '^%s '", datap->udata.buf);
                      if ((fp = popen(cmd, "r")) == NULL
                          || fgets(junkbuf, BUFSIZ, fp) == NULL) {
                              strcpy(datap->udata.buf, "n");
                      } else {
                              strcpy(datap->udata.buf, "y");
                      }
                      datap->udata.len = 1;
                      if (t_sndudata(dg_fd, datap) < 0) {
                              t_error("could not send datagram!");
                              continue;
                      }
              }
      }


                                    NOTES

    + The t_unitdata structure is defined as follows:

      struct t_unitdata {
         struct netbuf addr;
         struct netbuf opt;
         struct netbuf udata;
      }

    + addr contains:

         - The source address when used with t_rcvudata( )

         - The destination address when used with t_sndudata( )

    + opt identifies protocol specific options

    + udata holds the data

    + The flags argument of t_rcvudata( ) must point to an integer
      value

    + It will be set to T_MORE if the udata element of the
      t_unitdata structure was not large enough to hold the data

         - You would have to do more t_rcvudata( ) calls to get the
           rest of the datagram

    + In our case, t_alloc( ) specified with T_ALL allocates the
      maximum datagram size for the transport provider

         - No need to check for T_MORE



                                 CLIENT CODE
      #include <stdio.h>
      #include <tiuser.h>
      #include <fcntl.h>
      #include <sys/types.h>
      #include <sys/socket.h>
      #include <netinet/in.h>
      #include <netdb.h>
      #include <signal.h>

      #define NUM_TRIES   20
      #define SERV_PORT 5134

      struct hostent *gethostbyname();
      int timed_out;
      extern int t_errno;

      main(argc, argv)
      int argc;
      char **argv;
      {
              int fd;                   /* fd into transport provider */
              int flags = 0;            /* used to receive info       */
              struct t_unitdata *datap; /* used to call server        */
              int tries = NUM_TRIES;    /* number of tries to send    */
              int got_it = 0;           /* determines datagram receipt*/
              struct hostent *hp;       /* holds IP address of server */
              struct sockaddr_in servaddr;  /* the server's full addr */
              void handler();           /* handles timeout signals    */

              /* Check for proper usage */
              if (argc != 3) {
                      fprintf(stderr,"Usage: %s host username\n", argv[0]);
                      exit(2);
              }
              /* Open the transport provider */
              if ((fd = t_open("/dev/udp", O_RDWR, NULL)) < 0) {
                      t_error("t_open failed!");
                      exit(1);
              }

              /* Bind to an arbitrary return address */
              if (t_bind(fd, NULL, NULL) < 0) {
                      t_error("t_bind failed!");
                      exit(1);
              }

              /* Get memory to hold the datagram */

              if ((datap = (struct t_unitdata *)t_alloc(fd, T_UNITDATA,
                                                T_ALL)) == NULL) {
                      t_error("t_alloc failed!");
                      exit(1);
              }

              /* Fill in the server's address and the data.  */
              if (datap->udata.maxlen < (strlen(argv[2]) + 1)) {
                      fprintf(stderr, "The user name %s is too large\n", argv[2]);
                      exit(1);
              }
              datap->udata.len = strlen(argv[2]) + 1;
              strcpy(datap->udata.buf, argv[2]);

              datap->addr.len = sizeof(struct sockaddr_in);
              memset((void *)&servaddr, 0, sizeof(struct sockaddr_in));
              servaddr.sin_family = AF_INET;
              servaddr.sin_port = htons(SERV_PORT);

              hp = gethostbyname(argv[1]);
              if (hp == 0) {
                      fprintf(stderr, "could not obtain address of %s\n", argv[1]);
                      exit(1);
              }

              (void)memcpy((caddr_t)&servaddr.sin_addr, hp->h_addr_list[0],
                                       hp->h_length);
              (void)memcpy(datap->addr.buf, &servaddr, sizeof(servaddr));



                           TRANSPORT INDEPENDENCE

    + SVR4 clients can use the getnetconfigent() routine:

      #include <netconfig.h>
      #include <netdir.h>

        .
        .
        .
        struct netconfig * netcfp;

        .
        .
        .
        if (!(netcfp = getnetconfigent("udp")))
        {
          nc_perror("No UDP transport service");
          exit(1);
        }

        .
        .
        .
        t_open(netcfp->nc_device, O_RDWR, NULL);


    + SVR4 clients can use the netdir_getbyname() routine to get
      address of the server:


      #include <netconfig.h>
      #include <netdir.h>

        struct nd_hostserv nd_hostserv;
        struct nd_addrlist *nd_addrlistp;

         .
         .
         .
        nd_hostserv.h_host = server_name;
        nd_hostserv.h_serv = service_name;

         .
         .
         .
        if (netdir_getbyname(netcfp, &nd_hostserv,
                             &nd_addrlistp)) {
          netdir_perror("getbyname");
          exit(1);
        }

        netbufp = nd_addrlistp->n_addrs;

        datap->addr.len = netbufp->len;
        (void)memcpy(datap->addr.buf, netbufp->buf, netbufp->len);


                             CLIENT CODE (cont.)

              while (tries --) {
                      if (t_sndudata(fd, datap) == -1) {
                              t_error("t_sndudata failed!\n");
                              exit(1);
                      }

                      /*
                       *   Allow 15 seconds for the response to arrive
                       */

                      timed_out = 0;
                      signal(SIGALRM, handler);
                      alarm(15);

                      if (t_rcvudata(fd, datap, &flags) == 0) {
                              alarm(0);
                              got_it = 1;
                              break;
                      }

                      /*
                       *  We now know the t_rcvudata() routine failed.
                       *  Check if we timed out.  If we did, we continue
                       *  the loop and resend the request.
                       */

                      if (timed_out) {
                              printf("Server not responding, retrying...\n");
                              continue;
                      }

                      /*
                       *  If the t_rcvudata() routine fails, check if the
                       *  reason was because an error occurred on a previous
                       *  datagram we sent (t_errno is set to TLOOK if that is
                       *  the case).  If that's so, clear the error event
                       *  with the t_rcvuderr() routine.
                       */

                      if (t_errno == TLOOK) {
                              if (t_rcvuderr(fd, NULL) < 0) {
                                      t_error("could not clear error event!");
                                      exit(1);
                              }
                              continue;
                      }

                      t_error("t_rcvudata failed!");
                      exit(1);
              }

              if (!got_it) {
                      printf("failed %d times, exiting\n", NUM_TRIES);
                      exit(1);
              }

              if (datap->udata.buf[0] == 'y')
                      printf("%s is logged on server\n", argv[1]);
              else
                      printf("%s is not logged on server\n", argv[1]);
              exit(0);
      }

      void
      handler()
      {
              timed_out = 1;
      }