Project05 - Chat app

Due: Wed, May 4, 2022 at 11:59 PM to Github Classroom Assignment

Requirements

  1. You will develop a chat app which runs on the CSLabs Linux machines
  2. Your repo must contain a Makefile which generates an executable called lab05
  3. Your app will broadcast your online presence periodically
  4. Your app will use non-blocking I/O to send and receive chat messages from other users on the network
  5. Network requirements
    1. As in lab07, you will broadcast presence to the UDP broadcast address 10.10.13.255
    2. Your app will use UDP port 8221 for presence and your assigned TCP port for 1:1 chat messages
  6. Behavior requirements
    1. You must use your USF username (e.g. phpeterson) for presence and chat messages – no pseudonyms
    2. You must use the network in accordance with the USF’s Technology Resources Appropriate Use Policy

Divide and Conquer

  1. You can reuse your client code from lab07 which broadcasts your presence using the format: online phpeterson 8382 and offline phpeterson 8382
  2. You can reuse your server code from lab07 and incorporate it into project05 to accept presence messages
  3. When you know who’s online, you can create a data structure (array or linked list) which contains the username and IP address or hostname
  4. You can develop new code to use TCP sockets to send and receive chat messages
  5. You can accept user input @phpeterson: do you have OH today? on stdin using poll() as we will discuss in lecture.

Boundary Conditions

  1. You may assume that there are no more than 64 users online at a time
  2. You may assume that chat messages are no more than 128 characters long, and are NUL-terminated C strings

Rubric

  1. 20 pts: Presence: periodic online broadcasts, offline when your app exits
  2. 40 pts: Two-way 1:1 chat
  3. 20 pts: Interactive grading questions
  4. 10 pts: Error handling and memory management
  5. 10 pts: Neatness

Extra credit

  1. 2 pts: Add support for channels, e.g. #sushi-lovers: lunch at Amiti's?. Demonstrate this using multiple instances of your project05 executable on multiple lab hosts. You may assume that all of your instances (phpeterson-1, phpeterson-2, …) are members of the #sushi-lovers channel

Implementation Guide

Non-Blocking I/O

  1. Your program needs to process network connections and user input in a time-sliced way. You may want to use scanf() to get user input, but that blocks, which means network connections wouldn’t be processed.
  2. Your program should use poll() to do time-slicing between these activities
     struct pollfd {
         int   fd;         /* file descriptor */
         short events;     /* requested events */
         short revents;    /* returned events */
     };
     int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    
  3. You should write a loop which looks roughly like this:
     struct pollfd fds[64];
     int nfds = ...;
     while (!done) {
         int num_ready = poll(fds, nfds, 100);
         if (num_ready == ...) 
             /* do "server" things */
     }
    
  4. You will use a pollfd for your UDP broadcast socket on port 8221 and for your TCP listener on your assigned port.
  5. After you have created your pollfd structs, you can call poll() in a while(!done) loop
  6. poll() returns the number of pollfd structs which have requested flags (POLLIN) set. You should loop over your fdpoll array and process the file descriptors which have data available (STDIN_FILENO, or UDP broadcast, or TCP chat)

Polling STDIN_FILENO for user input

  1. Since stdin is just a file with a file descriptor (STDIN_FILENO), you can read keyboard input by using a pollfd for stdin and wait for POLLIN events on that FD
  2. poll() will return bits and pieces of user input typed in between timeouts
  3. You can read the pollfd.fd using getchar() or other functions
  4. You should accumulate (strcat()?) the bits and pieces you read in a string
  5. When the user types a '\n' you should write the string to the TCP socket for the recipient (client side, like the TCP test app)
  6. You should choose keyboard sequence to use to exit your loop. Perhaps q or use CTRL-D, which causes getchar() to return EOF (or -1)

UDP Broadcast

  1. As in lab07, you will use socket(), setsockopt() and UDP sendto() to broadcast your presence to the labs subnet (10.10.13.255) which includes all computers on the subnet listening to port 8221 (as in the given UDP client)
  2. Your project05 program will add the ability to receive new UDP messages, using the code from your lab07 server (similar to the given lab-udp server in the inclass repo)
    1. Setup the connection using the hints, getaddrinfo(), and bind()
    2. When your UDP file descriptor is ready to read, poll() will put the flag POLLIN in the revents of the pollfd
    3. When that happens, you can use UDP recvfrom() to get the data waiting to be read (as in the given UDP server)
  3. Protocol: The UDP packet should contain online yourname yourport. You can create this string with snprintf() and parse an incoming presence broadcast with snscanf()

TCP Setup

  1. You will use TCP (stream) sockets to do 1:1 communication with other users.
  2. For socket() the domain is still PF_INET, the type is SOCK_STREAM (rather than DATAGRAM) and the protocol is IPPROTO_TCP (rather than UDP)
  3. You should set up a TCP listener using hints, getaddrinfo(), socket(), bind() and listen(). The socket should have SO_REUSEADDR
  4. The socket needs to be non-blocking, which you can set up like this:
     int enable = 1;
     ioctl(fd, FIONBIO, (char*) &enable);
    
  5. You should add the TCP file descriptor to a pollfd in your array of pollfd so the poll() loop can notify you when someone is trying to connect.

Reading from the TCP socket

  1. When the TCP listener pollfd has revents & POLLIN you should accept() the connection, which creates a new file descriptor for the chat between you and the sending person
  2. That new file descriptor should be added to your list of pollfd so you can use it to send and receive messages with that user. One new FD will be created per user you chat with.

Writing to the TCP socket

  1. If the user types @phpeterson: office hours? you should lookup phpeterson in your list of (user, host, port)
  2. You should use hints and getaddrinfo() using the user’s hostname (e.g. vlab21.cs.usfca.edu) to create a socket, and then connect() to the socket:
     connect(fd, res->ai_addr, res->ai_addrlen);)
    
  3. When the socket has been connected, you can use TCP send() to transmit the user’s message to the socket:
     int sent = send(fd, msg, len, 0);
    

Keeping Track of Users

  1. In order to keep track of who is online, you may wish to build a list of users using a struct something like this
     struct user {
         char status[BUF_SIZE]; /* online or offline? */
         char name[BUF_SIZE];   /* user name */
         char port[BUF_SIZE];   /* TCP listener port */
         char host[NI_MAXHOST]; /* host name */
     }
    
  2. When you receive a presence message via UDP broadcast, you have the status, name, and port ready to snscanf()
  3. You can get the hostname using getnameinfo() as the given UDP server does.

Quitting the program

  1. If you catch CTRL-D (also known as EOF) when polling STDIN_FILENO, that can stop the poll() loop
  2. You can come up with an alternative, but CTRL-C does not shut down cleanly so you should not use that.
  3. When your program exits cleanly, you can broadcast offline presence and close() all of the pollfd array.