This is for a Linux system, in C. It involves network programming. It is for a file transfer program.
I’ve been having this problem where this piece of code works unpredictably. It either is completely successful, or the while loop in the client never ends. I discovered that this is because the fileLength variable would sometimes be a huge (negative or positive) value, which I thought was attributed to making some mistake with ntohl. When I put in a print statement, it seemed to work perfectly, without error.
Here is the client code:
//...here includes relevant header files int main (int argc, char *argv[]) { //socket file descriptor int sockfd; if (argc != 2) { fprintf (stderr, "usage: client hostnamen"); exit(1); } //...creates socket file descriptor, connects to server //create buffer for filename char name[256]; //recieve filename into name buffer, bytes recieved stored in numbytes if((numbytes = recv (sockfd, name, 255 * sizeof (char), 0)) == -1) { perror ("recv"); exit(1); } //Null terminator after the filename name[numbytes] = ''; //length of the file to recieve from server long fl; memset(&fl, 0, sizeof fl); //recieve filelength from server if((numbytes = recv (sockfd, &fl, sizeof(long), 0)) == -1) { perror ("recv"); exit(1); } //convert filelength to host format long fileLength = ntohl(fl); //check to make sure file does not exist, so that the application will not overwrite exisitng files if (fopen (name, "r") != NULL) { fprintf (stderr, "file already present in client directoryn"); exit(1); } //open file called name in write mode FILE *filefd = fopen (name, "wb"); //variable stating amount of data recieved long bytesTransferred = 0; //Until the file is recieved, keep recieving while (bytesTransferred < fileLength) { printf("transferred: %dntotal: %dn", bytesTransferred, fileLength); //set counter at beginning of unwritten segment fseek(filefd, bytesTransferred, SEEK_SET); //buffer of 256 bytes; 1 byte for byte-length of segment, 255 bytes of data char buf[256]; //recieve segment from server if ((numbytes = recv (sockfd, buf, sizeof buf, 0)) == -1) { perror ("recv"); exit(1); } //first byte of buffer, stating number of bytes of data in recieved segment //converting from char to short requires adding 128, since the char ranges from -128 to 127 short bufLength = buf[0] + 128; //write buffer into file, starting after the first byte of the buffer fwrite (buf + 1, 1, bufLength * sizeof (char), filefd); //add number of bytes of data recieved to bytesTransferred bytesTransferred += bufLength; } fclose (filefd); close (sockfd); return 0; }
This is the server code:
//...here includes relevant header files int main (int argc, char *argv[]) { if (argc != 2) { fprintf (stderr, "usage: server filenamen"); exit(1); } //socket file descriptor, file descriptor for specific client connections int sockfd, new_fd; //...get socket file descriptor for sockfd, bind sockfd to predetermined port, listen for incoming connections //...reaps zombie processes printf("awaiting connections...n"); while(1) { //...accepts any incoming connections, gets file descriptor and assigns to new_fd if (!fork()) { //close socket file discriptor, only need file descriptor for specific client connection close (sockfd); //open a file for reading FILE *filefd = fopen (argv[1], "rb"); //send filename to client if (send (new_fd, argv[1], strlen (argv[1]) * sizeof(char), 0) == -1) { perror ("send"); } //put counter at end of selected file, and find length fseek (filefd, 0, SEEK_END); long fileLength = ftell (filefd); //convert length to network form and send it to client long fl = htonl(fileLength); //Are we sure this is sending all the bytes??? TEST if (send (new_fd, &fl, sizeof fl, 0) == -1) { perror ("send"); } //variable stating amount of data unsent long len = fileLength; //Until file is sent, keep sending while(len > 0) { printf("remaining: %dntotal: %dn", len, fileLength); //set counter at beginning of unread segment fseek (filefd, fileLength - len, SEEK_SET); //length of the segment; 255 unless last segment short bufLength; if (len > 255) { len -= 255; bufLength = 255; } else { bufLength = len; len = 0; } //buffer of 256 bytes; 1 byte for byte-length of segment, 255 bytes of data char buf[256]; //Set first byte of buffer as the length of the segment //converting short to char requires subtracting 128 buf[0] = bufLength - 128; //read file into the buffer starting after the first byte of the buffer fread(buf + 1, 1, bufLength * sizeof(char), filefd); //Send data too client if (send (new_fd, buf, sizeof buf, 0) == -1) { perror ("send"); } } fclose (filefd); close (new_fd); exit (0); } close (new_fd); } return 0; }
Note: I’ve simplified the code a bit, to make it clearer I hope. Anything beginning with //… represents a bunch of code
Advertisement
Answer
You seem to be assuming that each send()
will either transfer the full number of bytes specified or will error out, and that each one will will pair perfectly with a recv()
on the other side, such that the recv()
receives exactly the number of bytes sent by the send()
(or error out), no more and no less. Those are not safe assumptions.
You don’t show the code by which you set up the network connection. If you’re using a datagram-based protocol (i.e. UDP) then you’re more likely to get the send/receive boundary matching you expect, but you need to account for the possibility that packets will be lost or corrupted. If you’re using a stream-based protocol (i.e. TCP) then you don’t have to be too concerned with data loss or corruption, but you have no reason at all to expect boundary-matching behavior.
You need at least three things:
An application-level protocol on top of the network-layer. You’ve got parts of that already, such as in how you transfer the file length first to advise the client about much content to expect, but you need to do similar for all data transferred that are not of pre-determined, fixed length. Alternatively, invent another means to communicate data boundaries.
Every
send()
/write()
that aims to transfer more than one byte must be performed in a loop to accommodate transfers being broken into multiple pieces. The return value tells you how many of the requested bytes were transferred (or at least how many were handed off to the network stack), and if that’s fewer than requested you must loop back to try to transfer the rest.Every
recv()
/read()
that aims to transfer more than one byte must be performed in a loop to accommodate transfers being broken into multiple pieces. I recommend structuring that along the same lines as described forsend()
, but you also have the option of receiving data until you see a pre-arranged delimiter. The delimiter-based approach is more complicated, however, because it requires additional buffering on the receiving side.
Without those measures, your server and client can easily get out of sync. Among the possible results of that are that the client interprets part of the file name or part of the file content as the file length.