17

This is the server (sendfile) part:

offset = 0;
for (size_to_send = fsize; size_to_send > 0; ){
  rc = sendfile(newsockd, fd, &offset, size_to_send);
  if (rc <= 0){
    perror("sendfile");
    onexit(newsockd, sockd, fd, 3);
  }
  offset += rc;
  size_to_send -= rc;
}
close(fd); /* la chiusura del file va qui altrimenti rischio loop infinito e scrittura all'interno del file */

memset(buffer, 0, sizeof(buffer));
strcpy(buffer, "226 File Successfully transfered\n");
if(send(newsockd, buffer, strlen(buffer), 0) < 0){
  perror("Errore durante l'invio 226");
  onexit(newsockd, sockd, 0, 2);
}
memset(buffer, 0, sizeof(buffer));

and this is the part of the client (recv file) part:

    fsize_tmp = fsize;
    sInfo.filebuffer = malloc(fsize);
  if(sInfo.filebuffer == NULL){
    perror("malloc");
    onexit(sockd, 0, fd, 4);
  }

  while(((uint32_t)total_bytes_read != fsize) && ((nread = read(sockd, sInfo.filebuffer, fsize_tmp)) > 0)){
    if(write(fd, sInfo.filebuffer, nread) != nread){
            perror("write RETR");
            onexit(sockd, 0, 0, 1);
        }
        total_bytes_read += nread;
        fsize_tmp -= nread;
    }
  close(fd); /* la chiusura del file va qui altrimenti client entra in loop infinito e si scrive all'interno del file */

    memset(buffer, 0, sizeof(buffer));
    if(recv(sockd, buffer, 34, 0) < 0){
    perror("Errore ricezione 226");
    onexit(sockd, 0, 0, 1);
  }
  printf("%s", buffer);
  memset(buffer, 0, sizeof(buffer));
  memset(dirpath, 0, sizeof(dirpath));
  free(sInfo.filebuffer);

The problem is that the string "226 File etc etc" is written inside the file that has been sent.
I've tried to do a small debug and so i've added a printf after the for loop (server sendfile) and a printf after the while loop (client) and i've noticed that the file is sent but on the client it doesn't exit from the while because the printf isn't printed...
Why i got this strange behaviour??
br>

EDIT:
The server send the file size to the client whit this code:

  fd = open(filename, O_RDONLY);
    if(fd < 0){
    error!!
    }

    if(fstat(fd, &fileStat) < 0){
        perror("Errore fstat");
        onexit(newsockd, sockd, fd, 3);
    }
    fsize = fileStat.st_size;
    if(send(newsockd, &fsize, sizeof(fsize), 0) < 0){
      perror("Errore durante l'invio della grandezza del file\n");
      onexit(newsockd, sockd, fd, 3);
     }

the client receives the fsize from the server with this code:

if(read(sockd, &fsize, sizeof(fsize)) < 0){
    perror("Errore durante ricezione grandezza file\n");
    onexit(sockd, 0 ,0 ,1);
}
fd = open(sInfo.filename, O_CREAT | O_WRONLY, 0644);
if (fd  < 0) {
    perror("open");
    onexit(sockd, 0 ,0 ,1);
}
fsize_tmp = fsize;

both fsize are declared as uint32_t...

polslinux
  • 1,739
  • 9
  • 34
  • 73
  • Possible duplicate of [Send and Receive a file in socket programming in Linux with C/C++ (GCC/G++)](https://stackoverflow.com/questions/2014033/send-and-receive-a-file-in-socket-programming-in-linux-with-c-c-gcc-g) – Ciro Santilli OurBigBook.com Jun 15 '17 at 05:40

5 Answers5

29

Try this code:

Client side:

/* Client code */
/* TODO : Modify to meet your need */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>

#define PORT_NUMBER     5000
#define SERVER_ADDRESS  "192.168.1.7"
#define FILENAME        "/home/toc/foo.c"

int main(int argc, char **argv)
{
        int client_socket;
        ssize_t len;
        struct sockaddr_in remote_addr;
        char buffer[BUFSIZ];
        int file_size;
        FILE *received_file;
        int remain_data = 0;

        /* Zeroing remote_addr struct */
        memset(&remote_addr, 0, sizeof(remote_addr));

        /* Construct remote_addr struct */
        remote_addr.sin_family = AF_INET;
        inet_pton(AF_INET, SERVER_ADDRESS, &(remote_addr.sin_addr));
        remote_addr.sin_port = htons(PORT_NUMBER);

        /* Create client socket */
        client_socket = socket(AF_INET, SOCK_STREAM, 0);
        if (client_socket == -1)
        {
                fprintf(stderr, "Error creating socket --> %s\n", strerror(errno));

                exit(EXIT_FAILURE);
        }

        /* Connect to the server */
        if (connect(client_socket, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr)) == -1)
        {
                fprintf(stderr, "Error on connect --> %s\n", strerror(errno));

                exit(EXIT_FAILURE);
        }

        /* Receiving file size */
        recv(client_socket, buffer, BUFSIZ, 0);
        file_size = atoi(buffer);
        //fprintf(stdout, "\nFile size : %d\n", file_size);

        received_file = fopen(FILENAME, "w");
        if (received_file == NULL)
        {
                fprintf(stderr, "Failed to open file foo --> %s\n", strerror(errno));

                exit(EXIT_FAILURE);
        }

        remain_data = file_size;

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

        close(client_socket);

        return 0;
}

Server side:

/* Server code */
/* TODO : Modify to meet your need */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sendfile.h>

#define PORT_NUMBER     5000
#define SERVER_ADDRESS  "192.168.1.7"
#define FILE_TO_SEND    "hello.c"

int main(int argc, char **argv)
{
        int server_socket;
        int peer_socket;
        socklen_t       sock_len;
        ssize_t len;
        struct sockaddr_in      server_addr;
        struct sockaddr_in      peer_addr;
        int fd;
        int sent_bytes = 0;
        char file_size[256];
        struct stat file_stat;
        int offset;
        int remain_data;

        /* Create server socket */
        server_socket = socket(AF_INET, SOCK_STREAM, 0);
        if (server_socket == -1)
        {
                fprintf(stderr, "Error creating socket --> %s", strerror(errno));

                exit(EXIT_FAILURE);
        }

        /* Zeroing server_addr struct */
        memset(&server_addr, 0, sizeof(server_addr));
        /* Construct server_addr struct */
        server_addr.sin_family = AF_INET;
        inet_pton(AF_INET, SERVER_ADDRESS, &(server_addr.sin_addr));
        server_addr.sin_port = htons(PORT_NUMBER);

        /* Bind */
        if ((bind(server_socket, (struct sockaddr *)&server_addr, sizeof(struct sockaddr))) == -1)
        {
                fprintf(stderr, "Error on bind --> %s", strerror(errno));

                exit(EXIT_FAILURE);
        }

        /* Listening to incoming connections */
        if ((listen(server_socket, 5)) == -1)
        {
                fprintf(stderr, "Error on listen --> %s", strerror(errno));

                exit(EXIT_FAILURE);
        }

        fd = open(FILE_TO_SEND, O_RDONLY);
        if (fd == -1)
        {
                fprintf(stderr, "Error opening file --> %s", strerror(errno));

                exit(EXIT_FAILURE);
        }

        /* Get file stats */
        if (fstat(fd, &file_stat) < 0)
        {
                fprintf(stderr, "Error fstat --> %s", strerror(errno));

                exit(EXIT_FAILURE);
        }

        fprintf(stdout, "File Size: \n%d bytes\n", file_stat.st_size);

        sock_len = sizeof(struct sockaddr_in);
        /* Accepting incoming peers */
        peer_socket = accept(server_socket, (struct sockaddr *)&peer_addr, &sock_len);
        if (peer_socket == -1)
        {
                fprintf(stderr, "Error on accept --> %s", strerror(errno));

                exit(EXIT_FAILURE);
        }
        fprintf(stdout, "Accept peer --> %s\n", inet_ntoa(peer_addr.sin_addr));

        sprintf(file_size, "%d", file_stat.st_size);

        /* Sending file size */
        len = send(peer_socket, file_size, sizeof(file_size), 0);
        if (len < 0)
        {
              fprintf(stderr, "Error on sending greetings --> %s", strerror(errno));

              exit(EXIT_FAILURE);
        }

        fprintf(stdout, "Server sent %d bytes for the size\n", len);

        offset = 0;
        remain_data = file_stat.st_size;
        /* Sending file data */
        while (((sent_bytes = sendfile(peer_socket, fd, &offset, BUFSIZ)) > 0) && (remain_data > 0))
        {
                fprintf(stdout, "1. Server sent %d bytes from file's data, offset is now : %d and remaining data = %d\n", sent_bytes, offset, remain_data);
                remain_data -= sent_bytes;
                fprintf(stdout, "2. Server sent %d bytes from file's data, offset is now : %d and remaining data = %d\n", sent_bytes, offset, remain_data);
        }

        close(peer_socket);
        close(server_socket);

        return 0;
}

EDIT : Adding explanation from the man about the offset

The man page of send file said:

If offset is not NULL, then it points to a variable holding the file offset from which sendfile() will start reading data from in_fd.
When sendfile() returns, this variable will be set to the offset of the byte following the last byte that was read.

James Cook
  • 7
  • 1
  • 4
TOC
  • 4,326
  • 18
  • 21
  • i've solved my problem :D thanks a lot! My error was that i've used "&fsize" instead of a char array to receive the size of file :) – polslinux Aug 16 '12 at 06:03
  • @polslinux : Welcome :-. ok great.there was also a problem about incrementing the offset on the server side. – TOC Aug 16 '12 at 06:11
  • @polslinux : Yes, in your code you increment the offset (offset += rc;), there is no need for this (in my code "Server side" i give you two fprintf to see what's going to the offset when sending data) – TOC Aug 16 '12 at 06:59
  • 8
    I test the code to send a simple text file with hello in it. However, the file I received on the client size is always blank. After referring to this post http://tldp.org/LDP/LGNET/91/misc/tranter/server.c.txt and read the warning, I find the warning that says offset variable should not be int, so I change offset type from int to off_t, and the sendfile works fine. – juejiang Oct 12 '15 at 13:44
0

The client doesn't know when the file ends. It just reads on until it has received fsize bytes. In your current implementaton the client will only work for files that are exactly fsize bytes.

I suggest that you change your protocol and add a header that contains the file size. Why not use the http protocol?

Klas Lindbäck
  • 33,105
  • 5
  • 57
  • 82
  • i have no idea on what to do to use http xD – polslinux Aug 14 '12 at 15:46
  • I've read your update. Seems the only thing missing is the actual `send` command for the file size. – Klas Lindbäck Aug 14 '12 at 20:48
  • you're right! i've updated my question! i've also found the problem: the server send the right fsize but the client receive a very big fsize :( :( for example if fsize is 1048 on the server, the client receives 20209320 O.o – polslinux Aug 14 '12 at 21:28
  • Server is Linux and client is Windows? Use `htonl` on the sending side to convert the size to network byte order. Use `ntohl` on the receiving side to convert it from network to host byte order. – Klas Lindbäck Aug 15 '12 at 06:35
  • both client and server are on Ubuntu 12.04! – polslinux Aug 15 '12 at 09:13
0

I think you should provide a more detailed code with at least the used variables declarations and initialisation.

How does the client part get the fsize value ?

You should also check the while condition like this :

while(((uint32_t)total_bytes_read < fsize) && ( .....

Don't use "!=", because if (for an unknown reason) total_bytes_read become greater than fsize, you'll be stucked in an infinite loop until the socket connection is closed (and read returns an error).

I also think (but not sure) that you should use recv instead of read in your client part.

phsym
  • 1,364
  • 10
  • 20
0

You should always determine the size of file you're sending as part of the protocol, for example, you can send the file size as the first 4 (or more, depending on file sizes you expect to handle) bytes, prior to the actual stream.

If you wish to work with constant size files, your implementation should work, in this case please add prints for fsize and total_bytes_read.

0

You are using the sendfile API incorrectly. Since you are passing in a non-NULL value in the third parameter, sendfile will update the offset for you. But because your send loop is also updating the offset, you will be skipping over some bytes in the case that sendfile was not able to send the whole file in a single call.

From the man page:

If offset is not NULL, then it points to a variable holding the file offset from which sendfile() will start reading data from in_fd. When sendfile() returns, this variable will be set to the offset of the byte following the last byte that was read.

You should remove this line from your send loop:

  offset += rc;

Edit: In your update, you use fpl to get the fstat information from your file, but in your sendfile code, you use fd. I would make sure these are what you expect them to be. (This was fixed in yet another update.) In any case, I have written a test program using the code you provided (with my suggested fix), and it seems to work fine with a less than 2KB file and a 3MB file.

jxh
  • 69,070
  • 8
  • 110
  • 193
  • doesn't works...the string "226 etc etc" is printed inside the file :( – polslinux Aug 14 '12 at 15:45
  • also i think that use a var is correct because `man sendfile` report: _If offset is not NULL, then it points to a variable holding the file offset from which sendfile() will start reading data from in_fd._ – polslinux Aug 14 '12 at 15:47
  • @polslinux: I updated the answer with the full quote from the man page. – jxh Aug 14 '12 at 15:52
  • @polslinux: Also, as others have asked you to do, please provide some indication of how you make sure the `fsize` value is the same for the sender and the receiver. – jxh Aug 14 '12 at 16:09
  • I'm sorry i've copied the wrong part xD i've updated my question! – polslinux Aug 14 '12 at 17:23
  • however these are 2 different programs, 1 for send and 1 for receive :) – polslinux Aug 14 '12 at 17:24
  • @polslinux: You should be able to take my program to make two different programs. The lines of code that gets executed is the same whether or not you split it into separate programs. – jxh Aug 14 '12 at 17:26
  • i've found the problem: the client receives a wrong fsize. For example if the file size is 1048 the server send 1048 but the client receives 20203902 – polslinux Aug 14 '12 at 21:29
  • @polslinux: If you are operating in a heterogeneous environment, that is an important fact to omit from the description of your problem. But, good job tracking it down. – jxh Aug 14 '12 at 22:08
  • yes i'm sorry! i'm operating on Ubuntu 12.04 with both of them (client and server) – polslinux Aug 15 '12 at 09:14