OsGate.org Logo

Write an echo server with C - inetd c programming tcp udp network

Script and coding Script and coding

Date 05.03.2012

Visits 5823

"This OpenCont will explain how to write a small echo server for both TCP and UDP. The server is written in C and it uses sockets to communicate with the client."

Variables declaration

This first section will birefly explain the variables that have been used for this small echoserver.

/** Variables declaration **/
int rc, numberOfLines, addrlen,fd_in_two, fd_in, mode;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char buf[100], hostname[10];
char send[] = "Thank you for registering\n";
FILE* log;
pid_t pid;

The first "rc" will hold the return code of functions so we can check if them are sucesfully executed or not, "numberOfLines" is used to print the number of messages received. The last four, "addrlen", "fd_in_two", "fd_in" and "mode" are related to network settings that our echoserver will have. "addrlen" will be used to pass the size of the sockaddr_in struct used later in the code. "fd_in_two" and "fd_in" are the file descriptor returned by the socket() functional call. The last one, "mode", will specify which protocol the server must use. When the value is 1, the echoserver will use TCP when 0 it will use UDP.

At the lines 17 and 18, we find the two sockadrr_in structures (http://www.beej.us/guide/bgnet/output/html/multipage/sockaddr_inman.html) used to store the network parameters for a host, like port number or IP address.

"buf" is an array of charaters that will store an incoming message. Note that the maximal size of a message is 100 (without new line character). "hostname" will hold the hostname of the local machine and "send" is the string that will be sent for every incoming message.

"log" will be the file variable used to write log messages and "pid" is the process id of the echoserver during its execution.

Variables assignement

After the declaration we want to assign some values to our variables so we do the following:

/** Some variables assignement **/
numberOfLines = 0;
log = fopen("log.txt", "a+");
pid = getppid();

//make sure that the server_addr struct is clean
//then populate it with the different parameters
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(1111);

We can see how "log" is initialized, in fact by using the "a+" parameter in the fopen() function, we want that the file "log.txt" will be opened for reading and appending lines at the end of the file, so we don't want any previouos line to be deleted.

The last assignement, involves the server_addr structure. This variable is of type sockaddr_in and we want to be sure that before any assignement it's clean. We do this by using memset() that zeroes the content of the variable. Later we specify the address family, AF_INET for IPv4, the listening address (INADDR_ANY means to listen on any address) and the port of our echoserver, that is 1111. Please note how htons() and htonl() are used in order to avoid endianness problems.

Logic

Now it's time to look at the program's logic, so we will see the strucutre of the echoserver and how it works.

/** Logic **/

//check the parameter
if(argc != 2){
printf("Use "tcp" or "udp" as parameters.\n");
   return 0;
}

//set the mode
if(strncmp(argv[1],"udp",3) == 0)
    mode = 0;
else if(strncmp(argv[1],"tcp",3) == 0)
    mode = 1;
else
    return 0;

This first two conditional if statement are only used to perform some checks and to set the mode variable. In the first if we check if the prameter is passed to the server and in the second if, we check if the argument matches "udp" or "tcp" and we set mode accordingly.

//create socket
if(mode)
    fd_in = socket(AF_INET, SOCK_STREAM,IPPROTO_TCP);
else
    fd_in = socket(AF_INET, SOCK_DGRAM,IPPROTO_UDP);

if(fd_in == -1) return 0;

//bind the socket and the address
rc = bind(fd_in, (struct sockaddr *)&server_addr, sizeof(server_addr));

if(rc < 0){ perror("bind"); return 0;}

//wait for connections when using TCP with listen()
if(mode)
    if(listen(fd_in, 5) < 0) { perror("listen"); return 0; };

        rc = gethostname(hostname, sizeof(hostname));

        if(rc < 0) { perror("gethostname"); return 0; }

In this section of code, we call some network functions in order to create the wanted behavior for our echo server.

First, we create the socket with the socket() function based on the value of the mode parameter. When using TCP, the SOCK_STREAM and IPPROTO_TCP parameters must be used, when using UDP we write SOCK_DGRAM and IPPROTO_UDP.

This give to us a file descriptor that we store in the "fd_in" variable.

When we have a socket, we must associate it with an address and the bind() function does what we want. By passing the file descriptor and the server_addr strcuture we bind the socket and the addresses of our local machine in order to have a usable access point for incoming and outcoming network flow. Note that the second parameter of bind() must be of type "sockaddr *" and not "sockaddr_in" so we have to cast it.

If we use TCP we must also call listen() in order to listen for connections on a socket. The second parameter specify the maximum length of the connections queue.

At the end, by calling gethostname(), we get the local hostname and we store its value to the "hostname" variable.

addrlen = sizeof(server_addr);

//accepts the connection when using TCP, do nothing with UDP
if(mode){
   //accept generates a fd_in_twoet. the old one is not used from this point
   fd_in_two = accept(fd_in, (struct sockaddr *)&client_addr, &addrlen);

   rc = getpeername(fd_in_two, (struct sockaddr *)&client_addr, &addrlen);

   if(rc < 0) { perror("getpeername"); return 0; }

  printf("200 Hello %s on the echo server called %s\n", inet_ntoa(client_addr.sin_addr), hostname);
}
else{
   fd_in_two = fd_in;
   printf("200 Hello on the echo server called %s\n", hostname);
}

do{
   //flush the buffer
   memset(buf, 0, sizeof(buf));

   //receive data
   rc = recvfrom(fd_in_two, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &addrlen);

   //stop when a new line has been sent
   if(strcmp("\n", buf) == 0) break;

   printf("My lord we have received %d bytes => %s", rc-1, buf);
   fprintf(log, "IP %s has sent the message %s", inet_ntoa(client_addr.sin_addr), buf);

   //send a response back
   if(rc) sendto(fd_in_two, send, sizeof(send), 0, (struct sockaddr *)&client_addr, addrlen);
                numberOfLines++;
   }
while(rc);

fprintf(log, "%s [%d] ended, received %d lines", argv[0], pid, numberOfLines);

These last lines are the end of the echo server. Here we basically receive the data and send a response back, but first, we have to use accept() if TCP is used. Note that accept() generates a new socket, so the old socket "fd_in" is not used anymore from this point (line 80) but "fd_in_two" is used instead.

With getpeername() we get some information about the other end point of the connection and we populate the client_addr structure so we can use for example to get the IP address of the connected machine.

In the case of UDP (line 87 to 91) we don't do any special call but we simply copy "fd_in" to "fd_in_two".

In the do-while block we receive and send the data. First of all we flush the buffer with memset() then we use recvfrom() to receive data from the IP specified in client_addr. The "buf" variable is used to store incoming data and we want to store at maximum, sizeof(buf) bytes. Note that recvfrom() will return the number of received bytes that we store in "rc".

If "buf" contains only a new line character, the server will stop. So the client can hit enter to stop the echo server.

With fprintf() we log some events and later with sendto(), we send an answer back to the connected client. We will send the content of the "send" variable.

At the end we will write in the log file that the echo server has ended.

Compiling and test

The compilation is really easy:

bash-4.2$ gcc echoserver.c -o echoserver

To test the echo server, we first execute it:

bash-4.2$ ./echoserver tcp

Then with "nc" we can send some messages from the client:

bash-4.2$ nc localhost 1111
test
Thank you for registering
this is a message
Thank you for registering

bash-4.2$

On the server side we will see:

bash-4.2$ ./echoserver tcp
200 Hello 127.0.0.1 on the echo server called darkstar
My lord we have received 4 bytes => test
My lord we have received 17 bytes => this is a message
bash-4.2$

Code


#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include
#include
#include
#include
#include
#include
#include
#include

int main(int argc, char *argv[]){
    /** Variables declaration **/
    int rc, numberOfLines, addrlen,fd_in_two, fd_in, mode;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    char buf[100], hostname[10];
    char send[] = "Thank you for registering\n";
    FILE* log;
    pid_t pid;

    /** Some variables assignement **/   
    numberOfLines = 0;
    log = fopen("log.txt", "a+");
    pid = getppid();

    //make sure that the server_addr struct is clean
    //then populate it with the different parameters
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(1111);

    /** Logic **/

    //check the parameter
    if(argc != 2){
        printf("Use "tcp" or "udp" as parameters.\n");
        return 0;
    }

    //set the mode
    if(strncmp(argv[1],"udp",3) == 0)
        mode = 0;
    else if(strncmp(argv[1],"tcp",3) == 0)
        mode = 1;
    else
        return 0;

    //create socket
    if(mode)
        fd_in = socket(AF_INET, SOCK_STREAM,IPPROTO_TCP);
    else
        fd_in = socket(AF_INET, SOCK_DGRAM,IPPROTO_UDP);

    if(fd_in == -1) return 0;

    //bind the socket and the address
   
    rc = bind(fd_in, (struct sockaddr *)&server_addr, sizeof(server_addr));

    if(rc < 0){ perror("bind"); return 0;}

    //wait for connections when using TCP with listen()
    if(mode)
        if(listen(fd_in, 5) < 0) { perror("listen"); return 0; };


    rc = gethostname(hostname, sizeof(hostname));

    if(rc < 0) { perror("gethostname"); return 0; }

    addrlen = sizeof(server_addr);

    //accepts the connection when using TCP, do nothing with UDP
    if(mode){
        //accept generates a fd_in_twoet. the old one is not used from this point
        fd_in_two = accept(fd_in, (struct sockaddr *)&client_addr, &addrlen);

        rc = getpeername(fd_in_two, (struct sockaddr *)&client_addr, &addrlen);

        if(rc < 0) { perror("getpeername"); return 0; }

        printf("200 Hello %s on the echo server called %s\n", inet_ntoa(client_addr.sin_addr), hostname);
    }else{
        fd_in_two = fd_in;

        printf("200 Hello on the echo server called %s\n", hostname);
    }

    do{
        //flush the buffer
        memset(buf, 0, sizeof(buf));
       
        //receive data
        rc = recvfrom(fd_in_two, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &addrlen);

        //stop when a new line has been sent
        if(strcmp("\n", buf) == 0) break;

        printf("My lord we have received %d bytes => %s", rc-1, buf);

        fprintf(log, "IP %s has sent the message %s", inet_ntoa(client_addr.sin_addr), buf);

        //send a response back
        if(rc) sendto(fd_in_two, send, sizeof(send), 0, (struct sockaddr *)&client_addr, addrlen);

        numberOfLines++;
    }while(rc);

    fprintf(log, "%s [%d] ended, received %d lines\n", argv[0], pid, numberOfLines);
}