2015:03:23:chatserver
Sommaire
Chatroom server
This week, you are going to simulate a chatroom.
Contact
Feel free to contact us with questions.
- rebisc_r (Robin Rebischung) rebisc_r@epita.fr
- visoiu_f (Francis Visoiu Mistrih) francis@epita.fr
You must specify your group and login whenever you want to reach us.
Submission
You have to submit on the official repository (login_x-chatserver
) before Sunday 29th of March 2015 23h42.
$tree . ├── AUTHORS * ├── Makefile * ├── README ├── src * │ ├── server.c * └── tests
Files marked as * are mandatory.
NOTES (!!!)
- You must provide a valid
Makefile
that contains a rule namedserver
which will create a program namedserver
at the root of your repository.
$ make server
should compile your submission.
- Your
main()
function must be located insrc/server.c
. This doesn't mean it's the only file you'll have. - Any bonus, as small as you think it is, will be rewarded. Please write it down into the
README
file.
man(1)
When you see the name of a function with its man page number, it means you HAVE to read the manual.
Error handling
We don't expect you to check for errors and handle them properly for every function.
Whenever you see a level with the symbol (!!)
, it means we're expecting you to handle the errors.
To handle errors, use perror(3) (when possible) and exit your program with status code 1.
When perror(3) can't be used, print a custom message on stderr and exit with status code 1.
Documentation
- Review the slides from your programming lecture. It's going to be a great help.
- Don't hesitate to check out Beej's guide. http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html
socket(2)
As you might expect, you are going to use sockets for the server as well, since a socket has to be open between a client and a server.
getaddrinfo(3)
To create your socket, use getaddrinfo(3) to get informations in order to create a socket. You will need to read the manual carefully for this one.
bind(2)
Your server is going to listen for connections on a specific port. It's called binding a port. This means you're reserving a port for your application, and you're matching all the packets incoming on the port to a certain process's file descriptor.
Ports represent services. Some examples:
- 80 : HTTP
- 22 : SSH
- 23 : telnet
This means, you won't have permission to bind ports that are already reserved. Stay away from ports <= 1023
NOTE
You should set the option SO_REUSEADDR
on your socket using setsockopt(2)
to avoid binding errors. (if your system didn't unbind the port yet)
listen(2) & accept(2)
As you noticed for the chat client, the server is waiting for the client to connect.
That's when listen(2) becomes useful. This system call allows you to listen for incoming connections on a socket.
These connections need to be accepted using accept(2).
send(2) & recv(2)
Last week, you used write(2) and read(2) to perform operations on a socket.
This week, we'll introduce you send(2) and recv(2). The only difference is that send(2) and recv(2) can only be used on a socket, and they allow you to set some options using flags.
Feel free to use write(2) and read(2) if you want.
NOTES
- send(2) can send less data than the length you told it to send. That is because your system decided it can't handle that much data. IT'S YOUR DUTY TO SEND THE REST OF THE DATA.
- Don't forget to close(2) everything. All the resources you reserve, must returned to the system.
nc(1) (netcat)
Netcat will be the tool that will allow you to test your server.
The server
The server is going to be divided in multiple levels.
The sequence of events
Here is what your server has to do:
- User connects to the server
- The server asks the user to identify
- The user identifies using
IDENTIFY login_x\n
- The user can send messages to the server. (
DISCONNECT
disconnects the user) - The server should show the messages sent by the specific user.
Level 1 - Setup (!!)
$ ./server 1337
This command should launch the server on the port 1337.
Level 2 - Listen (!!)
In this level, you have to listen for new connections and accept them.
Level 3 - Handle accepts (!!)
In this level, whenever a user connects to the server, you have to print the following message on stdout
:
Connection accepted\n
Level 4 - Users
In this level, whenever a user is accepted, an identification is going to be required.
You will read a list of logins from a file called logins.txt
. The file will have the following format:
$ cat -e logins.txt login_x$ visoiu_f$ rebisc_r$ burell_m$
The user needs to identify using the following command:
IDENTIFY login_x\n
If the login exists in the file, the following message should be printed on stdout
:
<login_x> successfuly identified\n
If it doesn't exist, close the connection by sending the following message to the client:
ACCESS DENIED\n
NOTE
You have to remove the Connection accepted\n
message from the last level.
Level 5 - Messages
In this level, you are going to receive messages from the client.
If the message is
DISCONNECT\n
, you have to close the connection with that user. The following message should be printed onstdout
:<login_x> disconnected\n
Any other messages should show on
stdout
using this format:<login_x> <message>\n
Example:
<visoiu_f> YO\n
Full example:
$ ./server 1337 | $ nc localhost 1337 | Welcome. Please identify. | IDENTIFY visoiu_f <visoiu_f> successfully identified | | Connected. Type DISCONNECT to close the connection. | YO <visoiu_f> YO | | DISCONNECT <visoiu_f> disconnected | $
Level 6 - Multiple connections - basic
As you noticed, your server accepts one connection, handles it, then accepts a new one ONLY when the current connection is closed.
This is happening because accept(2), send(2) and recv(2) are blocking the program.
So we can try and solve this in a naive way, by using fork(2).
For this level you have to :
- Accept a connection
- Fork, handle it
- Exit the forked process when finished
- Keep listening and accepting new connections while the current connection is being handled
Zombies
Be careful! When forking, you can easily generate zombies. Your program should be zombie-free.
When a child process exists, it sends a SIGCHLD signal to its parent. If there is no process waiting for the child to finish, it becomes a zombie. (an easy way to check it is using htop)
In order to avoid that, we are going to catch that signal.
The following code is using sigaction(3) to catch the signal and handle it using the function my_handler
.
struct sigaction sa; sa.sa_handler = my_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGCHLD, &sa, NULL);
my_handler is a function that will be called if a SIGCHLD signal is caught.
In our case, we want to eliminate all the child processes. The following handler is going to wait for any child process.
void my_handler(int s) { while(waitpid(-1, NULL, WNOHANG) > 0); }
NOTE
sigaction(2) is not the best way to handle signals. If you want, you can use signalfd(2) for better signal catching (Linux-only).
Level 7 - Multiple connections - select(2)
So, forking in order to allow multiple connections works pretty well.
But that's not as efficient as we want it to be (try a thousand of simultaneous connections).
One solution to this is select(2). This syscall allows you to monitor several sockets at the same time. It will directly tell you which one are ready for reading or writing.
For this level you have to :
- Remove the fork from your program
- Use the fd_set in order to store file descriptors and look for events
- Use select(2) to get notified if any of the sockets is ready
NOTES
- Be careful with clients that closed the connection on their end. You have to remove their socket from the set manually.
- Read about select(2) on Beej's guide for a more specific explanation.
- Please note that select(2) is far from being the best way to solve this problem. You may want to take a look at poll(2) and epoll(7) (kqueue(2) on BSD).
Bonus
WARNING: Please make sure the bonuses that you add don't modify the behaviour of the mandatory part of the subject. You can use the command-line arguments to enable your personal options.
Example:
$ ./server 1337 --gui
This would launch the server with an user interface. Don't forget to add documentation in your README
file, so we can evaluate you properly.
You can add a lot of bonuses to your server:
- Add a timeout on the connection
- Add a password identification
- Full error handling
- Graphic interface
- Manage multiple rooms
- Manage private messages
- Create a full server working with the client you did last week
- Use threads wisely
- If you have any other interesting ideas, we will be happy to hear them