ICS 451 Exercise 1: UNIX Client and Server

Assigned August 23rd, to be completed by August 30th. This exercise will not be graded, but you must turn it in by e-mailing to esb@hawaii.edu. Only send plain text except where another format is absolutely necessary.

You are encouraged to work in groups for this exercise.

To turn in this exercise, simply turn in the answers to section 1.1 (did you get the same time from each one? If not why not?), section 1.2 (a table of distances, computed Round-Trip Times, measured Round-Trip Times, and explanation for differences), section 2.4 (how long per connection?), and section 3.5. Do not send any code, but do send any questions you have.

  1. Existing Unix clients
    1. Telnet
    2. Use telnet to connect to the daytime port of a variety of hosts. The command (on Unix/Linux) 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 www2.hawaii.edu?

      You can use telnet to connect to port 80 of a machine that is running a web server. Then, type (or cut and paste):

      GET path HTTP/1.1
      Host: web.site.domain.name
      Accept: */*
      
      
      path can be as simple as a "/", or as complicated as you wish to type. Be sure to include the empty line. This should get you the HTML for the document specified by path, though some servers may give you unexpected responses.

      For example, if connecting to port 80 of www.hawaii.edu,

      GET / HTTP/1.1
      Host: www.hawaii.edu
      Accept: */*
      
      

      In HTTP 1.1, there may be one or more header fields following the initial line. Project 1 will explore this.

    3. Ping
    4. Now use ping to determine the round-trip time to each of the following hosts:

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

      1. Compute an approximate distance (in km. or mi.) from your computer 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)
      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 short string.

      
      /* client.c: program to connect to server. */
      /* on sun/solaris/oracle, compile with: gcc -o client client.c -lsocket -lnsl */
      /* on other systems, might compile with: gcc -o client client.c */
      
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <sys/types.h>
      #include <sys/socket.h>
      #include <netinet/in.h>
      #include <arpa/inet.h>
      #include <netdb.h>
      
      #define portnumber 4872
      #define BUFSIZE 1000
      
      #define 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];
      
        if ((protocolentry = getprotobyname("tcp")) == NULL) error("getprotobyname");
        if ((s = socket(AF_INET, SOCK_STREAM, protocolentry->p_proto)) < 0)
          error("socket");
        hostentry = gethostbyname("www2.ics.hawaii.edu");
        if ((hostentry == NULL) || (hostentry->h_addr_list == NULL))
          error("gethostbyname");
        memset (&sin, 0, sizeof (sin));
        sin.sin_family = AF_INET;
        memcpy(&(sin.sin_addr), hostentry->h_addr_list[0], hostentry->h_length);
        sin.sin_port = htons(portnumber);
        if (connect(s, sap, sizeof(sin)) < 0) error("connect");
        printf ("enter a string: ");
        fgets (buf, BUFSIZE, stdin);
        if (send(s, buf, strlen(buf), 0) < 0) error("send");
        if (close(s) < 0) error("close");
      }
      
      
      This program contacts a server running on the machine "www2.ics.hawaii.edu" and gives it the string you entered.

      If, for any reason, the server is not running, run your own server (part 3 of this exercise), and modify the domain name and port number to identify this server that you are running.

      If you run your server on a machine, and are unable to connect to it from outside that machine, but have no problem connecting to it from the local host, that machine may be behind a firewall.

      Make sure you understand what each line in this file does. If not, work with your group (if you have one), or post to the class mailing list (we might get a good discussion going!) or ask the instructor or study the textbook.

      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). On Linux, you do not need any "-l" switches.

      You are welcome (but not required) to rewrite this program to run on a Windows system.

    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 (just to show it is not the same data) sends it back in the reverse order, i.e., right-to-left or back-to-front.

      Save a copy of this program for later use, in part (3.1).

    5. Change host and port
    6. If you have a host for which you can turn on the daytime service, modify the client program to read data from this host. Do NOT use the hosts in the list in Section 1.1, since the next step will send lots of connection requests, which could be considered a nuisance by some network administrators. You may use the daytime server at 128.171.10.28 for this part and for the next step.

    7. Compute the time per connection
    8. If you have done the previous step, 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 establish and teardown 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 (2.2). Note that you have to change the name of the host that the client connects to (unless you run the server on uhunix2, the client must connect to the machine on which you run your server). Also, make sure you have the correct port numbers -- as posted here, the port numbers do not match. Select a port number at random (between 1025 and 65,535). 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 sun/solaris/oracle, compile with: gcc -o server server.c -lsocket -lnsl */
      /* on other systems, might compile with: gcc -o server server.c */
      
      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      #include <string.h>
      #include <sys/types.h>
      #include <sys/socket.h>
      #include <netinet/in.h>
      #include <signal.h>
      #include <netdb.h>
      
      #define portnumber 4321
      #define BUFSIZE 100
      
      #define error(s) { perror(s); exit(1); }
      
      main ()
      {
        int passive, session;
        struct sockaddr_in sin;
        struct sockaddr * sap = (struct sockaddr *) &sin;
        ssize_t count;
        int i = 0, pos = 0;
        socklen_t adrsize;
        char buf[BUFSIZE] = "";
        struct sigaction siga;
        struct protoent * protocolentry;
      
        if ((protocolentry = getprotobyname("tcp")) == NULL) error("getprotobyname");
        if ((passive = socket(PF_INET, SOCK_STREAM, protocolentry->p_proto)) < 0)
          error("socket");
        memset (&sin, 0, sizeof (sin));
        sin.sin_family = AF_INET;
        sin.sin_port = htons(portnumber);
        sin.sin_addr.s_addr = INADDR_ANY;
        if (bind(passive, sap, sizeof (sin)) != 0) error("bind");
        /* make sure child processes are really detached (see exit(2)) */
        if (sigaction (SIGCHLD, NULL, &siga) < 0) error ("sigaction/get");
        siga.sa_flags |= SA_NOCLDWAIT | SA_NOCLDSTOP;
        if (sigaction (SIGCHLD, &siga, NULL) < 0) error ("sigaction");
        /* specify the maximum queue length */
        if(listen(passive, 5) < 0) error("listen");
        adrsize = sizeof (sin);
        while ((session = accept(passive, sap, &adrsize)) >= 0) {
          if (fork() == 0) {	/* child process */
            count = recv(session, buf, BUFSIZE - 1, 0);
            buf[count] = '\0';
            pos = count - 1;
            if (buf[pos] == '\n') pos--;
            for (i = 0; i <= pos; ) {
      	int swap = buf[i];
      	buf[i++] = buf[pos];
      	buf[pos--] = swap;
            }
            if (send(session, buf, count, 0) < 0) error("write");
            if (close(session) < 0) error("child close");
            exit(0);
          } else {			/* parent process */
            if (close(session) < 0) error("parent close");
          }
        }
      }
      
      

      Again, make sure you understand what each line in this file does, but this time also check to see that the return values from each function are checked for errors. This is very important in network programming -- many errors are possible, depending on the state of the network and the remote (and local) machine.

    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 at a time can run accept on this port. If a connection has been opened, the operating system may keep the port "in use" for a few minutes after the server is gone.

      If you have trouble running the server (because bind fails), someone else on the same machine may be using the same port number, so try using a different port number. Be sure the port number in the client matches the port number in the server.

    5. Using Telnet to test your server
    6. Also use telnet (instead of the specialized client) to connect to your server, enter strings, and look at the result. This is how telnet is used to debug servers.

    7. Wireshark
    8. If you have access to Wireshark or can install it, repeat some of the accesses above after starting wireshark to look at the packets that are exchanged. Wireshark has no information about the (very minimal) protocol that our application uses, so simply displays the raw data.

    9. Questions
      1. Where does the program fail if the port is in use?
      2. What does the "fork" command do? Can you modify the program to remove "fork" and have a server that only handles one connection at a time? Try it, then see what happens if you try to connect another client while the server is connected to the first client.
      3. C has two ways of representing strings: null-terminated and length-based.
        • Which way is used by the "read" and "write" or "recv" and "send" 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?




Computer Networks, ICS 451
Instructor: Edo Biagioni