ICS 651 Homework 1: UNIX Client and Server

Handed out January 10th, to be completed by January 19th. This homework will not be graded, but you may turn it in if you wish for feedback. To turn in the homework, e-mail it to esb@hawaii.edu with subject line "ICS 651 homework 1".

A note on answers. If you do turn in the homework, please spend a little time making sure you only turn in the parts that are relevant, that is, answers to questions that the instructor asked, and questions that you may have, including any material needed to understand your questions. For this homework there is little benefit in sending me copies of the code that I posted on the web, or traceroute logs, or similar things.

  1. Existing Unix clients
    1. Telnet
    2. Use telnet to connect to the daytime port of a variety of hosts. The command is "telnet hostname daytime". Connect to each of the following hosts: Did you get the same time from each one? If not, explain the difference.

      What happens if you attempt to connect to the daytime port of www.berkeley.edu?

      You can use telnet to connect to port 80 of a machine that is running a web server. Then, type:

      GET path
      
      
      Be sure to include an empty line. This should get you the HTML source code for the document specified by path. You can also type (e.g., for www.berkeley.edu)
      GET / HTTP/1.1
      Host: www.berkeley.edu
      Accept: */*
      Connection: close
      
      
      (where "/" is your path). The first request is HTTP 0.9, this one is HTTP 1.1. In HTTP 1.1, we may have one or more header fields following the initial line. Project 3 will explore this.

    3. Ping
    4. Now use ping to determine the round-trip time to each of these hosts (including www.berkeley.edu). On the suns (including uhunix), the command is "/usr/sbin/ping". If you're not familiar with ping, use "man ping" (on any unix/linux system) to find out how to use the "-s" switch. Note that different versions of ping have different command-line switches -- "-s" should only be needed on Sun machines. Ping is available under windows as well as Unix and variants.

      Can you justify the round-trip times to each of these hosts by considering just the speed of light? In other words:

      If you can, also try this from your home machine, and compare and try to explain the results:

      1. Compute an approximate distance (in km. or mi.) from here to the site. You may have to use an atlas or a map of the world, or you can use the site How far is it?, which was suggested to me by Jitender Miglani. Note Muenster is not there, you may want to use Dortmund instead).
      2. Divide the distance (km or mi) by the speed of light, 300 km/ms (or 186 mi/ms). The result is a time estimate in ms (milli-seconds) of how long it would take a ray of light in vacuum to cover that distance.
      3. Ping measures the round-trip time, so double the time you just computed.
      4. How does your time estimate compare to the time measured by "ping"? compare to each of the minimum, average, and maximum times, and give a brief explanation of the differences, if any.

    5. Traceroute
    6. If you have access to a machine with traceroute, use traceroute to count the number of hops to each of the machines you just pinged. Traceroute on uhunix2 is /usr/sbin/traceroute -- on windows, it is called tracert.

  2. Writing a Unix client
    1. Compiling and running
    2. Cut and paste the following code into a file, compile it, and run it. At the prompt, enter a string that you want to see reversed.

      
      /* client.c: program to connect to server. */
      /* on the suns, compile with: gcc -o client client.c -lsocket -lnsl */
      /* on linux, compile with: gcc -o client client.c */
      
      #include <stdio.h>
      #include <sys/types.h>
      #include <sys/socket.h>
      #include <netinet/in.h>
      #include <arpa/inet.h>
      #include <netdb.h>
      
      #define PORTNUMBER 4321  /* this is where the server can be reached */
      #define BUFSIZE 1000     /* we truncate strings longer than this */
      
      #define print_error(s) { perror(s); exit(1); }
      
      main ()
      {
        int s;
        struct protoent * protocolentry;
        struct hostent * hostentry;
        struct sockaddr_in sin;
        struct sockaddr * sap = (struct sockaddr *) &sin;
        char buf[BUFSIZE];
      
        /* get the protocol named TCP */
        if ((protocolentry = getprotobyname("tcp")) == NULL)
          print_error("getprotobyname");
        /* create a TCP socket */
        if ((s = socket(AF_INET, SOCK_STREAM, protocolentry->p_proto)) < 0)
          print_error("socket");
        /* find the IP address of the server */
        hostentry = gethostbyname("maru.ics.hawaii.edu");
        /* always check the return value */
        if ((hostentry == NULL) || (hostentry->h_addr_list == NULL))
          print_error("gethostbyname");
        /* initialize the socket address */
        bzero (&sin, sizeof (sin));
        sin.sin_family = AF_INET;
        /* copy the destination IP to the socket address */
        memcpy(&(sin.sin_addr), hostentry->h_addr_list[0], hostentry->h_length);
        /* also set the port number in the socket address */
        sin.sin_port = htons(PORTNUMBER);
        /* now establish a TCP connection to the given address, referred to
           by the socket we created above */
        if (connect(s, sap, sizeof(sin)) < 0) print_error("connect");
        /* get user data, and send it to the server */
        printf ("enter a string: ");
        fgets (buf, BUFSIZE, stdin);
        if (write(s, buf, strlen(buf)) < 0) print_error("write");
        /* close the connection (and the socket at the same time) */
        if (close(s) < 0) print_error("close");
      }
      
      
      This program contacts a server running on the machine "maru.ics.hawaii.edu" and gives it the string you enter.

      If you are on a machine that is not a sun, you may have to use different include files or different link switches (the "-l" switches).

      You are welcome (but not required) to rewrite this program to run on a Windows system. If you send it to me, I will post it here.

    3. Reading data
    4. Modify the client program to read data from the server after sending the data. The server sends back the same data, but in the reverse order (i.e. right-to-left or back-to-front).

      Save a copy of this program for later use, in part 1 of the server exercise.

    5. Change host and port
    6. Modify the client program to read data from the daytime port, port 13, and from host "www2.ics.hawaii.edu".

    7. Compute the time per connection
    8. Modify the client program to connect to the daytime port in a loop until it has connected 1000 times, printing the first and the last string it gets. Manually compute the difference and figure out how long it takes to open and close a single connection.

  3. Writing a Unix server
    1. Compiling and running
    2. Cut and paste the following code into a file, compile it, and run it in the background. Then connect to it using your client program from the previous section. Note that you have to change the name of the host that the client connects to (if you run your server on uhunix2, the client must connect to uhunix2.its.hawaii.edu, not to maru.ics.hawaii.edu). Try running the client both on the same host as the server, and, if you have access to a different machine, on a different host.

      
      /* server.c: program to accept input from clients. */
      /* on suns, compile with: gcc -o server server.c -lsocket -lnsl */
      /* on linux, compile with: gcc -o server server.c */
      
      #include <stdio.h>
      #include <sys/types.h>
      #include <sys/socket.h>
      #include <sys/wait.h>
      #include <netinet/in.h>
      #include <signal.h>
      #include <netdb.h>
      
      #define PORTNUMBER 4321
      #define BUFSIZE 100
      
      #define print_error(s) { perror(s); exit(1); }
      
      main ()
      {
        int passive, session;
        struct sockaddr_in sin;
        struct sockaddr * sap = (struct sockaddr *) &sin;
        int count, i = 0, pos = 0;
        char buf[BUFSIZE] = "";
        struct sigaction siga;
        struct protoent * protocolentry;
      
        if ((protocolentry = getprotobyname("tcp")) == NULL)
          print_error("getprotobyname");
        if ((passive = socket(PF_INET, SOCK_STREAM, protocolentry->p_proto)) < 0)
          print_error("socket");
        bzero (&sin, sizeof (sin));
        sin.sin_family = AF_INET;
        sin.sin_port = htons(PORTNUMBER);
        /* connections may be to any IP address belonging to this host */
        sin.sin_addr.s_addr = INADDR_ANY;
        /* bind the socket to the address, to specify which port this server
         is using */
        if (bind(passive, sap, sizeof (sin)) != 0) print_error("bind");
        /* make sure child processes are really detached (see exit(2)) */
        /* note this doesn't work on Linux -- see below for Linux fix */
        if (sigaction (SIGCHLD, NULL, &siga) < 0) print_error ("sigaction/get");
        siga.sa_flags |= SA_NOCLDWAIT | SA_NOCLDSTOP;
        if (sigaction (SIGCHLD, &siga, NULL) < 0) print_error ("sigaction");
        /* specify the maximum queue length */
        if(listen(passive, 5) < 0) print_error("listen");
        count = sizeof (sin);
        /* "accept" creates new sockets when a client connects */
        while ((session = accept(passive, sap, &count)) >= 0) {
          if (fork() == 0) {	/* create a child process -- code follows */
            /* read the client data */
            count = read(session, buf, BUFSIZE - 1);
            /* turn it into a C string */
            buf[count] = '\0';
            pos = count - 1;
            /* get rid of any final newline */
            if (buf[pos] == '\n') pos--;
            /* just to do something, here we reverse the string
               that we received and return it to the client */
            for (i = 0; i <= pos; ) {
      	int swap = buf[i];
      	buf[i++] = buf[pos];
      	buf[pos--] = swap;
            }
            if (write(session, buf, count) < 0) print_error("write");
            /* this child is done, can exit */
            if (close(session) < 0) print_error("child close");
            exit(0);
          } else {			/* parent process */
            if (close(session) < 0) print_error("parent close");
            /* on Linux, the above sigactions do not prevent child
      	 processes from becoming zombies, so instead simply
      	 wait(2) (without suspending) until all that are ready
               to harvest are harvested. */
            while (waitpid (-1, NULL, WNOHANG) != 0) {
            }
          }
        }
      }
      
      

    3. Bind
    4. Note what happens if you try to run a second server while the first is still running. The operating system marks the port "in use" so that only one server can use it at a time. If a connection has been opened, the operating system keeps the port "in use" for a few minutes after the server is gone.

      If you have trouble running the server, someone else on the same machine may be using the same port number, so try using a different port number. Of course, the port number in the client must match the port number in the server.

    5. Questions
      1. Where does the program fail if the port is in use?
      2. What does the "fork" command do?
      3. C has two ways of representing strings: null-terminated and length-based. Which way is used by the "read" and "write" system calls? Which way is used by "strlen" and "strcpy"? Would it be a good idea to use "printf ("%s\n", buf);" with the result of reading the data? Why or why not?
      To answer some of these questions, you may want to refer to the C implementation thoughts in Chapter 1 of the notes, at the end of the section "A Simple Network".




Computer Networks, ICS 651
Instructor: Edo Biagioni