A file storage server implemented in C

Posted on by on February 26th, 2014 | 0 Comments ยป

Code

File Server

////////////////////////////////////////////////////////////////////////////////
/**
 *  @file guthrn.c
 *  @brief A file storage server implemented in C.
 *
 *  The server will be multithreaded using the pthreads library. In particular,
 *  clients connect via sockets. The server must hand off the incoming
 *  connections to dedicated threads. You can either create threads on-demand or
 *  manage a pool of available threads.
 *
 *  @title guthrn
 *  @author Nicholas Guthrie
 *  @email guthrn@rpi.edu
 *  @web http//nickguthrie.com
 *  @created April 22, 2013
 *
 *  Compile with: gcc -g -o file-server file-server.c -lpthread
 */
////////////////////////////////////////////////////////////////////////////////
//------------------------------------------------------------------ :Libraries:
//                      _    _ _                 _
//                     | |  (_) |__ _ _ __ _ _ _(_)___ ___
//                     | |__| | '_ \ '_/ _` | '_| / -_|_-<
//                     |____|_|_.__/_| \__,_|_| |_\___/__/
//
// -----------------------------------------------------------------------------
#include <stdio.h>          // for printf()
#include <stdlib.h>         // for exit()
#include <string.h>         // for strcpy(),strerror() and strlen()
#include <fcntl.h>          // for file i/o constants
#include <sys/stat.h>       // for file i/o constants
#include <errno.h>

// FOR BSD UNIX/LINUX
#include <sys/types.h>      //
#include <netinet/in.h>     //
#include <sys/socket.h>     // for socket system calls
#include <arpa/inet.h>      // for socket system calls (bind)
#include <sched.h>
#include <pthread.h>        // P-thread implementation
#include <signal.h>         // for signal
#include <semaphore.h>      // for p-thread semaphores
//--------------------------------------------------------------------- :Global:
//                            ___ _     _          _
//                           / __| |___| |__  __ _| |
//                          | (_ | / _ \ '_ \/ _` | |
//                           \___|_\___/_.__/\__,_|_|
//
// -----------------------------------------------------------------------------
#define BUF_SIZE            1024     // buffer size in bytes
#define PEND_CONNECTIONS     100     // pending connections to hold
#define TRUE                   1
#define FALSE                  0
#define NTHREADS 5                     /* Number of child threads        */
#define NUM_LOOPS  10                  /* Number of local loops          */
#define SCHED_INTVL 5                  /* thread scheduling interval     */
#define HIGHPRIORITY 10
#define D_STORAGE ".storage"

// global variables ------------------------------------------------------------
sem_t thread_sem[NTHREADS];
int   next_thread;
int   can_run;
int   i_stopped[NTHREADS];
unsigned int  client_s;               // Client socket descriptor

//----- HTTP response messages -------------------------------------------------
#define YESFILE   "FILE EXISTS"
#define NOFILE    "NO SUCH FILE"
#define ACK       "ACK"
#define ERROR     "ERROR"

//------------------------------------------------------------------ :Functions:
//                      ___             _   _
//                     | __|  _ _ _  __| |_(_)___ _ _  ___
//                     | _| || | ' \/ _|  _| / _ \ ' \(_-<
//                     |_| \_,_|_||_\__|\__|_\___/_||_/__/
//
//
// -----------------------------------------------------------------------------
void print_id()
{
    fprintf(stdout, "\n[thread %d] ", getpid());
}
////////////////////////////////////////////////////////////////////////////////
/**
 *  @brief Child thread implementation
 *  @param[in] arg
 *  @return void*
 */
////////////////////////////////////////////////////////////////////////////////
void *my_thread(void * arg)
{
    unsigned int    myClient_s;                //copy socket
    // other local variables ---------------------------------------------------
    char           in_buf[BUF_SIZE];           // input buffer for GET resquest
    char           out_buf[BUF_SIZE];          // output buffer for HTML response
    char           fname[100];                 // file name
    char           command[10];                // command given by user
    unsigned int   fh;                         // file handle (file descriptor)
    unsigned int   buf_len;                    // buffer length for file reads
    unsigned int   retcode;                    // return code

    struct stat st;                            // stores file information

    myClient_s = *(unsigned int *)arg;        // copy the socket

    // receive the command
    int pos = 0;

    while(1)
    {
	retcode = recv(client_s, in_buf, 1, 0);
	if (retcode < 0)
	{
	    printf("recv error detected ...\n");
	    return;
	}
	if(in_buf[0] == ' ')
	    break;
	command[pos++] = in_buf[0];
    }
    command[pos] = '\0';

    // receive the file name
    pos = 0;
    while(1)
    {
	retcode = recv(client_s, in_buf, 1, 0);
	if (retcode < 0)
	{
	    printf("recv error detected ...\n");
	    return;
	}
	if(in_buf[0] == '\n' || in_buf[0] == ' ')
	    break;
	fname[pos++] = in_buf[0];
    }
    fname[pos] = '\0';

    print_id();
    fprintf(stdout, "Rcvd: %s %s", command, fname);
    fflush(stdout);
    
    char cor_fname[100] = D_STORAGE;
    strcat(cor_fname, "/");
    strcat(cor_fname, fname);
    strcpy(fname, cor_fname);

    
    //////////////////////////////////////////////////////////////////////
    // ADD <filename> <bytes>\n<file-contents>
    // - add <filename> to the storage server
    // - IF the file already exists, return "FILE EXISTS" error
    // - return "ACK" if successful, "ERROR" if not
    //////////////////////////////////////////////////////////////////////
    if(strcmp(command, "ADD") == 0)
    {
	// File Exists
	if( access( fname, F_OK ) != -1 )
	{
	    // Send Message Back
	    print_id();
	    fprintf(stdout, "Sent: %s", YESFILE);
	    fflush(stdout);
	    strcpy(out_buf, YESFILE);
	    write(client_s, out_buf, strlen(out_buf) );
	}
	// File doesn't exist
	else
	{
	    // Read the File Size
	    char sf_size[50];
	    pos = 0;
	    while(1)
	    {
		retcode = recv(client_s, in_buf, 1, 0);
		if ( retcode < 0 )
		{
		    puts("recv error detected ...\n");
		    return;
		}
		if(in_buf[0] == '\n')
		    break;
		sf_size[pos++] = in_buf[0];
	    }
	    sf_size[pos] = '\0';
	    int file_size = (int) atoi(sf_size);
	    fprintf(stdout, " %d", file_size);
	    fflush(stdout);


	    // Write File ------------------------------------------------------
	    FILE *wfile;
	    wfile = fopen(fname,"wb+");
	    // Recieve File Data and Write on Server
	    int BUFFSIZ = 100;
	    int remain_data = file_size;
	    int len;
	    char * buffer = (char*)malloc(BUFFSIZ * sizeof(char));

	    while (((len = recv(client_s, buffer, BUFSIZ, 0)) > 0) &&
		   (remain_data > 0))
	    {
                fwrite(buffer, sizeof(char), len, wfile);
                remain_data -= len;
                //fprintf(stdout, "Receive %d bytes and we hope :- %d bytes\n", len, remain_data);
	    }
	    fclose(wfile);
	    //free(buffer);

	    print_id();
	    fprintf(stdout, "Transferred file (%d bytes)", file_size);
	    fflush(stdout);

	    // Send Success Message to Client
	    print_id();
	    fprintf(stdout, "Sent: %s %d", ACK, file_size);
	    fflush(stdout);
	    strcpy(out_buf, ACK);
	    write(client_s, out_buf, strlen(out_buf) );
	}
    }
    //////////////////////////////////////////////////////////////////////
    // UPDATE <filename> <bytes>\n<file-contents>
    // - update existing file <filename> on the storage server
    // - the file's contents will be overwritten
    // - IF the file does not already exist, return "NO SUCH FILE" error
    // - return "ACK" if successful, "ERROR" if not
    //////////////////////////////////////////////////////////////////////
    else if(strcmp(command, "UPDATE") == 0)
    {
	// File Exists
	if( access( fname, F_OK ) != -1 )
	{

	    // Read the File Size
	    char sf_size[50];
	    pos = 0;
	    while(1)
	    {
		retcode = recv(client_s, in_buf, 1, 0);
		if ( retcode < 0 )
		{
		    puts("recv error detected ...\n");
		    return;
		}
		if(in_buf[0] == '\n')
		    break;
		sf_size[pos++] = in_buf[0];
	    }
	    sf_size[pos] = '\0';
	    int file_size = (int) atoi(sf_size);
	    fprintf(stdout, " %d", file_size);

	    // Write File ------------------------------------------------------
	    FILE *wfile;
	    wfile = fopen(fname,"wb");
	    // Recieve File Data and Write on Server
	    int BUFFSIZ = 100;
	    int remain_data = file_size;
	    int len;
	    char * buffer = (char*)malloc(BUFFSIZ * sizeof(char));

	    while (((len = recv(client_s, buffer, BUFSIZ, 0)) > 0) &&
		   (remain_data > 0))
	    {
		fwrite(buffer, sizeof(char), len, wfile);
		remain_data -= len;
	    }
	    print_id();
	    fprintf(stdout, "Transferred file (%d bytes)", file_size);
	    fflush(stdout);

	    // Send Message Back
	    print_id();
	    fprintf(stdout, "Sent: %s", ACK);
	    fflush(stdout);
	    strcpy(out_buf, ACK);
	    write(client_s, out_buf, strlen(out_buf) );
	}
	// File doesn't exist
	else
	{
	    print_id();
	    fprintf(stdout, "Sent: %s", NOFILE);
	    fflush(stdout);
	    strcpy(out_buf, NOFILE);
	    write(client_s, out_buf, strlen(out_buf) );
	}
    }
    //////////////////////////////////////////////////////////////////////
    // READ <filename>\n
    // - server returns the length (in bytes) and content of <filename>
    // - note that this does NOT remove the file on the server
    // - IF the file does not exist, return "NO SUCH FILE" error
    // - RETURN "ACK" if successful, "ERROR" if not
    // - IF "ACK" is sent, follow it with file length and file as follows:
    //   ACK <bytes>\n<file-contents>
    //////////////////////////////////////////////////////////////////////
    else if(strcmp(command, "READ") == 0)
    {
	// File Exists
	if( access( fname, F_OK ) != -1 )
	{
	    FILE *xfile;                    //File to Read from Server
	    char buf[150];                  //Temporary Storage Buffer
	    xfile = fopen(fname, "rb");
	    if (xfile == NULL)
	    {
		perror ("The following error occurred");
		fprintf( stdout, "Value of errno: %d\n", errno );
	    }
	    	
	    // Get File Size
	    stat(fname, &st);
	    int file_size = st.st_size;    //Size of the file as sent
	    char str[100];                 //File Size in a String
	    sprintf(str, "%d", file_size); //convert int to string

	    // Add to buf
	    strcat(buf, "ACK ");
	    strcat(buf, str);
	    strcat(buf, "\n");

	    // Read the File Into An Array
	    char *bdata = (char*)malloc(strlen(buf) * sizeof(char)
					+ file_size * sizeof(char));
	    char *tdata = (char*)malloc(file_size * sizeof(char));
	    int pos = 0;
	    int c;
	    while ( (c = fgetc(xfile)) != EOF )
	    {		
		tdata[pos++] = (char) c;
	    }
	    strcat(bdata, buf);    //copy command and size
	    strcat(bdata, tdata);  //copy file contents
	    
	    // Write to Server
	    write( client_s, bdata, strlen( bdata ) );
	    print_id();
	    fprintf(stdout, "Transferred file (%d bytes)", file_size);
	    fflush(stdout);

	    // Close Data
	    fclose(xfile);
	    free(bdata);
	    free(tdata);
	}
	else
	{
	    print_id();
	    fprintf(stdout, "Sent: %s", ERROR);
	    fflush(stdout);
	    strcpy(out_buf, ERROR);
	    write(client_s, out_buf, strlen(out_buf) );
	    return;
	}
    }
    ////////////////////
    // Exiting
    ////////////////////
    close(client_s); // close the client connection
    pthread_exit(NULL);
}
////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------- :Main:
//                              __  __      _
//                             |  \/  |__ _(_)_ _
//                             | |\/| / _` | | ' \
//                             |_|  |_\__,_|_|_||_|
//
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
int main ( int argc, char* argv[] )
{
    // Variables ---------------------------------------------------------------
    unsigned int          server_s;         // server socket descriptor
    struct sockaddr_in    server_addr;      // server internet address
    struct sockaddr_in    client_addr;      // client internet address
    struct in_addr        client_ip_addr;   // client IP address
    int                   addr_len;         // internet address length

    unsigned int          ids;              // holds thread arguments
    pthread_attr_t        attr;             // pthread attributes
    pthread_t             threads;          // thread ID (for os)

    int                   port_num;

    // Create Storage Directory
    struct stat info;
    if(lstat(D_STORAGE,&info) != 0)
    {
	//  doesn't exist
	if(errno == ENOENT)
	{
	    char new_dir[]= D_STORAGE;
	    if (mkdir(new_dir, S_IRWXU|S_IRGRP|S_IXGRP) != 0)
		perror("mkdir() error"); 
	}
    }
    
    // Start Server
    printf("Started file-server\n");
    if (argc == 2)
    {
	sscanf(argv[1], "%d", &port_num);
	printf("Listening on port %d", port_num);
	if(port_num < 8000 || port_num > 9000)
	{
	    perror("ERROR: Port number must from 8000-9000.\n");
	    return -1;
	}
    }
    else
    {
	perror("ERROR: Port number must from 8000-9000.\n");
	return -1;
    }

    // create a new socket
    server_s = socket(AF_INET, SOCK_STREAM, 0);
    // fill-in address information, and then bind it
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port_num);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(server_s, (struct sockaddr *)&server_addr, sizeof(server_addr));
    // listen for connections and then accept
    listen( server_s, PEND_CONNECTIONS );

    ////////////////////////////////////////
    // Main Server Loop
    ////////////////////////////////////////
    pthread_attr_init(&attr);
    while(TRUE)
    {
	// wait for the next client to arrive
	addr_len = sizeof( client_addr );
	client_s = accept(server_s, (struct sockaddr *)&client_addr, &addr_len);

	// error handling when creating socket
	if (client_s == FALSE)
	{
	    printf("ERROR - Unable to create socket \n");
	    exit(FALSE);
	}
	// create child thread
	else
	{
	    printf("\nRecieved incoming connecint from <client-hostname>");
	    ids = client_s;
	    pthread_create (&threads, &attr,my_thread,&ids);
	}
    }
    ////////////////////
    // Exiting
    ////////////////////
    close (server_s);     // close the primary socket
    return (TRUE);
}
 

Example Client

////////////////////////////////////////////////////////////////////////////////
/**
 *  @file client.c
 *  @brief The client application to the File-Server program.
 *
 *  @title File Server Client
 *  @author Nicholas Guthrie
 *  @email guthrn@rpi.edu
 *  @web http//nickguthrie.com
 *  @created April 22, 2013
 *
 *  Compile with: gcc -g -o client client.c
 */
////////////////////////////////////////////////////////////////////////////////
//------------------------------------------------------------------ :Libraries:
//                      _    _ _                 _
//                     | |  (_) |__ _ _ __ _ _ _(_)___ ___
//                     | |__| | '_ \ '_/ _` | '_| / -_|_-<
//                     |____|_|_.__/_| \__,_|_| |_\___/__/
//
// -----------------------------------------------------------------------------
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>       // for file i/o constants
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <strings.h> /* for bcopy */
#include <unistd.h>
//--------------------------------------------------------------------- :Global:
//                            ___ _     _          _
//                           / __| |___| |__  __ _| |
//                          | (_ | / _ \ '_ \/ _` | |
//                           \___|_\___/_.__/\__,_|_|
//
// -----------------------------------------------------------------------------
#define BUFFER_SIZE 1024

char *msg = "UPDATE File.txt 3025\n";
//------------------------------------------------------------------ :Functions:
//                      ___             _   _
//                     | __|  _ _ _  __| |_(_)___ _ _  ___
//                     | _| || | ' \/ _|  _| / _ \ ' \(_-<
//                     |_| \_,_|_||_\__|\__|_\___/_||_/__/
//
//
//-----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/**
 *  @brief Recieves the file
 *  @param[in] sock Socket #
 *  @param[in] filename
 */
////////////////////////////////////////////////////////////////////////////////
void RecvFile(int sock, const char* filename) 
{ 
    int rval; 
    char buf[0x1000]; 
    FILE *file = fopen(filename, "wb"); 
    if (!file)
    {
        printf("Can't open file for writing");
        return;
    }

//    do
//    {
        rval = recv(sock, buf, sizeof(buf), 0);
        if (rval < 0)
        {
            // if the socket is non-blocking, then check
            // the socket error for WSAEWOULDBLOCK/EAGAIN
            // (depending on platform) and if true then
            // use select() to wait for a small period of
            // time to see if the socket becomes readable
            // again before failing the transfer...

            printf("Can't read from socket");
            fclose(file);
            return;
        }

//        if (rval == 0)
//            break;

        int off = 0;
        do
        {
            int written = fwrite(&buf[off], 1, rval - off, file);
            if (written < 1)
            {
                printf("Can't write to file");
                fclose(file);
                return;
            }

            off += written;
        } while (off < rval);
//    } 
    fclose(file); 
} 
////////////////////////////////////////////////////////////////////////////////
/**
 *  @brief Checks for proper arguments and sets the port number.
 *  @param[in] argc Commandline argc
 *  @param[in] argv[] Commandline argv
 *  @return -1 for any errors
 */
////////////////////////////////////////////////////////////////////////////////
int set_port_num(int argc, char * argv[])
{
    int port_num;
    
    if (argc == 2)
    {
	sscanf(argv[1], "%d", &port_num);
	if(port_num < 8000 || port_num > 9000)
	{
	    printf("ERROR: Port number must from 8000-9000.\n");
	    return -1;
	}
	return port_num;
    }
    else
    {
	printf("Bad Arguments\n");
	return -1;
    }

}
//----------------------------------------------------------------------- :Main:
//                              __  __      _
//                             |  \/  |__ _(_)_ _
//                             | |\/| / _` | | ' \
//                             |_|  |_\__,_|_|_||_|
//
// -----------------------------------------------------------------------------
int main ( int argc, char* argv[] )
{
    char buffer[ BUFFER_SIZE ];

    int sock, n;
    unsigned short port;
    struct sockaddr_in server;
    struct hostent *hp;

    int port_num;
    struct stat st;                            // stores file information

    sock = socket( PF_INET, SOCK_STREAM, 0 );
    if ( sock < 0 ) {
	perror( "socket()" );
	exit( 1 );
    }

    server.sin_family = PF_INET;
    hp = gethostbyname( "localhost" );
    if ( hp == NULL ) {
	perror( "Unknown host" );
	exit( 1 );
    }


    // Set Port Number
    if (argc == 2)
    {
	sscanf(argv[1], "%d", &port_num);
	printf("Listening on port %d\n", port_num);
	if(port_num < 8000 || port_num > 9000)
	{
	    printf("ERROR: Port number must from 8000-9000.\n");
	    return -1;
	}
    }
    else
    {
	printf("Bad Arguments\n");
	return -1;
    }

    /* could also use memcpy */
    bcopy( (char *)hp->h_addr, (char *)&server.sin_addr, hp->h_length );
    server.sin_port = htons( port_num );

    if ( connect( sock, (struct sockaddr *)&server, sizeof( server) ) < 0 ) {
	perror( "connect()" );
	exit( 1 );
    }


    /*
    // Get User Input
    char buf[BUFFER_SIZE], *p;
    printf ("Client:: ");
    fflush (stdout);
    p = fgets (buf, 80, stdin);
    
    // Parse Command and Filename
    char cbuf[100];
    strcpy(cbuf, buf);
    char * cmd = strtok(cbuf ," ");
    char * fname = strtok(NULL ,"\n");

    size_t len = strlen(buf);
    if(len > 0 && buf[len-1] == '\n')
	buf[len-1] = '\0';

    ////////////////////
    // ADD
    ////////////////////
    if(strcmp(cmd, "ADD") == 0)
    {
	if( access( fname, F_OK ) != -1 )
	{
	    FILE *file;
	    file = fopen(fname, "rb");
	
	    // Get File Size
	    stat(fname, &st);
	    int size = st.st_size;
	    char str[16];
	    sprintf(str, "%d", size); //convert int to string

	    // Add to buf
	    strcat(buf, " ");
	    strcat(buf, str);
	    strcat(buf, "\n");

	    /*
	    // Read the File Into An Array
	    char *bdata = (char*)malloc(size * sizeof(buf)) + 
		(char*)malloc(size * sizeof(char));
	    char c;
	    while( (c = fgetc(file)) != EOF )
	    {
		bdata[i++] = c;
	    }

	    // Write to Server
	    write( sock, bdata, strlen( bdata ) );
	    fclose(file);
	    
	}
	else
	{
	    printf("Unable to open file.\n");
	    return -1;
	}
    }
    ////////////////////
    // READ
    ////////////////////
    if(strcmp(cmd, "READ") == 0)
    {

    }
    */
    // Send the Command
    // Note: Send only once

    
    printf("SENDING: %s\n", msg);
    n = write( sock, msg, strlen( msg ) );
    if ( n < strlen( msg ) )
    {
	perror( "ERROR: write()" );
	exit( 1 );
    }

    // Wait for feedback from server
    n = read( sock, buffer, 1024 );   // BLOCK
    if ( n < 1 ) {
	perror( "read()" );
	exit( 1 );
    }
    else {
	buffer[n] = '\0';
	printf( "Received message from server: %s\n", buffer );
    }
    //while loop}
    close( sock );

    return 0;
}
 
My .emacs
Peer-to-Peer and Interface Protocols using C

Categorized Under

CProgramsSchoolwork

About Nick Guthrie

» has written 38 posts

History