This lab should be done with your
lab 2 partner
Lab 2 Goals:
- Learn socket programming by implementing two
version of a TCP socket client-server program.
- More pthreads and synchronization.
- C strings.
- Implement a specific message protocol, and test
correctness by seeing that your client/server can
communicate with other group's server/client.
Make use of the CS87 piazza
page for questions and answers about socket programming,
C strings, pthreads, Unix utilities. If you have taken Networks, please help
your classmates who have not with socket programming questions.
Contents:
Lab 2 Overview
The main focus of this lab is learning client-server socket
programming and implementing a specific message protocol for
clients and servers to talk to each other.
You will implement two versions of a chat or talk client and
server program to allow users on different machines to talk.
You will be given a protocol for
how clients and servers communicate, and your solution must
implement the protocol such that your clients and servers
can communicate with other group's.
In this lab you will implement at least 2 different versions of the
client and server.
- The Part 1 version: allows for one-at-a-time chat session between a
single client on one machine and a single server on another machine.
When the current chat session ends, another client can connect to
the server end and start a new chat session. The client and server
alternate sending messages back and forth (in a question and answer type
style).
The Part 1 solution is due in one week. I encourage
you to finish this part early and get started in on Part2.
- The Part 2 version: allows for a chat session between multiple clients.
In this version the server does not participate in the chat itself, but
fosters allowing multiple clients to connect to the chat session. Clients
participating in the group chat send their message to the server and the
server shares it with all of the other clients participating in the chat
session. In this version, clients can "speak" at anytime; there is no
specified ordering or taking turns writing messages to the group.
Individual clients can also come and go in a chat session.
The Part 2 solution is due in two weeks.
- The Part 3 version (optional for non-NW groups, required for NW-groups):
impelement the "Vote to let Join" chat server. With all the NW-groups, you will
first develop
a voting protocol that will allow all particpants currrently participating
in a talk session decide if a new client can join in on the coverstaion.
Defining a protocol means that agreeing on the message sequence
for a "voting to let join" protocol--every lab group in the class must
implement the same messaging protocol. Your
individual server implementations can use their own voting algorithm
(majority, at least one "yes", ...) to determine if a
new client requester gets to join in on the coverstaion it is hosting, but
all groups voting protocols (communication between clients and the server
hosting) to make this decision must match. The result should be that clients
and servers from different groups know how to participate in a voting protocol
when a new client requests to join a chat session hosted by a server.
The Part 3 solution is due in two weeks.
This assignment is very loosely based on
Unix
talk,
which was a very early Chat or IM system.
(Note that you will NOT be implementing the Unix
talk protocol in this lab; instead you will implement a different protocol
that has some similar general functionality.)
Lab 2 Starting Point Repo
- Both you and your partner should:
- Create a cs87/labs subdirectory on the CS system:
mkdir cs87
mkdir cs87/labs
cd cs87/labs
- Get your Lab02 ssh-URL from the GitHub org for our class:
cs87-s20
- On the CS system, cd into your cs87/labs subdirectory
- Clone a local copy of your shared repo in your private
cs87/labs subdirectory:
git clone [your_Lab02_URL]
Then cd into your Lab02-you-partner subdirectory.
If all was successful, you should see the following files when you run ls:
Makefile README.md client.c cs87talk.c cs87talk.h server.c
If this didn't work, or for more detailed instructions on git see:
the Using Git help page.
Starting Point files
With the starting point are several files, many have some starting point
code written for you. These files include:
Message Protocols
Messaging Protocols define how two parties communicate--the protocol
defines both the set of application-level (higher level) messages,
the send-recv exchange in handling types, and the expected
format of messages.
For this lab you will implement three different high-level message
protocols as part of the cs87talk protocol. The three are:
- HELLO: a client introduces itself to the server before talking. The
server will tell the client if it can join the chat or not as part of this
exchange.
- MSG: an endpoint will send a message to another endpoint. A client
can send a MSG to a server and a server can send a MSG to the client.
- QUIT: a client tells a server it is done and leaving the talk
session.
Each part of the protocol is begun by one party sending to the other party
1 byte message tag. The tag value is used to indicate which of the
three messaging protocols the client and server should run (the
expected send-recv sequence between the client and server for the
interaction type is indicated by the tag).
Protocols Definitions:
And see the
cs87talk.h file for type and constant definitions
that you should use in your implementation of these.
- HELLO: this protocol is initiated by the client when it
wants to start a talk session with a server (the client should initiate
this immediately after the TCP connection has been established and
before trying to send talk messages to the server). The send-recv behavior
is defined by:
client server
====== ======
1. send HELLO_MSG tag -------------> recv 1 byte tag
2. send len -----------------------> recv 1 byte len
3. send name ----------------------> recv a len bytes name string
4. recv 1 byte tag <---------------- send 1 byte reply tag
(HELLO_OK or HELLO_ERROR)
The client sends 3 values to the server in this order: the HELLO_MSG tag
as a 1 byte value; a 1 byte len value, which is the number of bytes in the
name string; and a len bytes message containing the name string (passed
as a command line argument to the client program). The client then receives
the 1 byte tag response from the server, indicating if the server is letting
it join the talk session.
The server will terminate the connection after it replies HELLO_ERROR.
If it replies HELLO_OK, it will update state about this connection,
and create a message buffer for this client with the client's name string
as a prefix. All messages set by this client will be prefixed by the
string "name:" to mark that this message was "said" by this client.
- MSG: send a message string from a client to a server or from a
server to a client:
initiator other end
========= =========
1. send MSG_DATA ------------------> recv 1 byte tag value
2. send len -----------------------> recv 1 byte len value
3. send data ----------------------> recv len byte message
If the server is the "other end" it should store the received message in
a string with the a "name:" prefix (the name is obtained from the HELLO
protocol) to identify the sender. For example, if the server is communicating
with Frey, and Frey initiates a MSG_DATA message, sending "hello there"
as the data part, the server will store the received message in a string:
"Freya: hello there".
If the client is the "other end" it just receives the message of len bytes and
creates a string from the len byte message (null terminate '\0' before
printing out); the client side doesn't add a prefix to received messages.
For example if the server sends the client a MSG_DATA message, with
"hello there" as the data part, the client will store the recived message
in a string "hello there".
- QUIT: when the client wants to drop out of the chat session
it sends the QUIT message tag to the server:
client server
====== ======
1. send QUIT -----------------------> recv 1 byte tag value
After sending QUIT, the client should close its end of the socket and exit.
The server then cleans up any state associated with this connection, including
closing its end of the socket that was dedicated to this communication.
After receiving a QUIT, the server process should not exit.
- For Part 1, it should be ready to accept a new client connection
for the next chat session.
- For Part 2, this client has just left the group chat.
Part 1: One Client at a time talks with a server, alternating Client-Server Talk
In the first version of this assignment you will implement a client and
server program that implement the messaging protocols described above.
In this version, a client will connect to a server, and then the client
and server will talk back and forth, each taking turns sending a message
to the other. The server will have a dedicated talk session with this
client until the client terminates the connection (either it closes its
end of the socket or it sends a QUIT message). The client should exit
after this, but the server should not exit, instead it is now available
for another client to connect to it to have a talk session.
The behavior should be:
- client connects to a server using the TCP protocol. Run the
client with the server's IP and and the client's chat name as command
line args:
./cs87_client 130.58.68.67 tia
- the client initiates the HELLO protocol with the server
- if the server replies HELLO_ERROR, the client exits (no talking will take place)
- if the server responds with HELLO_OK, then the client and server will start a talk session
- the client and server will have a talk session that consists of some
number of alternating message exchanges. the first MSG should be sent
from client to server, then next from server to client, then by client to
server, and so on.
- the talk session is ended by either the client sending a QUIT message
or the server detecting that the client's end of the socket has been closed.
An individual message in (3) should be a line of texted entered by the
user running the client (or server) side. I suggest using readline to
read in a line of user input and return it as a string (and remember you
are responsible for freeing the return string when done with it...run valgrind).
Sample Output
Here is sample output from two back-to-back
talk sessions with a talk server. Your client and server program can print
out any prompts and messages to the user than mine, but they should have
this same alternating behavior and the server should handle only a
single client connection and talk session at a time. Only after the
current talk session ends, can the server accept the next talk session
from another client (note where that is in the server output).
Requirements
- Use the TCP sockets client-server connection protocol.
Here is a picture of the TCP client-server connection protocol:
- Write your solution in C, using the starting point code.
- Use the definitions in cs87_talk.h in your program. For example,
use the type tsize_t rather than unsigned char, and use the #defines
for message tags:
tsize_t tag;
tag = HELLO_OK;
ret = send(socket, &tag, sizeof(tsize_t), MSG_NOSIGNAL);
- For Part 1, the server should never reply HELLO_ERROR (in Part 2
this is an option).
- Make sure to close sockets when done or when an error is detected.
- Your solutions should be robust. Check
return values from all system calls, and handle appropriately.
- Use send and recv functions to send and
receive messages on the TCP socket. For recv the flags field should be 0,
for send pass MSG_NOSIGNAL to avoid getting sent a SIGPIPE signal when the
other end of the connection closes its end of the socket.
- You should set the socket options SO_LINGER to off and SO_REUSEADDR on
the server's listen socket (see Help Useful Utilities).
- Any message read in from the user that is longer than BUFMAX should
be truncated before being sent (make sure a sender never sends a message
greater than or equal to BUFMAX bytes). Similarly for the NAMEMAX limit.
(note: BUFMAX is currently defined to be 256, thus it is not possible to
send a message size value that is larger than 255 (since message size
is represented in a single byte). However, your code
should work regardless of what BUFMAX is defined to be, up to 256.
For example, if it is redefined to be 32, your code for checking and
only sending up to BUFMAX bytes should still work.
- You should define a struct on the server side for keeping track of
a client's information (this will be necessary for Part 2). The struct
should minimally hold:
- the client's socket file descriptor (returned by call from accept).
- a buffer for the client's message. You can statically declare an
array of size BUFMAX. I suggest copying the client's "name:" to the
front of this buffer so it will be a prefix of all messages from this
client.
This means that a client message length plus the name prefix length
longer than BUFMAX will result in the client message being truncated to
fit in this
buffer (so the max message size will be smaller than BUFMAX if you do
this). For example, if the prefix is "tia:" then there are 4 fewer
characters
for a message. If the prefix is "kevin:" then there are 6 fewer. You
should also leave the last bucket in this buffer for storing a null
terminating character for the string '\0'. '\0' shouldn't be passed in
the message.
- where the start of buffer is for message data (after the name prefix)
- Your client should be able to connect to another group's server and
have a chat. Your server should be able to accept a connection from
another group's client and have a chat. We will test in lab next week.
- You should check return values from all system and function calls
and handle errors. For system calls (like send, recieve, socket, etc.),
you should call perror to print out an error specific message.
Here is one example of how I might call perror and exit if a call to
setsockopt fails:
int reuse_true = 1;
retval = setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse_true,
sizeof(reuse_true) );
if (retval < 0) {
perror("Setting socket option SO_REUSEADDR failed");
exit(1);
}
These are NOT required, but are some ideas for extra things to add
to make your chat program a little more user friendly:
- When a new client connects to a group chat session, the server send all
other clients a message saying that they have joined (like "Freya has joined
the chat").
- When a client leaves the group chat session, the server sends all other
clients a message saying that the user has left the chat session (like "Freya
has left the chat")
- When a client sends a chat message to the server, the server sends the
message to every other client except for the client whose message it is. For
example, if Jo, Flo, and Bo are in a chat and Bo types in a message, the
server sends Bo's message just to Jo and Flo.
- You could add a messaging protocol for a client to ask the server for all
the names of people in the chat session. The user on the client side would
have to type in a specific string to trigger this protocol (like typing in
"goodbye" triggers the QUIT protocol), this would send a specific message tag
to the server that would have it send back to the client the names of all
other chat members.
- You could add a messaging protocl for a client to ask the server to send
it all of the last messages from each chat member. Again, the user on the
client side would trigger this by entering some unique string to distinguish
this request from a message to share with other clients in the chat session.
- if you want to try to implement a solution with nicer looking output,
you can try using the ncurses library to split the terminal window and
direct stdin to one half and stdout and stderror to the other half (this
is not trivial to do). You could also try something easier and print out
stdout output in a different color if it is coming from the prompt printing
thread vs. from the thread that receives and prints out messages
from other clients forwarded from the server. Here is some
information about ncurses: Cool C libraries.
Help and Useful Utilities
Sockets and IP info:
- See
Socket Programming Links. Beej's Guide is a good staring point and has code examples (sections 5 and 6 are particularly useful).
You should use send and recv to send and receive
messages on sockets.
NOTE: send and recv should be called
in a loop until all the bytes of the message have been received. If
a call to send or recv returns with fewer bytes than were send/recived, you
need to call again to send/recvie the remaining bytes of the message, starting
from within the buffer you are sending/receiving.
For example, you
can specify the next point to receive in a buffer by passing recv the
address of the bucket:
ret = recv(sock, &(buf[i]), amt_left, 0);
Also, Steven's Unix Network Programming (2nd Edition Vol. 1).
Chapters 3 and 4 are very helpful. A copy of this book is in the CS Lab.
- set sockopts on the listen socket: SO_REUSEADDR & SO_LINGER
If you set the sockoption SO_LINGER to off, then the socket will
close immediately upon a process exit:
struct linger linger_val;
linger_val.l_onoff = 0;
linger_val.l_linger = 0;
setsockopt(sock_fd, SOL_SOCKET, SO_LINGER, (void *)(&linger_val),
(socklen_t) (sizeof(struct linger));
This is done for you already in the starting point code.
- read the man pages system calls like socket, connect, accept, listen, send, recv, etc.
man 2 socket
Note RETURN values and be sure to check for error returns and handle
appropriately in your code. You should use perror to print
error return messages from system calls like these, and printf for
not system call error return values (like error return values from function
you write).
- ifconfig to get your own IP address
$ /sbin/ifconfig # in the eth0 entry its "inet addr:"
eth0: flags=4163 mtu 1500
inet 130.58.68.165 ...
- nslookup or dig to get another machines IP address
$ nslookup lime
Address: 130.58.68.165
$ dig +short lime.cs.swarthmore.edu
130.58.68.165
C Programming, strings, pthreads, readline library:
Other Resources:
Part 2: Multi-client Chat
The Part 2 talk server changes the role of the server, allows multiple
clients to simultaneously connect to the same server to all participate
in a group chat, allows clients to send messages to others at any time
(there is no predefined order or alternating behavior). In addition,
new clients can enter an existing chat at any time, and current clients
can leave at any time, and the chat continues on.
Requirements
- The server will allow multiple clients to be connected at once.
- The server is no longer a participant in the chat: it just facilitates
some number of clients to have a group chat: the server doesn't read in
input from a user, nor does it echo any of the messages it receives from
clients. Its only output can be a message when a new client enters the
chat and when a client leaves (or a connection ends).
- The server should be multi-threaded: on each client connection
(accept), the sever should create a new thread dedicated to that
connection; when the client quits, the thread should clean-up any server
state associated with its client, and then the thread should exit.
- The main server thread, after an accept and starting a worker thread
for the connection, should go back to the main accept loop ready to
accept another connection from another client.
- The server should accept no more than 10 simultaneous client connections.
If there are 10 clients connected and a new one tries to connect, the
server thread should reply HELLO_ERROR to this client and terminate the
connection. When a connected client leaves, then the next connect request
should succeed. #define some constant for this max size (10)
- When a server thread receives a message from its client, it will
send the message to all other clients.
- The message sent will have the client's name prefix
- The server should keep an array of structs of client info type you
defined in Part1 so that
the server thread can send a message on all client's sockets. Think about
if you need any synchronization.
- The client should be multi-threaded: one thread handles receiving MSG
from the server, the other handles sending messages read in from the user
to the server. sockets are bi-directional communication channels, which
means that one client thread can send a message on the socket to the
server, while another thread receives a message from the server on the
same socket.
- Clients can connect and disconnect at any point
- Clients can send messages whenever they read in a line entered by
the user: there is no message ordering.
Sample Output
Here is sample output from a multi-client
chat session. Note that the server is not a participant in the chatting,
but instead forwards a message from one client to all others (and note the
name prefix). Also note that clients enter and leave and the chat continues.
A note about client-side I/O
Because you will have mulitple client threads sharing the same stdin and
stdout, you will see messages from other clients being printed out in the
same terminal window as the prompt and the input message the human user is
typing in. This is fine.
Part 3: "Vote to let Join" mulit-connection Chat Server
This part is required for the lab2 partners who have taken a
Networking course, and optional for the groups who have not.
For this part, you are going to add a new version of the HELLO protocol
to your Part2 solution.
When a new client wants to connect to a chat session (via the HELLO protocol),
the current set of clients will decide if they are going to let the
new client join the chat session or not.
If the current set of clients in the chat choose NO, then the server will
reply with HELLO_ERROR and not let the new client join. If the current
set of clients choose yes, then the server will reply with HELLO_OK and the
new client can join in the group chat.
Protocol for voting in a new chat member
All of the groups together will need to come up with a protocol for the
server and the clients currently in the chat to make a decision.
Your server/client should work with any other client/server that implements
this voting to join protocol.
For this part, develp the messaging protocol between the server and
clients in the chat to communicate a voting exchange. Your groups
should agree on just the message protocol: what are the new tags, and
what is the expected send-recv behavior for this protocol. This should
be like defining the HELLO or MSG or QUIT protocols.
This protocol should be VERY simple. This is just the agreed
message exchange between the server and clients already participating in
a chat. It should be similar to one of the existing protocls like
HELLO or MSG. If it starts to get longer than about 2x any of the
current ones, step back and simplify.
In addition to message tags involved in this protocol, specify the type
and size of any associated data sent. For example if you send the value
numeric value 10, are you sending it as a 1 byte value, a 2 byte value, a
4 byte value, ... it is important to ensure that receiver and sender
agree on the same number of bytes of the message sent and received.
Group's particular voting algorithms can differ (what they decide with
the set of clients' yes and no votes), but the messaging protocol
used by client and server processes should be the same;
your group may choose any algorithm for tallying a result, but your
group's client should know how to interpret a voting message request from
another group's server, and your server should be able to have a
voting interaction that includes another group's client program.
Publish your agreed upon protocol on piazza (you can create
a new page for the Lab2 Voting Protocol off the top-level class wiki page.
Use the chain looking link to do this).
Some Advice: in designing the protocol try very hard to separate
out the protocol design from how it will be implemented. For example,
worrying about race conditions or deadlock or clients exiting are all
implementation issues and should not be part of the protocol (other than
ensuring that your msgs send-recvs in the protocol are not a deadlock).
Focus solely on the message exchange between clients and servers to initiate
a voting session and to collect votes.
Similarly, the particular voting algorithm is not part of the protocol.
You do not need to agree on a voting algorithm; one group
could choose a majority algorithm of the client's votes, another
could choose a random vote, anther could do something else with the
clients' votes.
Some requirements (and not)
- Your group's Part 3 client/server should be able to participate in
a vote with other group's Part 3 server/client. Hence the voting protocol.
- You should think about, and handle, clients that exit during a voting
event.
You do not need to support any number of simultaneous voting of letting
in new clients. For example, if a second new client tries to join
the chat during the voting decision for a current new client, it is fine
to serialize the voting process: first complete the voting decision about
the first new client, then start the voting process on the second new client.
Submit
Before the Due Date,
one of you or your partner should push
your solution to github from one of your local repos to the GitHub
remote repo. (it doesn't hurt if you both push, but the last
pushed version before the due date is the one I will grade, so be
careful that you are pushing the version you want to submit for grading):
From one of your local repos (in your ~you/cs87/labs/Lab02-partner1-partner2 subdirectory)
git add *.c *.h
git commit
git push
Also TAG your commit: because you are going to
implement your Part 2 (and Part 3) in the same repo as your Part 1, you
should add a git tag to your submitted solution to a particular part.
To do this:
# list all current tags associated with a repo
git tag -l
# create a new tag named Part1 with a tag message (optional)
# and share a tag: push the tag to the origin to share it:
git tag -a Part1 -m "soln to Part1"
git push origin Part1
# checking out a specific tagged version of the code
# (checkout Part1 version of code into a local repo named my_part1)"
git checkout -b my_part1 Part1
# to list other data along with the commit for a specific tag
# (this will show the commit number, date and who created the tag):
git show Part1
You must name it exactly Part1 (and Part2 for part2,
and Part3 for part3)
otherwise your professor will be very sad.
If you have git problems, take a look at the "Troubleshooting" and "Advanced
Features" sections of the
Using git
page.