Skip to content
Advertisement

Printf makes program work in C, htonl and ntohl not working?

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 for send(), 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.

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement