Practical Programming David Bouchet
Practical Work #2

TCP Server

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:

The AUTHORS file must contain the following information.

AUTHORS
First Name
Family Name
Login
Email Address

The last character of your AUTHORS file must be a newline character.

For instance:

AUTHORS
$ 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
# 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

echo.h
#ifndef ECHO_H
#define ECHO_H

void echo(int fd_in, int fd_out);

#endif
echo.c
#include <stdlib.h>
#include <unistd.h>
#include <err.h>

#define BUFFER_SIZE 512

void echo(int fd_in, int fd_out)
{
    // TODO
}
echo_test.c
#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:

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:

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

single_server.c
#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.

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).

Server
$ ./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.

Client
$ nc localhost 2048

If the connection is successful, your server should print a message.

Server
$ ./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.

Client
$ 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:

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).

Server
$ ./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.

Client 1
$ nc localhost 2048

If the connection is successful, your server should print a message.

Server
$ ./loop_server 2048
Waiting for connections...
Connection successful!

Open a new terminal and create a second client.

Client 2
$ nc localhost 2048

Nothing changes for the server.

Server
$ ./loop_server 2048
Waiting for connections...
Connection successful!

Now, enter some sequences of characters in the first client (do not press Ctrl‑C).

Client 1
$ 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).

Client 2
$ 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.

Client 1
$ 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.

Server
$ ./loop_server 2048
Waiting for connections...
Connection successful!
Waiting for connections...
Connection successful!

And the text of the second client has been duplicated.

Client 2
$ 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.

Client 2
$ 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.

Server
$ ./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:

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).

Server
$ ./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.

Client 1
$ nc localhost 2048

If the connection is successful, your server should print a message.

Server
$ ./fork_server 2048
Waiting for connection...
New connection (pid = 12717)

Open a new terminal and create a second client.

Client 2
$ nc localhost 2048

Your server should detect this new connection.

Server
$ ./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).

Client 1
$ 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).

Client 2
$ 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.

Client 1
$ 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.

Server
$ ./fork_server 2048
Waiting for connection...
New connection (pid = 12717)
New connection (pid = 12773)
Close connection (pid = 12717)

Finally, close the second client.

Client 2
$ 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.

Server
$ ./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:

Here is an example of the expected result.

Server
$ 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).

Client 1
$ nc localhost 2048
84/2
42

Create a second client. Send some text and exit.

Client 2
$ 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.

Client 1
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.