Submission
Due Date
By Friday 15 February 2019 23:42
Directory Hierarchy
Create your git repository (replace john.smith by your own login).
$ git clone git@git.cri.epita.net:p/2022-s4-tp/tp02-john.smith
It must contain the following files and directories:
-
pw_02_tcp_server/
- AUTHORS
echo_server/
- echo.c
- echo.h
- echo_test.c
- single_server.c
- loop_server.c
- fork_server.c
- Makefile
bc_server/
- main.c
- Makefile
The AUTHORS
file
must contain the following information.
First Name
Family Name
Login
Email Address
The last character of your AUTHORS
file must be a newline character.
For instance:
$ cat AUTHORS
John
Smith
john.smith
john.smith@epita.fr
$ # Command prompt ready for the next command...
Be careful, if you do not follow all the given instructions, no point will be given to your answers.
The Echo Server
The Makefile
For this part, a Makefile is provided. Feel free to use it in order to test the different parts of your code.
# Makefile
CPPFLAGS = -MMD
CC = gcc
CFLAGS = -Wall -Wextra
LDFLAGS =
LDLIBS =
SRC = echo.c echo_test.c single_server.c loop_server.c fork_server.c
OBJ = ${SRC:.c=.o}
DEP = ${SRC:.c=.d}
all: echo_test single_server loop_server fork_server
echo_test: echo_test.o echo.o
single_server: single_server.o echo.o
loop_server: loop_server.o echo.o
fork_server: fork_server.o echo.o
-include ${DEP}
.PHONY: clean
clean:
${RM} ${OBJ}
${RM} ${DEP}
${RM} echo_test single_server loop_server fork_server
# END
The echo()
Function
Provided Files
#ifndef ECHO_H
#define ECHO_H
void echo(int fd_in, int fd_out);
#endif
#include <stdlib.h>
#include <unistd.h>
#include <err.h>
#define BUFFER_SIZE 512
void echo(int fd_in, int fd_out)
{
// TODO
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>
#include <fcntl.h>
#include "echo.h"
int main(int argc, char **argv)
{
// Print mode.
// If one parameter is passed,
// print the contents of the file.
if (argc == 2)
{
int fd = open(argv[1], O_RDONLY);
if (fd == -1)
err(EXIT_FAILURE, "open()");
echo(fd, STDOUT_FILENO);
close(fd);
}
// Copy mode.
// If two parameters are passed,
// copy the first file to the second file.
else if (argc == 3)
{
int fdr = open(argv[1], O_RDONLY);
if (fdr == -1)
err(EXIT_FAILURE, "open()");
int fdw = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (fdw == -1)
{
close(fdr);
err(EXIT_FAILURE, "open()");
}
echo(fdr, fdw);
close(fdw);
close(fdr);
}
// Echo mode.
// Otherwise, copy the standard input to the standard output.
else
echo(STDIN_FILENO, STDOUT_FILENO);
return EXIT_SUCCESS;
}
Implementation
An echo server is a very basic server.
It sends everything it receives back to the client.
So, the first thing you need is an echo()
function.
void echo(int fd_in, int fd_out);
The echo()
function reads some data
from the input file descriptor (fd_in
)
and sends it to the output file descriptor (fd_out
).
Here are some tips to write this function:
-
In an infinite loop:
-
Read some data from
fd_in
. - Use the read(2) function to store this data into a buffer of characters (the size of this buffer should be 512 characters).
-
Store the return value of the
read()
function into the r variable. - When all the data has been read (r = 0), you must break the loop and return from the function.
- If an error occurs (r = -1), you must exit the program with an error message.
-
When some data is in the buffer,
you must send it to
fd_out
. To do so, use the write(2) function. Be careful, sometimes, the number of bytes written could be less than count (the third parameter of thewrite()
function). So, if the returned value ofwrite()
is less than count, you must write again the data that was not written. To do so:- Initialize an offset variable to zero.
-
While r > 0:
- w = write(fd_out, buffer + offset, r)
- If an error occurs (w = -1), you must exit the program with an error message.
- offset += w
- r -= w
-
Read some data from
Write the echo()
function by completing the
echo.c
file.
Testing
Compile your code by using the provided Makefile
and the echo_test.c
file.
$ make echo_test
gcc -Wall -Wextra -MMD -c -o echo_test.o echo_test.c
gcc -Wall -Wextra -MMD -c -o echo.o echo.c
gcc echo_test.o echo.o -o echo_test
The echo_test.c
file allows you to test your
echo()
function in three different ways.
The first way to test your function is to launch the
echo_test
executable file without arguments.
(Your program will then be similar to the cat
command without arguments.)
Then:
- Type any sequence of characters.
- Press the Enter key (your sequence is sent to the standard input).
- The program copies the standard input to the standard output. Therefore, the same sequence is printed again.
- To stop the program, send the EOF character (end-of-file character) by pressing Ctrl‑D.
For instance:
$ ./echo_test
Hello World! # Press Enter
Hello World!
My name is David. # Press Enter
My name is David.
Good Bye! # Press Enter
Good Bye! # Press Ctrl-D (send the EOF character).
$
The second way to test your function is to launch the
echo_test
executable file with one argument.
(Your program will then be similar to the cat
command with one argument.)
For instance:
$ ./echo_test echo.h
#ifndef ECHO_H
#define ECHO_H
void echo(int fd_in, int fd_out);
#endif
The third way to test your function is to launch the
echo_test
executable file with two arguments.
Your program will then be similar to the copy
command.
For instance:
$ ./echo_test echo.h echo2.h
$ ./echo_test echo2.h
#ifndef ECHO_H
#define ECHO_H
void echo(int fd_in, int fd_out);
#endif
Read carefully the echo_test.c
file and try
to understand it.
Single Connection
Provided File
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <err.h>
#include "echo.h"
int main(int argc, char** argv)
{
if (argc != 2)
errx(EXIT_FAILURE, "Usage:\n"
"Arg 1 = Port number (e.g. 2048)");
// TODO
}
Implementation
Now, you have to write the first version of your server. For this first version, the server will be able to connect to only one client.
Your program will take one argument only: the port number.
The code of a server is similar to that of a client.
-
First, you have to initialize an addrinfo structure.
Initialize all of its fields to zeros by using the
memset(3) function
and set only the following fields:
- ai_family should be equal to AF_INET (IPv4)
- ai_socktype should be equal to SOCK_STREAM (TCP)
- ai_flags should be equal to AI_PASSIVE (Server Mode)
- Use the getaddrinfo(3) function to get a linked list of addresses.
-
Then, for each address:
- Try to open a socket by using the socket(2) function. If an error occurs, continue with the next address.
- In order to reuse the same address immediately after closing a connection, you have to set the SO_REUSEADDR to one. To do so, use the getsockopt(2) function.
-
Try to bind the socket to the address by using the
bind(2) function.
- If no error occurs, break the loop.
- If an error occurs, close the socket and continue with the next address.
- Free the linked list.
- If no socket has been open, exit the program with an error message.
- Specify that the socket can be used to accept incoming connections. Use the listen(2) function (the value 5 for backlog will be enough). If an error occurs, exit the program with an error message.
- Print a message saying that your server is waiting for connections.
- Wait for connections by using the accept(2) function. A new connected socket is then created and should be used to read from or write to the client. If an error occurs, exit the program with an error message.
- Print any message showing that a connection is successful.
-
Use the file descriptor of this new socket with your
echo()
function. - Close the two sockets.
Testing
Compile your code by using the provided Makefile.
$ make single_server
gcc -Wall -Wextra -MMD -c -o single_server.o single_server.c
gcc -Wall -Wextra -MMD -c -o echo.o echo.c
gcc single_server.o echo.o -o single_server
Run your server with a port number (greater than 1024).
$ ./single_server 2048
Waiting for connections...
Open a new terminal and use the nc
command as a client in order to connect to your server.
$ nc localhost 2048
If the connection is successful, your server should print a message.
$ ./single_server 2048
Waiting for connections...
Connection successful!
Now, enter some sequences of characters in the client. When you are done, close the client by pressing Ctrl‑C. Your server should then end automatically.
$ nc localhost 2048
Hello World!
Hello World!
Who are you?
Who are you?
^C
Successive Connections
Implementation
Copy single_server.c
to loop_server.c
.
$ cp single_server.c loop_server.c
Modify the loop_server.c
file so that your server accepts
successive connections.
To do so, create an infinite loop that:
- Prints "Waiting for connections...".
- Waits for connections.
- Prints "Connection successful!".
- Echoes the data received from the client.
- Closes the socket returned by
accept()
.
As usual, if any error occurs, exit your program with an error message.
Testing
Compile your code by using the provided Makefile.
$ make loop_server
gcc -Wall -Wextra -MMD -c -o loop_server.o loop_server.c
gcc -Wall -Wextra -MMD -c -o echo.o echo.c
gcc loop_server.o echo.o -o loop_server
Run your server with a port number (greater than 1024).
$ ./loop_server 2048
Waiting for connections...
Open a new terminal and use the nc
command as a client in order to connect to your server.
$ nc localhost 2048
If the connection is successful, your server should print a message.
$ ./loop_server 2048
Waiting for connections...
Connection successful!
Open a new terminal and create a second client.
$ nc localhost 2048
Nothing changes for the server.
$ ./loop_server 2048
Waiting for connections...
Connection successful!
Now, enter some sequences of characters in the first client (do not press Ctrl‑C).
$ nc localhost 2048
Hello World!
Hello World!
Are you a number?
Are you a number?
Then, enter some sequences of characters in the second client (do not press Ctrl‑C).
$ nc localhost 2048
No, I am not.
I mean, I am not a number.
As you can see, the sentences are not duplicated for the second client. Why ? Because, the server is still connected to the first client. So now, let us close the first client.
$ nc localhost 2048
Hello World!
Hello World!
Are you a number?
Are you a number?
^C
Now that the first connection is closed, the server has accepted the second client.
$ ./loop_server 2048
Waiting for connections...
Connection successful!
Waiting for connections...
Connection successful!
And the text of the second client has been duplicated.
$ nc localhost 2048
No, I am not.
I mean, I am not a number.
No, I am not.
I mean, I am not a number.
Finally, close the second client.
$ nc localhost 2048
No, I am not.
I mean, I am not a number.
No, I am not.
I mean, I am not a number.
^C
Your server is now waiting for new connections.
$ ./loop_server 2048
Waiting for connections...
Connection successful!
Waiting for connections...
Connection successful!
Waiting for connections...
The only way to close your server is to press Ctrl‑C. If some sockets have not been explicitly closed by your program, they will be automatically closed by the system.
Multiple Connections
Implementation
The previous version of your server cannot accept multiple connections at the same time. This is what we are going to change in this section.
To accept multiple connections, you will have to create a new process each time a client is asking for a connection.
Copy loop_server.c
to fork_server.c
.
$ cp loop_server.c fork_server.c
You will have to modify the fork_server.c
file
so that your server accepts multiple connections.
To do so, create an infinite loop that:
- Waits for a connection.
- Creates a new process by using fork(2).
-
The parent process should:
- Close the socket returned by
accept()
. - Wait for other connections (i.e. continue the loop).
- Close the socket returned by
-
The child process should:
-
Close the socket returned by
socket()
. - Print the message "New connection (pid = %i)" with the pid of the child process.
- Echo the data.
-
Close the socket returned by
accept()
. - Print the message "Close connection (pid = %i)" with the pid of the child process.
-
Exit the process.
Be careful, when the child process ends, it becomes a zombie.
You should catch
SIGCHLD
to solve this.
-
Close the socket returned by
As usual, if any error occurs, exit your program with an error message.
Testing
Compile your code by using the provided Makefile.
$ make fork_server
gcc -Wall -Wextra -MMD -c -o fork_server.o fork_server.c
gcc -Wall -Wextra -MMD -c -o echo.o echo.c
gcc fork_server.o echo.o -o fork_server
Run your server with a port number (greater than 1024).
$ ./fork_server 2048
Waiting for connections...
Open a new terminal and use the nc
command as a client in order to connect to your server.
$ nc localhost 2048
If the connection is successful, your server should print a message.
$ ./fork_server 2048
Waiting for connection...
New connection (pid = 12717)
Open a new terminal and create a second client.
$ nc localhost 2048
Your server should detect this new connection.
$ ./fork_server 2048
Waiting for connection...
New connection (pid = 12717)
New connection (pid = 12773)
Now, enter some sequences of characters in the first client (do not press Ctrl‑C).
$ nc localhost 2048
Hello World!
Hello World!
Are you a number?
Are you a number?
Then, enter some sequences of characters in the second client (do not press Ctrl‑C).
$ nc localhost 2048
No, I am not.
No, I am not.
I mean, I am not a number.
I mean, I am not a number.
This time, the text of the second client has been duplicated. Now, close the first client.
$ nc localhost 2048
Hello World!
Hello World!
Are you a number?
Are you a number?
^C
The disconnection of the first client has been detected by the server.
$ ./fork_server 2048
Waiting for connection...
New connection (pid = 12717)
New connection (pid = 12773)
Close connection (pid = 12717)
Finally, close the second client.
$ nc localhost 2048
No, I am not.
No, I am not.
I mean, I am not a number.
I mean, I am not a number.
^C
The disconnection of the second client has been detected by the server, which is now waiting for new connections.
$ ./fork_server 2048
Waiting for connection...
New connection (pid = 12717)
New connection (pid = 12773)
Close connection (pid = 12717)
Close connection (pid = 12773)
The bc Server
The bc
command allows you to perform calculations.
Try it without parameters (type "quit" to exit the program).
Also, this command can read the standard input and write the result to the standard output (or to the standard error). For instance:
$ echo 50/2 | bc
25
$ echo 10/0 | bc
Runtime error (func=(main), adr=6): Divide by zero
The goal of this section is to write a server that sends the client the result of an operation. For example, if the client sends the server "40 + 2", the server will send back "42".
To perform an operation,
the server will use the bc
command.
You can execute this command by using the
execlp(3) function.
As the bc
command uses the standard streams
(stdin, stdout and stderr),
you just have to redirect the file descriptor of the connection socket to those of the standard streams.
This way, everything sent by the client will be automatically sent
to bc
and vice versa.
To do so, you can use the
dup(2) function.
About the implementation:
-
Keep the same structure of your echo server (
fork_server.c
). -
Remove the
echo()
function (you no longer need it). - Your bc server should not display any messages. For instance, you must not print any line indicating a successful connection or the pid of a child process.
-
You must provide two files only:
- main.c
- Makefile
Here is an example of the expected result.
$ make
gcc -Wall -Wextra -MMD -c -o main.o main.c
gcc main.o -o main
$ ./main 2048
Create a first client. Send some text (but do not exit).
$ nc localhost 2048
84/2
42
Create a second client. Send some text and exit.
$ nc localhost 2048
30+12
42
10/0
Runtime error (func=(main), adr=6): Divide by zero
10*2
20
^C
Send some new text from the client 1 and exit.
84/2
42
15*5
75
7/(3-2-1)
Runtime error (func=(main), adr=13): Divide by zero
^C
Note: Instead of the bc
command,
you can execute any command that uses the standard streams.
For example, if you replace bc
by cat
,
your bc server will become an echo server.