Home > Mobile >  Issues with i/o calls over TCP socket
Issues with i/o calls over TCP socket

Time:11-30

For some background, I am writing a TCP socket server / client communication program to copy one text file to another. I'm struggling with my implementation of reading over the socket. I'm developing on linux.

This is the server-side read function that is called when the client side read function sends the read opcode through the socket. The program reaches this code properly with the correct fd and count.

/* s_read
 * server side read
 * Send in socket: 4 bytes return value, 4 bytes errno, n bytes data
 */
int s_read(int conn){
    
    // Get fd
    unsigned char fdstr[4];
    int L     = sizeof(char) * 4;
    int bytes = 0;
    int total = 0;
    while ( total < L ) {
        if ( (bytes = read(conn, fdstr   total, L - total)) < 0) {
            printf("r_server.c::s_read: Couldn't receive fd\n");
            return -1;
        }
        total  = bytes;
    }
    int fd = 0;
    fd = (fdstr[0] << 24) | (fdstr[1] << 16) | (fdstr[2] <<  8) | fdstr[3];
    
    // Get count
    unsigned char countstr[4];
    L     = sizeof(char) * 4;
    bytes = 0;
    total = 0;
    while ( total < L ) {
        if ( (bytes = read(conn, countstr   total, L - total)) < 0) {
            printf("r_server.c::s_read: Couldn't receive count\n");
            return -1;
        }
        total  = bytes;
    }
    int count = 0;
    count = (countstr[0] << 24) | (countstr[1] << 16) | (countstr[2] <<  8) | countstr[3];
    
    // Prepare return message
    int    return_value;
    L = 8             // 4 bytes for return value, 4 bytes for errno
        count;        // count bytes for read in data
    int    error = 0;
    char * msg;
    char * read_value = malloc(count);
    
    // Execute open call
    bytes = 0;
    total = 0;
    while ( total < count ) {
        if ( (return_value = read(fd, read_value   total, count - total)) < 0 ) {
            error = errno;
            break;
        }
        total  = return_value;
    }
    
    // Build return message
    msg = malloc(L);
    L=0;
    msg[L  ] = (return_value >> 24) & 0xff; // put the kernel return value
    msg[L  ] = (return_value >> 16) & 0xff;
    msg[L  ] = (return_value >>  8) & 0xff;
    msg[L  ] = (return_value      ) & 0xff;
    msg[L  ] = (error >> 24) & 0xff;        // put the errno
    msg[L  ] = (error >> 16) & 0xff;
    msg[L  ] = (error >>  8) & 0xff;
    msg[L  ] = (error      ) & 0xff;
    for (int i=0; i < count; i  )
        msg[L  ] = read_value[i];           // put the read in data.
    
    // Send return message
    bytes = 0;
    total = 0;
    while ( total < L ) {
        if ( (bytes = write(conn, msg   total, L - total)) < 0) {
            printf("r_server.c::s_read: Error sending r_read return value to client\n");
            return -1;
        }
        total  = bytes;
    }
    
    free(read_value);
    free(msg);
    
    return 0;
}

This is the client-side read function that puts together a payload and sends it over the socket to request the server to read on its end.

/* r_read
 * remote read
 */
int r_read(int fd, void *buf, int count) {
    
    int    L;
    char * msg;
    int    in_msg;
    int    in_err;
    
    L = 1               // byte for opcode
        sizeof(fd)      // int bytes for fd.
        sizeof(count);  // int bytes for count.
    
    msg = malloc(L);
    L=0;
    msg[L  ] = 3;                    // this is the code for read.
    
    msg[L  ] = (fd >> 24) & 0xff;    // put the fd.
    msg[L  ] = (fd >> 16) & 0xff;
    msg[L  ] = (fd >>  8) & 0xff;
    msg[L  ] = (fd      ) & 0xff;
    
    msg[L  ] = (count >> 24) & 0xff; // put the count.
    msg[L  ] = (count >> 16) & 0xff;
    msg[L  ] = (count >>  8) & 0xff;
    msg[L  ] = (count      ) & 0xff;
    
    int bytes = 0;
    int total = 0;
    while ( total < L ) {
        if ( (bytes = write(sock, msg   total, L - total)) < 0) {
            printf("Failed to send r_read to server\n");
            return -1;
        }
        total  = bytes;
    }
    
    bytes = 0;
    total = 0;
    while ( total < 8 ) {
        if ( (bytes = read(sock, msg   total, 8 - total)) < 0) {
            printf("Failed to receive r_read from server\n");
            return -1;
        }
        total  = bytes;
    }
    
    in_msg = (msg[0] << 24) | (msg[1] << 16) | (msg[2] << 8) | msg[3];
    in_err = (msg[4] << 24) | (msg[5] << 16) | (msg[6] << 8) | msg[7];
    for (int i = 0; i < count; i  ) {
        *(char *)(buf   i) = msg[i   8];
    }
    
    errno = in_err;
    
    free(msg);
    
    return in_msg;
}

The client is receiving a bogus return_value in in_msg, but the server sees the proper return_value before sending it. The actual read in data is also bogus seemingly on both ends.

This is the code actually calling the i/o functions to copy the one file to the other. I have a similar one that copies from a local file to a remote file, and that one works properly. It is called from main after main receives the socket information and strips those args from argv[].

int entry(int argc, char* argv[]){
    
    // Input guards
    if (argc != 3) {
        printf("Invalid arguments\n");
        return -1;
    }
    
    // Get file names
    char *filename = argv[1];
    char *copyfilename = argv[2];
    printf("rclient2.c::entry: Copying remote file %s to local file %s\n", filename, copyfilename);
    
    // Open files
    int fd = r_open((const char*) filename, O_RDWR, (mode_t) 0600);
    if (fd < 0) {
        printf("rclient2.c::entry: r_open failed.\n");
        return -1;
    }
    int copyfd = open((const char*) copyfilename, O_RDWR | O_CREAT, (mode_t) 0600);
    if (copyfd < 0) {
        printf("rclient2.c::entry: open failed.\n");
        return -1;
    }
    
    // Seek to position 10
    //r_lseek(fd, 10, SEEK_SET); // Later requirement once read is working

    // Copy file
    char buf;
    while ( r_read(fd, &buf, 1) > 0 ) {
        if (write(copyfd, &buf, 1) < 0) {
            printf("rclient2::entry: write failed.\n");
            return -1;
        }
    }
    
    // Close files
    r_close(fd);
    close(copyfd);
    
    return 0;
}

More detail on the incorrect behavior: The server is only getting a s_read() call 6 times before closing in a case where it should be getting about 41 of them. Upon reading the "copied" file, it is full of bogus values that are not human readable. None of my errors are getting tripped except for one after the close() call. Specifically the "r_server.c::main: Couldn't receive opcode." error. Because of this, I'm including the server's main function below (where it reads the opcode provided by the client functions).

/* main - server implementation
 */
int main(int argc, char *argv[]){
    
    // Setup socket data
    int listener, conn;
    unsigned int length;
    struct sockaddr_in s1, s2;
    
    // Create server socket
    listener = socket(AF_INET, SOCK_STREAM, 0);
    if (listener < 0) {
        printf("r_server.c::main: Error creating server socket\n");
        return -1;
    }
    
    // Establish server socket
    bzero((char *) &s1, sizeof(s1));
    s1.sin_family = AF_INET;
    s1.sin_port = htons(0);
    s1.sin_addr.s_addr = inet_addr("127.0.0.1");
    if( bind( listener, (struct sockaddr *) &s1, sizeof(s1)) < 0) {
        printf("r_server.c::main: Server couldn't bind to the port\n");
        return -1;
    }
    
    // Print port and start server
    length = sizeof(s1);
    getsockname(listener, (struct sockaddr *) &s1, &length);
    printf("%d\n", s1.sin_port);
    if ( listen(listener, 1) < 0) {
        printf("r_server.c::main: Server error while listening\n");
        return -1;
    }

    // While server running
    while(1) {
        
        // Connect to new client
        length = sizeof(s2);
        conn = accept(listener, (struct sockaddr *) &s2, &length);
        if( conn < 0 ) {
            printf("r_server.c::main: Server failed to accept incoming stuff\n");
            return -1;
        }
        
        // Fork to manage client and listen for new clients
        if (fork() == 0) {
            
            // Until client disconnects
            while (1) {
                
                // Get opcode
                unsigned char opcode;
                int success;
                if ((success = read(conn, &opcode, sizeof(opcode))) < 0) {
                    printf("r_server.c::main: Couldn't receive opcode. opcode = %d\n", opcode);
                    return -1;
                }
                
                // Client disconnected
                if (success == 0) {
                    return 0;
                }
                
                // Call related server side function
                switch (opcode) {
                    case (1):
                        printf("Opening...\n");
                        if (s_open(conn) < 0)
                            return -1;
                        break;
                    case (2):
                        printf("Closing...\n");
                        if (s_close(conn) < 0)
                            return -1;
                        break;
                    case (3):
                        printf("Reading...\n");
                        if (s_read(conn) < 0)
                            return -1;
                        break;
                    case (4):
                        printf("Writing...\n");
                        if (s_write(conn) < 0)
                            return -1;
                        break;
                    case (5):
                        printf("Seeking...\n");
                        if (s_lseek(conn) < 0)
                            return -1;
                        break;
                    case (6):
                        printf("Piping...\n");
                        if (s_pipe(conn) < 0)
                            return -1;
                        break;
                    case (7):
                        printf("Duping...\n");
                        if (s_dup2(conn) < 0)
                            return -1;
                        break;
                    default:
                        return -1;              
                }
            }
            return 0;
        }
    }
    return 0;
}

CodePudding user response:

The code is blindly assuming that read will always read exactly the number of bytes asked for and write will always write the full buffer given. Both of these assumptions are wrong.

read will only read up to the given size but might actually read less. Similar write will only write at most the full buffer, but might write less. Therefore the return value has to be checked to find out how much data are actually read or written. If not everything was read or written the operations have to be repeated for the remaining data, maybe even multiple times.

CodePudding user response:

In the r_read() function, we never read in the value the server put into the response. We're only reading in the 8 bytes corresponding to the return_value and errno.

Instead of

int bytes = 0;
int total = 0;
while ( total < L ) {
    if ( (bytes = write(sock, msg   total, L - total)) < 0) {
        printf("Failed to send r_read to server\n");
        return -1;
    }
    total  = bytes;
}
    
bytes = 0;
total = 0;
while ( total < 8 ) {
    if ( (bytes = read(sock, msg   total, 8 - total)) < 0) {
        printf("Failed to receive r_read from server\n");
        return -1;
    }
    total  = bytes;
}

in_msg = (msg[0] << 24) | (msg[1] << 16) | (msg[2] << 8) | msg[3];
in_err = (msg[4] << 24) | (msg[5] << 16) | (msg[6] << 8) | msg[7];
for (int i = 0; i < count; i  ) {
    *(char *)(buf   i) = msg[i   8];
}

We need to do

int bytes = 0;
int total = 0;
while ( total < L ) {
    if ( (bytes = write(sock, msg   total, L - total)) < 0) {
        printf("Failed to send r_read to server\n");
        return -1;
    }
    total  = bytes;
}

bytes = 0;
total = 0;
while ( total < 8 ) {
    if ( (bytes = read(sock, msg   total, 8 - total)) < 0) {
        printf("Failed to receive r_read from server\n");
        return -1;
    }
    total  = bytes;
}

in_msg = (msg[0] << 24) | (msg[1] << 16) | (msg[2] << 8) | msg[3]; // # of bytes remaining in the socket buffer (the data we asked for)
in_err = (msg[4] << 24) | (msg[5] << 16) | (msg[6] << 8) | msg[7];


bytes = 0;
total = 0;
while ( total < in_msg ) {
    if ( (bytes = read(sock, buf   total, in_msg - total)) < 0) {
        printf("Failed to read the r_read data from server\n");
        return -1;
    }
}

Note that the while ( total ...) logic is only required if you're needing to handle a case where a read or write call does not fully succeed. In my case, we're allowed to simply exit with an error if this happens.

  • Related