ICS 651 Exercise 1: UNIX Client and Server

Assigned August 25th, to be completed by September 1st. This homework will not be graded. You may get feedback on it by e-mailing to esb@hawaii.edu. Only send plain text except where another format is absolutely necessary.

Do not do this homework if you took ICS 451.

You are encouraged to work in groups for this homework.

  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 hosts in the above list, and also www2.hawaii.edu. On uhunix, the command is "/usr/sbin/ping". If you're not familiar with ping, use "man ping" to find out how to use the "-s" switch. Note that different versions of ping (for similar but not identical OSs) have different command-line switches -- "-s" should only be needed on Sun machines such as uhunix.

      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 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 -- and don't worry about the distance to symmetricon.com) or this tool (suggested to me by Peter Rodrigues).
      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 uhunix 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. */
      /* 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("uhunix.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 "uhunix.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.

      Make sure you understand what each line in this file does. If not, work with your group, 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.

    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 uhunix, 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. */
      /* compile with: gcc -o server server.c -lsocket -lnsl */
      
      #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 651
Instructor: Edo Biagioni