i/o 通过 TCP 套接字调用的问题

Issues with i/o calls over TCP socket

对于某些背景,我正在编写一个 TCP 套接字服务器/客户端通信程序来将一个文本文件复制到另一个文本文件。我正在努力实现通过套接字读取。我正在 linux.

上开发

这是服务端读取函数,当客户端读取函数通过套接字发送读取操作码时调用。程序使用正确的 fd 和计数正确地到达此代码。

/* 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;
}

这是客户端读取函数,它将有效负载放在一起并通过套接字发送,以请求服务器端读取。

/* 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;
}

客户端在 in_msg 中接收到伪造的 return_value,但服务器在发送之前看到了正确的 return_value。两端实际读入的数据貌似也是假的

这是实际调用 i/o 函数将一个文件复制到另一个文件的代码。我有一个类似的文件,可以从 local 文件复制到远程文件,并且可以正常工作。在 main 接收到套接字信息并从 argv[].

中剥离这些 args 后从 main 调用它
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;
}

有关错误行为的更多详细信息: 服务器在关闭前只收到 6 次 s_read() 调用,本应收到大约 41 次调用。在读取“复制”文件时,它充满了人类无法阅读的虚假值。 None 我的错误被触发,除了 close() 调用之后的一个错误。特别是“r_server.c::main: 无法接收操作码。”错误。因此,我在下面包含了服务器的主要功能(它读取客户端功能提供的操作码)。

/* 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;
}

代码盲目地假设 read 将始终准确读取请求的字节数,而 write 将始终写入给定的完整缓冲区。这两个假设都是错误的。

read 只会读取 最多 给定的大小,但实际上可能会读取更少。类似的 write 只会写入 至多 整个缓冲区,但可能会写入更少。因此必须检查 return 值以确定实际读取或写入了多少数据。如果没有读取或写入所有内容,则必须对剩余数据重复操作,甚至可能重复多次。

在 r_read() 函数中,我们从未读入服务器放入响应中的值。我们只读取对应于 return_value 和 errno.

的 8 个字节

而不是

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];
}

我们需要做的

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;
    }
}

请注意,仅当您需要处理读取或写入调用未完全 成功的情况时,才需要使用 while ( total ...) 逻辑。就我而言,如果发生这种情况,我们可以简单地退出并返回错误。