Search code examples
cloopssocketsudpfile-transfer

UDP File Transfer Program in C, While loops won't do anything


I am trying to make a file transfer program with UDP, using a stop and wait protocol. The client prompts user to enter the text file to transfer and if exist, the server will find it and send it back in packets (80 char at a time). The part where the client sends the file name and the server receives it works, but once I get into the while(1) loop for the file transfer, nothing happens. Not sure why this is the case, any help will be great! thanks!

Client.c

`#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <errno.h>

/*This source file lies the client side of the file transfer. Its job is to open a
socket and ask to connect with server
It should then ask user for input of the text file they wish to transfer.
Once server sends out the file in packets, the client will then place this data in an
output.txt file, viewable for the user*/

typedef struct packet{
    char pktData[80]; //data in each packet
}Packet;

typedef struct frame{
    int frame_kind; //ACK:0, SEQ:1 FIN:2
    int countOfChar; //count of characters in a packet
    int sq_no;
    int ack;
    Packet packet;
}Frame;

int offset = 4;

int main(){
    int sockfd = 0; //socket descriptor
    int countOfChar = 0; //count of characters in a packet
    char buffer[80]; //data in each packet
    char fileName[20];
    char pktData[80];

    //socklen_t addr_size;

    int frame_id = 0;
    int frame_id2 = 0;
    Frame frame_send; //frame being sent to server (for the filename)
    Frame frame_recv; //frame being received from the server (for the data)
    Frame name_send; 
    Frame name_recv;
    int ack_recv = 1;
    
    struct sockaddr_in serv_addr;
    struct sockaddr_in new_addr;
    memset(&serv_addr, '0', sizeof(serv_addr));
    
    
    // Open a socket
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
        printf("Error creating socket");
        return 1;
    }
    // Server address structure
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(20000);
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    socklen_t addr_size = sizeof(serv_addr);

    //Sending File name
    printf("\nConnected to server successfully!\nEnter the file name: ");
    scanf("%[^\n]%*c",fileName);
    printf("\n");
    char buff[20];
    strcpy(buff, fileName);

    sendto(sockfd, &buff, sizeof(buff), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

 
        int new_addr_size = sizeof(new_addr);
        //int addr_size = sizeof(serv_addr);
        //writing file into output.txt
        FILE *fp = fopen("output.txt", "ab");

        //loop until theres no more to read
        while(1){
        int f_recv_size = recvfrom(sockfd, &frame_recv, sizeof(Frame), 0, (struct sockaddr*)&serv_addr, &addr_size);
        if (f_recv_size > 0 && frame_recv.frame_kind == 1 && frame_recv.sq_no == frame_id){
            fwrite(frame_recv.packet.pktData, 1, sizeof(frame_recv.packet.pktData), fp);
            //printf("[+]Frame %d received with %lu data bytes\n",frame_recv.sq_no, sizeof(frame_recv.packet.pktData));
            printf("Frame Received");
            
            frame_send.sq_no = 0;
            frame_send.frame_kind = 0;
            frame_send.ack = frame_recv.sq_no + 1;
            sendto(sockfd, &frame_send, sizeof(frame_send), 0, (struct sockaddr*)&serv_addr, addr_size);
            printf("[+]Ack Sent\n");
        }else{
            printf("[+]Frame Not Received\n");
        }
        frame_id++; 
        }
close(sockfd);
    return 0;

}

Server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/*This source file lies the server side of the file transfer. Its job is to listen
for a connection and connect with client when prompted.
It should then read the file with the txt file name and start sending out packets of 
80 bytes each with the data of the given file name.*/

typedef struct packet{
    char pktData[80]; //data in each packet
}Packet;

typedef struct frame{
    int frame_kind; //ACK:0, SEQ:1 FIN:2
    int countOfChar; //count of characters in a packet
    int sq_no;
    int ack;
    Packet packet;
}Frame;

int main(){

    unsigned int size = 0;
    int listenfd = 0;
    int connfd = 0;
    char file_buff[20];
    char buffer[80];
    char FLAG[] = "NF"; //No file flag

    int frame_id = 0;
    int frame_id2 = 0;
    Frame name_recv;
    Frame name_send;
    Frame frame_recv;
    Frame frame_send;
    int ack_recv;

    struct sockaddr_in server_addr, client_addr;
    char sendBuff[1025];
    size = sizeof(client_addr);

    listenfd = socket(AF_INET, SOCK_DGRAM, 0);  //listening descriptor 
    printf("Server initiated...\n");

    memset(&server_addr, '0', sizeof(server_addr));
    memset(sendBuff, '0', sizeof(sendBuff));

    //Local address structure
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(20000);

    //bind to local address
    bind(listenfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    
     // Receiving File Name
    recvfrom(listenfd, &file_buff, sizeof(file_buff), 0, (struct sockaddr*)&client_addr, &size);
    printf("Filename requested by client = %s\n",file_buff);

        if (access(file_buff, F_OK) != -1 ) { 
            printf("File exist!\n");
            FILE *fp = fopen(file_buff,"rb"); 
            while(1){
        
                if(ack_recv == 1){
                frame_send.sq_no = frame_id;
                frame_send.frame_kind = 1;
                frame_send.ack = 0;     
    
                fscanf(fp, "%s ", buffer);
                strcpy(frame_send.packet.pktData, buffer);
                sendto(listenfd, &frame_send, sizeof(Frame), 0, (struct sockaddr*)&client_addr, sizeof(client_addr));
                //printf("[+]Frame %d Sent with %lu data bytes\n",frame_send.sq_no, strlen(frame_send.packet.pktData));
                printf("Frame Sent");
                }
            int addr_size = sizeof(server_addr);
            int f_recv_size = recvfrom(listenfd, &frame_recv, sizeof(frame_recv), 0 ,(struct sockaddr*)&client_addr, &size);
        
            if( f_recv_size > 0 && frame_recv.sq_no == 0 && frame_recv.ack == frame_id+1){
                printf("[+]Ack Received\n");
                ack_recv = 1;
                }   
            else{
                printf("[-]Ack Not Received\n");
                ack_recv = 0;
                }   
            
            frame_id++;     
            }
        }
        else {
            printf("File doesn't exist\n");
            write(listenfd, FLAG, sizeof(FLAG));
            close(listenfd);
            return 0;
        }
 close(listenfd);
    return 0;

}

Sorry for all the clutter and unnecessary variables in advanced, I've been trying so many options but nothing's working.

Server's response in terminal

Client's response in terminal


Solution

  • The first problem is that you never initialize ack_recv in the server program before attempting to read it. Reading an uninitialized variable that hasn't had its address taken triggers undefined behavior.

    At the very least, a value besides 1 was read so the server is waiting on a read from the client which never comes because the client is waiting on the server. You need to initialize ack_recv to 1 so that the server sends the first part of the file.

    The code has more problems than this, however.

    The main problem is it assumes that no packets get lost. UDP doesn't guarantee delivery. So if a packet does get lost, one side will be stuck forever waiting for a packet that doesn't arrive. When reading, each side should use select to allow for a timeout while waiting. If the timeout triggers, it should assume that the last packet sent was lost and resend it.

    For the client, this also means that it can get multiple copies of a given sequence number from the server if an ACK gets lost. So it should not increment frame_id until it receives that sequence number.

    There's also an infinite loop on both sides, as there is a while (1) loop with no break, return, or exit inside. The server needs to know when to stop sending, and it needs some way to let the client know when it's done.

    Also, as a general rule, when debugging network programs you should use tcpdump or wireshark to trace the packets traveling in both directions.