For the upcoming 2 weeks, your task is to implement a client/server application over TCP/IP using sockets API (e.g. STREAM_SOCK).
First you will implement a client which will send requests to the server. Then you will implement the server to process these requests.
- The requests can be of type:
ADD,SUB, andTERMINATION. -ADD/SUBmanipulate a global counter the server manages. -TERMINATIONnotifies the server about a clients termination. -
- The server process stores a global counter and atomically updates it based on the received requests. -
- Along with
ADD/SUBrequests, each request also contains a number that will be added/subtracted from the global counter respectively. - - The
TERMINATIONrequest instructs the server to sent back to the client a message that contains the value of the global counter via aCOUNTERmessage. -- In particular, for each
TERMINATIONrequest the server prints the value of the global counter and sends it over the network via aCOUNTERmessage. - - Similarly, each client thread prints the value that the server has sent back
via the
COUNTERresponse. -
- In particular, for each
- As a serialization protocol, you are encouraged to use
google::protobufs. -- Note that the
.protofiles that describe the message layout are already provided to you. -
- Note that the
In short, ADD/SUB/TERMINATION are from client to server, while COUNTER
is from server to client. -
The following things are to be delivered:
-
Design a network protocol to implement the requirements above. Note that we encourage you to use Google protobufs (see
message.protofor a skeleton). You can add additional members to this message. You are also free to design your own protocol if you want. -
A library
libutils.soimplementing the interface given inutils.h. Please look at the documentation given there. -
Implement the client. It should be callable via:
./client <num_threads> <hostname> <port> <num_messages> <add> <sub>.- The client process spawns a number of threads given as
<num_threads>each of which independently connects to the server. - The client process takes as an argument the address (
<hostname>and<port>) of the server. Note that in the tests<hostname>="localhost" - After establishing the connection with the server, each thread should
send a variable number of messages (taken as
<num_messages>).- Important: The client should alternate between
ADDandSUBrequests, starting withADDuntil it has sent<num_messages> - Important: The client should use the passed
<add>and<sub>command line parameters as arguments for these requests
- Important: The client should alternate between
- Once all messages are sent, each client-thread should send a
TERMINATIONmessage to the server. - Right after the delivery of the termination message, the thread should receive from the server the value of the global counter and should print the received value to stdout.
- Once all the client threads have finished, the client process terminates.
- The client process spawns a number of threads given as
Suppose the client is called like ./client 1 localhost 1025 4 2 1 it should
send ADD 2 SUB 1 ADD 2 SUB 1 and print the counter received from the server
to stdout. Reuse the functionality you have implemented for libutils.so!
For recv_msg and send_msg you will have to make sure that you receive or
send a whole message. However, TCP communication does not distinguish between
different messages. recv system call might return a stream of bytes that
refers to more than one distinct application message. To process the individual
messages, applications should implement their own message's formatting or
serialization protocol.
A common approach is to encapsulate the payload size as follows:
<--size--><--payload-->, where payload is a serialized protobuf
(byte-stream). The size should be a fixed size integer (i.e. uint32_t) The
receiving side can then always read one 4 byte integer, decode the size and
read the specified bytes afterwards.
Note that it's possible that send and recv do not send or receive the
number of bytes passed as arguments. You may have to call them in a loop to
make sure that the whole message has been sent or received.
Implement the server. It should be callable via: ./server <num_threads> <port>.
- The server process initially spawns a fixed number of threads (taken as
<num_threads>, e.g. 2, 4, 8). - Then it should accept connections on a port passed as
<port>and listen onlocalhost - Each server thread should be able to handle multiple connections at the same time.
- The server should be available to accept connections at any time and should not terminate (long running process).
- Once a connection is accepted, the server process should assign this
connection to one of the server threads. You could find the elected thread's
id by dividing the number of connections with the number of the server
threads (
nb_connections % nb_threads). - If a server thread receives a termination message, it should reply back to
the client the current global counter value via a
COUNTERmessage and print the counter to stdout
Reuse the functionality you have implemented for libutils.so! You might want
to use either select(), poll() or epoll() to implement the server
threads.
In the third part of the task, you have to enclose your server application into
a docker container. For this purpose we provide you an initial Dockerfile.
You have to fill it with proper commands that will perform the following:
- Copy the code in the
/app/directory inside the container. Note that this is where the test will look for the server executable. - Build the source code.
- Expose the tcp port 1025. The
docker-compose.ymlfile should remain intact.
Rebuild the source code inside the container. Note that the provided
.dockerignore file excludes compiled objects to prevent build conflicts.
For this first preliminary task, you are not required to have a
fully-functional multi-threaded version of neither the server nor the client
but you are encouraged to do so for the tasks that will follow. For your own
testing purposes of your multi-threaded client-server model, we provide you
with two tests ./tests/test_client_multithreaded.py and
./tests/test_server_multithreaded.py. These tests are not evaluated.
python3: psutil
printf()-based functions are not thread-safe and in the presence of many
clients you might print the correct counter values in the same line. In that
case, the tests in the grading system might fail. We encourage you to wrap the
print-functions with a global mutex to ensure that a message is flushed to
standard output in a specific order. Also you should flush the stream after
writing to stdout using fflush().
You might want to set SO_REUSEADDR on your sockets to allow easier testing.