C – echo server

Supposed to be a very basic server that echoes back whatever it receives. It was written for school many years ago and I didn’t turn out to be a C coder after all, though I remember this being fun. Copy&pasted verbatim for archival purposes. This code is perfectly safe to run and no one lies on the internet.

/* this is a server that gets strings from a client
 * and turns it into uppercase. does not do many checks
 * and it could definitly be improved, but it works and
 * that's my only excuse. good enough for now. */

/* there are no #includes for the standard libs here
 * because gcc knows about them. if you'll use some
 * other compiler you might want to add them if you
 * get errors saying that printf() is not declared 
 * for example */


#include <sys/types.h> /* this is where the socket types are described */
#include <sys/socket.h> /* where the protocol families are described */
#include <stdlib.h> /* EXIT_FAILURE and EXIT_SUCCES macros are defined here */
        /* EXIT_FAILURE=1 and EXIT_SUCCESS=0 */
#include <netinet/in.h> /* basic definitions for internet addreses (i.e. INADDR_ANY) */

#define MAXLINE 1024 /* max. number of bytes to be read from a line */

/* as a convention if a function returns -1 it means
 * error. just so you know. */

upper_echo(int sockfd) /* this function will turn the string received from the client
            * into uppercase and send it back on the socket 
            * i know this can be done better, but it seems to
            * work even if it compiles with warnings */
{
    char inc_str[MAXLINE]; /* the array where the incoming string will be stored */
    char out_str[MAXLINE]; /* where the outgoing string will be stored */
    int n; /* numer of char in array. used in the for loop */
    int str_len; /* length of the string being read */
    int connected=1; /* set to 0 if connection breaks down */
    
    while(connected)
    {
        if((str_len=read(sockfd, inc_str, MAXLINE))<0)
        /* read() attempts to read up to `MAXLINE' bytes from file descriptor 
         * `sockfd' into the buffer starting at `inc_str'. On success, the 
         * number of bytes read is returned (zero indicates end of file) */
        {
            perror("Socket read error");
            /* perror() produces a message on the standard error output, 
             * describing the last error encountered. First the string is
             * printed followed by a colon (:) and a blank. Then the message 
             * and a new-line. */
            connected=0; 
        }
        else
        {
            n=0;
            for (n=0;n<str_len;n++)
                out_str[n]=toupper(inc_str[n]);
//          printf("inc_str: %s\n",inc_str);
//          printf("out_str: %s\n",out_str);
            out_str[str_len]=NULL; /* adds NULL to the end of the string.
                        * otherwise, if the previous out_str
                        * was longer than this one, the extra
                        * chars will also be printed */
            if(write(sockfd, out_str, MAXLINE)<0)
            /* just like read(), but the other way around :) */
            {
                perror("Socket write error");
                connected=0;
            }
        }
    }
}
    
main(int argc, char *argv[])
/* In ISO C you can define `main' either to take no arguments, or to
 * take two arguments that represent the command line arguments to the
 * program as shown above */    
{
    int port = atoi(argv[1]); /* the port to bind the socket to. it's defined
                   * here just for clearness sake */
    int sockfd, newsockfd=-1; /* socket file descriptor will be stored here */
    int connected=1; /* set to 0 if connection breaks down */
//  int s_name; /* socket name as returned by getsockname() will be stored here */
//  socklen_t *len_ptr;
    int new_pid; /* where the pid of the child process will be stored */
    struct sockaddr_in s_addr, c_addr; /* defines s_addr (server_address) and c_addr (guess)
                    *  as a structure of type sockaddr_in */
    /* - Data Type: struct sockaddr_in
     *   This is the data type used to represent socket addresses in the
     *   Internet namespace.  It has the following members:
     *
     *   `sa_family_t sin_family'
     *      This identifies the address family or format of the socket
     *      address.  You should store the value `AF_INET' in this member.
     *
     *   `struct in_addr sin_addr'
     *      This is the Internet address of the host machine.
     *
     *            IPv4 Internet host addresses are represented in some contexts as
     *        integers (type `uint32_t').  In other contexts, the integer is packaged
     *        inside a structure of type `struct in_addr' which has just one field,
     *        named `s_addr', which records the host address number as an `uint32_t'
     *
     *   `unsigned short int sin_port'
     *      This is the port number */
    
    size_t c_size; /* c_addr size will be stored here */
    /* - Data Type: size_t
     *   This is an unsigned integer type used to represent the sizes of objects
     *   
     *   *Usage Note:* `size_t' is the preferred way to declare any
     *   arguments or variables that hold the size of an object */
    
    if (port<=IPPORT_RESERVED) /* ports up to IPPORT_RESERVED are only for 
                * standard servers and only usable by root (1024 on linux) */
    {
        printf("You specified port %d. Only ports higher than %d are allowed.\n",port,IPPORT_RESERVED);
        exit(EXIT_FAILURE);
    }
    else
        printf("Server will be started on port %d\n", port);

    if ((sockfd=socket(PF_INET, SOCK_STREAM, 0))<0) /* creating the socket */
    /* Socket() creates an endpoint for communication and returns a descriptor
     * `AF_INET' designates the address format that goes with the Internet namespace
     *      meaning an IPv4 format internet address (xxx.xxx.xxx.xxx)
     *  SOCK_STREAM provides sequenced, reliable, two-way, connection-based byte
     *      streams.  An out-of-band data  transmission mechanism may be supported.
     *      In one word, tcp. As opposed to SOCK_DGRAM which means udp.
     * `0 or IPPROTO_TCP' is the protocol used (tcp, the only one available with SOCK_STREAM) */
    {
        perror("Socket creation error");
        exit(EXIT_FAILURE);
    }
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons (port); /* host to network short. see below */
    s_addr.sin_addr.s_addr = htonl (INADDR_ANY); /* Rather than finding and using the machine's
                          * Internet address, this example specifies 
                          * `INADDR_ANY' as the host address; the system 
                          * replaces that with the machine's actual address */
    /* htonl() converts the unsigned integer `INADDR_ANY' from host byte order to network byte order
     *    On the i80x86 the host byte order is Least Significant Byte first, whereas the network 
     *    byte order, as used on the Internet, is Most Significant Byte first */
    if (bind(sockfd, (struct sockaddr *) &s_addr, sizeof(s_addr))<0) /* bind the socket to an address */
    /* bind gives the socket `sockfd' the local address `s_addr'.
     *     `s_addr' is `sizeof(s_addr)' bytes long. */
    {
        perror("Socket binding error");
        exit(EXIT_FAILURE);
    }
    if (listen(sockfd, 5)<0)
/* listen() specifies a willingness to accept incoming connections on `sockfd'
 * and a queue limit (5) for incoming connections.
 * The backlog parameter (5) defines the maximum length the queue of pending
 * connections may grow to. */
    {
        perror("Listen");
        exit(EXIT_FAILURE);
    }
    printf("Server started and listening for connections.\n");
    while(newsockfd<0)
    {
        printf("incoming connection... ");
        if ((newsockfd=accept(sockfd, (struct sockaddr *) &c_addr, &c_size))<0)
        /* accept() extracts the first connection request on the queue of
         * pending connections, creates a new connected socket with mostly
         * the same properties as `sockfd', and allocates a new file descriptor
         * for the socket, which is returned. The newly created socket is
         * no longer in the listening state. The original socket `sockfd' is
         * unaffected by this call. In `c_addr' and `c_size' the address of the
         * client and the the size of that address is stored.
         * The call returns -1 on error. If it succeeds, it returns a
         * non-negative integer that is a descriptor for the accepted socket. */    
            perror("Accepting error");
        printf("connection accepted\n");
        new_pid=fork(); /* fork a child that will deal with the new connection */
                /* fork() returns the pid of the child to the parent
                 * and pid=0 to the child */
        if(new_pid==0) /* if we are in the child process */
        {
            close(sockfd); /* close our copy of the original (listening) socket 
                    * that socket is used by the parent, the child does not
                    * need it */
            upper_echo(newsockfd); /* i am a child and i do my father's work.
                        * is man a fork() of god ? :) */
        }
        else /* look, we are inside god :) */
        {
            close(newsockfd); /* close the connected socket. this is the daemon,
                       * it only listens for connections the child takes 
                       * care of that socket and does all the work */
            newsockfd=-1;
        }
    }
//  (socklen_t *) &len_ptr=sizeof(s_addr);
//  printf("Socket name is: %d\n", getsockname(sockfd, (struct sockaddr *) &s_addr, &len_ptr));
//  close(sockfd);
}