Send and Receive a File in Socket Programming in Linux with C/C++ (Gcc/G++)

Send and Receive a file in socket programming in Linux with C/C++ (GCC/G++)

The most portable solution is just to read the file in chunks, and then write the data out to the socket, in a loop (and likewise, the other way around when receiving the file). You allocate a buffer, read into that buffer, and write from that buffer into your socket (you could also use send and recv, which are socket-specific ways of writing and reading data). The outline would look something like this:

while (1) {
// Read data into buffer. We may not have enough to fill up buffer, so we
// store how many bytes were actually read in bytes_read.
int bytes_read = read(input_file, buffer, sizeof(buffer));
if (bytes_read == 0) // We're done reading from the file
break;

if (bytes_read < 0) {
// handle errors
}

// You need a loop for the write, because not all of the data may be written
// in one call; write will return how many bytes were written. p keeps
// track of where in the buffer we are, while we decrement bytes_read
// to keep track of how many bytes are left to write.
void *p = buffer;
while (bytes_read > 0) {
int bytes_written = write(output_socket, p, bytes_read);
if (bytes_written <= 0) {
// handle errors
}
bytes_read -= bytes_written;
p += bytes_written;
}
}

Make sure to read the documentation for read and write carefully, especially when handling errors. Some of the error codes mean that you should just try again, for instance just looping again with a continue statement, while others mean something is broken and you need to stop.

For sending the file to a socket, there is a system call, sendfile that does just what you want. It tells the kernel to send a file from one file descriptor to another, and then the kernel can take care of the rest. There is a caveat that the source file descriptor must support mmap (as in, be an actual file, not a socket), and the destination must be a socket (so you can't use it to copy files, or send data directly from one socket to another); it is designed to support the usage you describe, of sending a file to a socket. It doesn't help with receiving the file, however; you would need to do the loop yourself for that. I cannot tell you why there is a sendfile call but no analogous recvfile.

Beware that sendfile is Linux specific; it is not portable to other systems. Other systems frequently have their own version of sendfile, but the exact interface may vary (FreeBSD, Mac OS X, Solaris).

In Linux 2.6.17, the splice system call was introduced, and as of 2.6.23 is used internally to implement sendfile. splice is a more general purpose API than sendfile. For a good description of splice and tee, see the rather good explanation from Linus himself. He points out how using splice is basically just like the loop above, using read and write, except that the buffer is in the kernel, so the data doesn't have to transferred between the kernel and user space, or may not even ever pass through the CPU (known as "zero-copy I/O").

c send and receive file

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.

File transfer server/client using socket

Some comments in no particular order:

  • You're passing up the opportunity to know exact errors too often:

    if(listen(sockfd,BACKLOG) == -1)
    {
    printf("ERROR: Failed to listen Port %d.\n", PORT);
    return (0);
    }

    This block should definitely include a perror("listen") or something similar. Always include perror() or strerror() in every error handling block when the error details will be reported via errno. Having exact failure reasons will save you hours when programming and will save you and your users hours when things don't work as expected in the future.

  • Your error handling needs some further standardizing:

    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 )
    {
    printf("ERROR: Failed to obtain Socket Descriptor.\n");
    return (0);
    }

    This should not return 0 because that will signal to the shell that the program ran to completion without error. You should return 1 (or use EXIT_SUCCESS and EXIT_FAILURE) to signal an abnormal exit.

     else 
    printf("[Server] Server has got connected from %s.\n", inet_ntoa(addr_remote.sin_addr));

    /*Receive File from Client */

    In this preceding block you've gotten an error condition but continue executing anyway. That's a quick way to get very undesirable behavior. This should either re-start the main server loop or exit the child process or something similar. (Depends if you keep the multi-process server.)

    if(!fork())
    {

    The preceding block forgot to account for fork() failing. fork() can, and does fail -- especially in shared hosting environments common at universities -- so you should be prepared for the full, complicated three possible return values from fork(): failure, child, parent.

  • It appears you're using fork() indiscriminately; your client and server are both very simple and the way they are designed to run means they cannot be used to service multiple clients simultaneously. You should probably stick to exactly one process for each, at least until the algorithm is perfectly debugged and you figure out some way to run multiple clients simultaneously. I expect this is the source of the problem you're encountering now.

  • You need to use functions to encapsulate details; write a function to connect to the server, a function to send the file, a function to write the file, etc. Write a function to handle the complicated partial writes. (I especially recommend stealing the writen function from the Advanced Programming in the Unix Environment book's source code. File lib/writen.c.) If you write the functions correctly you can re-use them in both the client and server. (Something like placing them in utils.c and compiling the programs like gcc -o server server.c utils.c.)

    Having smaller functions that each do one thing will allow you to focus on smaller amounts of code at a time and write little tests for each that will help you narrow down which sections of code still need improvement.

Cannot use send() to send .exe in c programming

You'll need to implement that functionality yourself, or use a library that does so. send(2) just sends a raw byte buffer. If you want to send a file, you'll need to use some sort of protocol that both the client and the server understand.

The simplest possible protocol might be to have the client send the filename, the length of the file, and then the raw file data. For example (error checking omitted for clarity):

// Server:
const char *filename = ...;
uint32_t filenameLen = strlen(filename);
uint32_t filenameLenNet = htonl(filenameLen);
send(fd, &filenameLenNet, sizeof(filenameLenNet), 0);
send(fd, filename, filenameLen, 0);

// You should probably use a 64-bit file length; also, for large files, you
// don't want to read the entire file into memory, you should just stream it
// from disk to network in reasonably-sized chunks.
const void *fileData = ...;
uint32_t fileLen = ...;
uint32_t fileLenNet = htonl(fileLen);
send(fd, &fileLenNet, sizeof(fileLenNet), 0);
send(fd, fileData, fileLen, 0);

// Client:
char *filename;
uint32_t filenameLen;
recv(fd, &filenameLen, sizeof(filenameLen), 0);
filenameLen = ntohl(filenameLen);
filename = malloc(filenameLen + 1);
recv(fd, filename, filenameLen, 0);
filename[filenameLen] = 0;
uint32_t fileLen;
recv(fd, &fileLen, sizeof(fileLen), 0);
fileLen = ntohl(fileLen);
void *fileData = malloc(fileLen);
recv(fd, fileData, fileLen, 0);
// Write file Data to the output file. Again, for large files, it would be
// better to stream it in in chunks instead of reading it all in at once.

Also, be careful about partial sends/receives. Make sure you check the return values from send and recv, since they can (and do) often only send or receive a subset of the data you intent to send or receive. When that happens, you have to recover by resending or rereceiving in a loop until you've processed all the data you intended or an error occurs.

As an alternative to using your own protocol, you can use an existing protocol with a library such as libftp or libcurl.

my c++ client/server file exchange implementation is very slow...why?

Skip the acknowledgement of buffers! You insert an artificial round trip (server->client+client->server) for probably each single packet.

This slows down the transfer.

You do not need this ack. You are using TCP, which gives you a reliable stream. Send the number of bytes, then send the whole file. Do not read after send and so on.

EDIT: As a second step, you should increase the buffer size. For internet transfer you can assume an MTU of 1500, so there will be space for a payload of 1452 bytes in each IP packet. This should be your minimal buffer size. Make it larger and let the operating system slice the buffers into packets for you. For LAN you have a much higher MTU.



Related Topics



Leave a reply



Submit