0

I have implemented the mget command using socket. But the output i am getting is having some random behavior. Sometime i am able to download the whole file on client, sometimes i am able to download files partially, sometime server code gives segmentation fault and sometimes client goes into infinite loop.

Server Code:

      /* A simple server in the internet domain using TCP
   The port number is passed as an argument */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>

int main(int argc, char *argv[])
{
     int sockfd, newsockfd, portno;
     socklen_t clilen;
//     char buffer[256];
     struct sockaddr_in serv_addr, cli_addr;
     int n;
     if (argc < 2) {
         fprintf(stderr,"ERROR, no port provided\n");
         exit(1);
     }
     sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
     if (sockfd < 0) 
        error("ERROR opening socket");
     bzero((char *) &serv_addr, sizeof(serv_addr));
     portno = atoi(argv[1]);
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);
     if (bind(sockfd, (struct sockaddr *) &serv_addr,
              sizeof(serv_addr)) < 0) 
              error("ERROR on binding");
     listen(sockfd,5);
     clilen = sizeof(cli_addr);
     newsockfd = accept(sockfd, 
                 (struct sockaddr *) &cli_addr, 
                 &clilen);
     if (newsockfd < 0) 
          error("ERROR on accept");
     /*bzero(buffer,256);
     n = read(newsockfd,buffer,255);
     if (n < 0) error("ERROR reading from socket");
     printf("Here is the message: %s\n",buffer);
     n = write(newsockfd,"I got your message",18);
     if (n < 0) error("ERROR writing to socket");*/

     char command[512];
     bzero(command, 512);

    char buffer[4096];
     char output[5000];
     char f_size[20];

     int send_bytes;
     int written_bytes;
     int total_sent_bytes = 0;

     long int file_size;

     n = recv(newsockfd, command, 512, 0);  //try with MSG_WAITALL
     if(n < 0)error("Error receiving command");
     puts(command); //only file name

     FILE * fp;
     fp = fopen(command, "rb");
     if(fp == NULL)error("Can not open requested file");

     FILE * test_file_fp;
     test_file_fp = fopen("test_file.txt", "wb");
     if(test_file_fp == NULL)error("Can not open test file");

     fseek(fp,0, SEEK_END);
     file_size = ftell(fp);
     rewind(fp);

     sprintf(f_size,"%ld", file_size);

     send(newsockfd, f_size, strlen(f_size), 0);

     while(!feof(fp)){
         bzero(buffer, 4096);

         n = fread(buffer, sizeof(char), 4095, fp);
         sprintf(output, "read bytes using fread = %d", n);
         puts(output);

         send_bytes = send(newsockfd, buffer, strlen(buffer) + 1, MSG_MORE);
         total_sent_bytes += send_bytes;
         sprintf(output, "sent bytes using send = %d", send_bytes);
         puts(output);

         written_bytes = fwrite(buffer, sizeof(char), strlen(buffer), test_file_fp);
         sprintf(output, "written bytes using fwrite = %d", written_bytes);
         puts(output);

        //bzero(command, 512);

        //recv(newsockfd, buffer, 512, 0);

        puts("\n");
     }

     sprintf(output, "total sent bytes using send = %d\n", total_sent_bytes);
     puts(output);

     send(newsockfd, NULL, 1, 0);

     fclose(test_file_fp);
     fclose(fp);
     close(newsockfd);
     close(sockfd);
     return 0; 
}

Client Code:

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

void error(const char *msg)
{
    perror(msg);
    exit(0);
}

int main(int argc, char *argv[])
{
    int sockfd, portno, n;
    struct sockaddr_in serv_addr;
    struct hostent *server;
    int i;
//    char buffer[5000];
    if (argc < 3) {
       fprintf(stderr,"usage %s hostname port\n", argv[0]);
       exit(0);
    }
    portno = atoi(argv[2]);
    sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockfd < 0)
        error("ERROR opening socket");
    server = gethostbyname(argv[1]);
    if (server == NULL) {
        fprintf(stderr,"ERROR, no such host\n");
        exit(0);
    }
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr,
         (char *)&serv_addr.sin_addr.s_addr,
         server->h_length);
    serv_addr.sin_port = htons(portno);
    if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        error("ERROR connecting");


    char command[512];
    char output[5096];

    int send_command_bytes;
    int reveived_bytes;
    char buffer[4096];

    long int total_bytes;
    long int total_received_bytes;

    int written_bytes;

    sprintf(output, "IP Protocol Number = %d", IPPROTO_TCP);
     puts(output);

    FILE * fp;

    fp = fopen("received_file.txt", "wb");
    if(fp == NULL)error("Could not open file for receiving data");

    memcpy(command, "send_me.txt", 11); 

    send_command_bytes = send(sockfd, command, strlen(command), 0);
    puts(command);

    recv(sockfd, buffer, 4096, 0);
    total_bytes = atoi(buffer);

    sprintf(output, "Total bytes to be received = %ld", total_bytes);
    puts(output);

    total_received_bytes = 0;

    while(totala_received_bytes < total_bytes){
        bzero(buffer, 4096);
        reveived_bytes = recv(sockfd, buffer, 4095, 0); //try with MSG_WAITALL, MSG_DONTWAIT
        total_received_bytes += reveived_bytes;
        sprintf(output, "Number of bytes received = %d", reveived_bytes);
        puts(output);

        written_bytes = fwrite(buffer, sizeof(char), strlen(buffer), fp);
        sprintf(output, "Total written bytes = %d", written_bytes);
        puts(output);

        //send(sockfd, "1", 2, 0);

        sprintf(output, "total Number of bytes received so far = %ld", total_received_bytes);
        puts(output);
        puts("\n");
    }
    fclose(fp);
    close(sockfd);
    return 0;
}

From client i am sending the file name "send_me.txt" which needs to be downloaded and this filename is being read by server in a buffer named command. This file is being opened in server and then read and then sent to client. I have tried many possibilities to overcome this issue but the issue still persists.

Kenster
  • 23,465
  • 21
  • 80
  • 106

1 Answers1

0

Your code has both of the common problems when dealing with communications over sockets

1. Lack of NUL Termination
Strings in C are NUL terminated, so either you need to send the NUL as part of the message, or you need to add the NUL on the receive side.

2. Assumptions about message sizes
It's a common misconception in socket programming that each recv will match the corresponding send. For example, if I send 10 bytes, followed by 50 bytes, and later 20 bytes, then the expectation is that recv will need to be called three times, and will return 10 bytes, 50 bytes, and 20 bytes. That is completely, utterly, totally wrong. In fact, the first recv will return anywhere from 1 byte to 80 bytes. That is, it may return any portion of the data from the first send, up to all of the data from all of the sends.

Example from your code
In the server code, you do this

 sprintf(f_size,"%ld", file_size);
 send(newsockfd, f_size, strlen(f_size), 0);

If the file_size is 123, then the sprintf will write 1, 2, 3, \0 to f_size, i.e. three digits and a NUL terminator. The strlen will return 3, so the code only sends the three digits but doesn't send the NUL terminator.

On the client side, you have this

recv(sockfd, buffer, 4096, 0);
total_bytes = atoi(buffer);

Since the server doesn't send the NUL byte and the client doesn't add the NUL byte, the atoi will see whatever the recv put in the buffer, followed by garbage. If you're lucky, the first garbage character won't be a digit, and atoi will work correctly. If you're unlucky the first garbage character will be a digit.

In addition to the missing NUL, the recv may only get 1 byte, in which case the file size is 1 (assuming the first garbage character is not a digit). Or the recv may get 100 bytes (the 3 byte length plus some of the bytes from the file). If the file happens to start with digits, the size will be wrong.

Fixing the code
On the server side, send the file size as a fixed length message with the NUL included, e.g.

#define MSGLEN 24
char buffer[MSGLEN];
snprintf( buffer, MSGLEN, "%*ld", MSGLEN-1, file_size );
send( sockfd, buffer, MSGLEN, 0 );

On the client side, read in a loop until you have all of the bytes for the size, but no more

#define MSGLEN 24
char buffer[MSGLEN];
int count = 0;
int index;
for ( index = 0; index < MSGLEN; index += count )
{
    count = recv( sockfd, &buffer[index], MSGLEN - index, 0 );
    if ( count <= 0 )
        break;
}
if ( index != MSGLEN || buffer[MSGLEN-1] != '\0' )
{
    printf( "bad file size\n" );
    exit( 1 );
}
total_bytes = atoi( buffer );

One final note
while(!feof(fp))-is-always-wrong

Community
  • 1
  • 1
user3386109
  • 34,287
  • 7
  • 49
  • 68