Home > Software engineering >  Sending UDP struct using C programming
Sending UDP struct using C programming

Time:12-12

What I would like to achieve: One client know multiple IP addresses of different servers which hosts exactly same files.

Client should be able to receive infos about files/folders hosted on server (permissions, size and so on). I'm stuck since I moved buffer from char* to struct to have control about corrupted packet. Right now my client doesn't reach server using send() function.

It worked perfectly sith sendto and recvfrom but as far I've readed, they are not usable with structs, only with char* buffers.

Thank you!

Server.c

// Server side implementation of UDP client-server model
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/stat.h>   //Used to get file metadata
#include <time.h>       //Used to format time-based metadata
#include <dirent.h>     //User to list directory's content
   
#define PORT     8080
#define MAXLINE 65527 // 65535 is MAX and other 8 bytes are reserved for header

struct SPacket {
    int len;
    char msg[MAXLINE - sizeof(int)];
};

void GetFileMetadata(const char* szPath, char* Out)
{
    if (access(szPath, F_OK) != 0 )
        snprintf(Out, MAXLINE, "%s", szPath);
    else
    {
        struct stat res;
        stat(szPath, &res);

        int length = 0;
        length  = snprintf(Out length, MAXLINE, "Filename:\t\t%s\n", szPath);
        length  = snprintf(Out length, MAXLINE, "Filesize:\t\t%d\n", res.st_size);
        length  = snprintf(Out length, MAXLINE, "Permissions:\t\t");
        length  = snprintf(Out length, MAXLINE, (S_ISDIR(res.st_mode)) ? "d" : "-");
        length  = snprintf(Out length, MAXLINE, (res.st_mode & S_IRUSR) ? "r" : "-");
        length  = snprintf(Out length, MAXLINE, (res.st_mode & S_IWUSR) ? "w" : "-");
        length  = snprintf(Out length, MAXLINE, (res.st_mode & S_IXUSR) ? "x" : "-");
        length  = snprintf(Out length, MAXLINE, (res.st_mode & S_IRGRP) ? "r" : "-");
        length  = snprintf(Out length, MAXLINE, (res.st_mode & S_IWGRP) ? "w" : "-");
        length  = snprintf(Out length, MAXLINE, (res.st_mode & S_IXGRP) ? "x" : "-");
        length  = snprintf(Out length, MAXLINE, (res.st_mode & S_IROTH) ? "r" : "-");
        length  = snprintf(Out length, MAXLINE, (res.st_mode & S_IWOTH) ? "w" : "-");
        length  = snprintf(Out length, MAXLINE, (res.st_mode & S_IXOTH) ? "x\n" : "-\n");
        length  = snprintf(Out length, MAXLINE, "Owner:\t\t\t%d\n", res.st_gid);
        length  = snprintf(Out length, MAXLINE, "Last status change:\t%s", ctime(&res.st_ctime));
        length  = snprintf(Out length, MAXLINE, "Last file access:\t%s", ctime(&res.st_atime));
        length  = snprintf(Out length, MAXLINE, "Last file modification:\t%s", ctime(&res.st_mtime));

        DIR *d;
        struct dirent *dir;
        d = opendir(szPath);
        if (d != NULL)
        {
            length  = snprintf(Out length, MAXLINE, "Content of %s:\t", szPath);
            while ((dir = readdir(d)) != NULL)
            {
                length  = snprintf(Out length, MAXLINE, "%s\t", dir->d_name);
            }

            closedir(d);
        }
        length  = snprintf(Out length, MAXLINE, "\n");
    }
}

int main()
{
    int sockfd;
    struct SPacket toSend, toRecv;

    struct sockaddr_in servaddr, cliaddr;
       
    // Creating socket file descriptor
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 )
    {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
       
    memset(&servaddr, 0, sizeof(servaddr));
    memset(&cliaddr, 0, sizeof(cliaddr));
       
    // Filling server information
    servaddr.sin_family    = AF_INET; // IPv4
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(PORT);
       
    // Bind the socket with the server address
    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    int len, n;
   
    len = sizeof(cliaddr);  //len is value/resuslt

    while(1)
    {
        n = recv(sockfd, &toRecv, sizeof(toRecv), 0);
        toRecv.msg[n] = '\0';
        // printf("[DEBUG] Client request: %s\n", InMsg);
        GetFileMetadata(toRecv.msg, toSend.msg);
        // printf("[DEBUG] Server answer: %s (len = %d)\n", OutMsg, strlen(OutMsg));
        send(sockfd, &toSend, sizeof(toSend), 0);

        memset(&toRecv, 0, MAXLINE);      //We're preparing buffer for next request
        memset(&toSend, 0, MAXLINE);     //We're preparing buffer for next request
    }
       
    return 0;
}

Client.c

// Client side implementation of UDP client-server model
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdbool.h> //So retarded, need this for bool functions because we compile with C99 standard
   
#define PORT     8080
#define MAXLINE 65527 // 65535 is MAX and other 8 bytes are reserved for header

struct SPacket {
    int len;
    char msg[MAXLINE - sizeof(int)];
};

bool isValidIpAddress(char *ipAddress)
{
    struct sockaddr_in sa;
    int result = inet_pton(AF_INET, ipAddress, &(sa.sin_addr)); //Convert IP address from string to binary
    return result != 0;
}

bool ReadIPsList(char** iplist, int maxLines, int maxLen, int* ipCount)
{
    FILE *fp = fopen("Servers.txt", "r");
    size_t len = 255;

    char *line = malloc(sizeof(char) * len);

    if (fp == NULL)
    {
        printf("Can't open file Servers.txt!\n");
        return false;
    }

    while(fgets(line, len, fp) != NULL)
    {
        line[strlen(line)-1]='\0';    //Remove newline

        if (*ipCount < maxLines && strlen(line) < maxLen && isValidIpAddress(line))
        {
            iplist[*ipCount] = (char *)malloc(16);
            strncpy(iplist[*ipCount], line, maxLen);
            printf("\"%s\" added to IP list\n", iplist[*ipCount]);
        }
        else
        {
            printf("Invalid IP address (%s), line (%d).\n", line, *ipCount);
            return false;
        }

        (*ipCount)  = 1;
    }

    if (*ipCount == 0)
    {
        printf("Servers.txt is empty ?!?\n");
        return false;
    }

    fclose(fp);
    free(line);
    printf("\n");
    return true;
}

bool AskAppendNewServer(char** iplist, int maxLines, int maxLen, int* ipCount)
{
    printf("Do you want to add a new server? (Y/N)\n");
    
    char ans = getchar();

    if (ans == 'y' || ans == 'Y')
    {
        if (*ipCount >= maxLines)
        {
            printf("ERROR! Maximum number of IP addresses has been reached!\n");
            return false;
        }

        FILE *fp = fopen("Servers.txt", "a");

        if (fp == NULL)
        {
            printf("Can't open file Servers.txt!\n");
            return false;
        }


        bool is_ok = false;
        char IP[16   1];
        do {
            printf("\nEnter IP address: ");
            scanf("%s", IP);

            if (isValidIpAddress(IP))
            {
                is_ok = true;
                fprintf(fp, "%s\n", IP);

                iplist[*ipCount] = (char *)malloc(16);
                strncpy(iplist[*ipCount], IP, maxLen);
                printf("\"%s\" added to IP list\n", iplist[*ipCount]);
            }
            else
                printf("ERROR! %s is not a valid IP address!", IP);
        } while (is_ok != true);

        fclose(fp);
        (*ipCount)  = 1;

        return true;
    }

    return false;
}

int main()
{
    int sockfd, ipCount = 0, selIp = 0;
    struct SPacket toSend, toRecv;
    struct sockaddr_in servaddr;

    char* ipList[255];
    if (ReadIPsList(ipList, 255, 16, &ipCount) == 0)
        return -1;
   
    // Creating socket file descriptor
    if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
   
    memset(&servaddr, 0, sizeof(servaddr));

    // Filling server information
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    servaddr.sin_addr.s_addr = inet_addr(ipList[selIp]); //Trying 1st server then we move to a new one if this become unavailable
       
    int n, len;

    //If we don't receive any answer in less than 1 second then is a problem..
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 100000;
    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO,&tv,sizeof(tv)) < 0) {
        perror("Error");
    }

    connect(sockfd, (const struct sockaddr *) &servaddr, sizeof(servaddr));

    while(1)
    {
        char c;
        while((c= getchar()) != '\n' && c != EOF);  // Remove any unwanted character from stdin (scanf from AskAppendNewServer created this issue)
        printf("Enter filename:\t");
        fgets(toSend.msg, MAXLINE, stdin);
        toSend.msg[strcspn(toSend.msg, "\n")] = '\0';    //Remove newline
        toSend.len = strlen(toSend.msg);
        
        if (strlen(toSend.msg) == 0)
            continue;
        
        send(sockfd, &toSend, sizeof(toSend), 0);
            
        if (n = recv(sockfd, &toRecv, sizeof(toRecv), 0) > 0)
        {
            if (strcmp(toSend.msg, toRecv.msg) == 0) // Yep, this file does not exists on host.
            {
                if (AskAppendNewServer(ipList, 255, 16, &ipCount) == true)
                {
                    selIp = ipCount - 1; //We append always at the end of the array
                    printf("Server (%s) has been added successfully. Now you can send your request from it.\n", ipList[selIp]);
                    servaddr.sin_addr.s_addr = inet_addr(ipList[selIp]);
                }
            }
            else
                printf("%s\n", toRecv.msg);
            // printf("[DEBUG] Received msg len: %d\n", strlen(toRecv.msg));
        }
        else
        {
            int counter = 1, foundValidServer = 0;

            while (counter < ipCount && foundValidServer == 0)
            {
                printf("Timeout reached :: server (%s) not available, trying next server available (%s)\n", ipList[selIp], ipList[(selIp 1) % ipCount]);
                counter  = 1;
                selIp = (selIp 1) % ipCount;
                servaddr.sin_addr.s_addr = inet_addr(ipList[selIp]); // Try next available server
                sendto(sockfd, (const char *)toSend.msg, strlen(toSend.msg), MSG_CONFIRM, (const struct sockaddr *) &servaddr, sizeof(servaddr));

                if (n = recvfrom(sockfd, (char *)toRecv.msg, MAXLINE, MSG_WAITALL, (struct sockaddr *) &servaddr, &len) > 0)
                {
                    if (strcmp(toSend.msg, toRecv.msg) == 0)
                        AskAppendNewServer(ipList, 255, 16, &ipCount);
                    else
                        printf("%s\n", toRecv.msg);

                    foundValidServer = 1;
                }
            }
        }

        memset(&toSend.msg, 0, MAXLINE);   //We're preparing buffer for next request
        memset(&toRecv.msg, 0, MAXLINE);   //We're preparing buffer for next request
    }

    free(ipList); //No memory leaks, please.
    close(sockfd);
    return 0;
}

CodePudding user response:

It doesn't work with struct because it isn't meant to. You need to provide a pointer to the char buffer (and not the buffer itself) and the length of the buffer in a separate argument. See this recv() documentation.

If what you want is sending struct data with a socket, try making a string representation within the buffer, and then parse it on receive.

  • Related