为什么我通过socket发送一个图片文件时,字节数不一样?

Why is there a difference in bytes when I send a picture file through a socket?

我目前正在构建一个 C/C++ 应用程序,它通过网络套接字发送和接收图片文件。但是,当我通过套接字发送文件时,目标文件与源文件相比有轻微的字节差异。

为了使此应用程序正常运行,目标(客户端)文件必须与源(服务器)文件在字节方面完全相同。

客户代码:

int client_app(int argc, char *argv[]) {
    // create a socket
    int network_socket;
    network_socket = socket(AF_INET, SOCK_STREAM, 0);

    // specify an address for the socket
    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(9002);
    server_address.sin_addr.s_addr = INADDR_ANY;

    // connect returns an integer
    int connection_status = connect(network_socket, (struct sockaddr *) &server_address, sizeof(server_address));

    if (connection_status == -1) {
        printf("Error making connection to remote socket \n\n");
    }

    // read picture byte array
    printf("Reading picture byte array...\n");
    char p_array[BUFSIZ];

    // convert it back into a pic
    printf("Converting byte array to DNG...\n");
    FILE *image = fopen("out_compressed.GPR", "w");
    int nb;
    while ((nb = read(network_socket, p_array, BUFSIZ)) > 0) {
        fwrite(p_array, 1, nb, image);
        bzero(p_array, BUFSIZ);
    }
    fclose(image);

    // and then close the socket
    close(network_socket);
}

服务器代码:

int server_app(int argc, char *argv[]) {
    char *process_argv[100];
    *process_argv = *argv;
    int process_argc = 0;
    for (int i = 1; i < 6; i++) {
        process_argv[i] = argv[i + 1];
        process_argc++;
    }
    process(process_argc, process_argv);

    // create the server socket
    int server_socket;
    server_socket = socket(AF_INET, SOCK_STREAM, 0);

    // define the port number
    int port = 9002;

    // define the server address
    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(port);
    server_address.sin_addr.s_addr = INADDR_ANY;

    // bind the socket to our specified IP and port
    bind(server_socket, (struct sockaddr *) &server_address, sizeof(server_address));

    // listen for connections on the socket
    listen(server_socket, 5);

    // create client socket for the server to send data to
    int client_socket;
    client_socket = accept(server_socket, nullptr, nullptr);

    // get picture size
    char *fpath = process_argv[4];
    FILE *picture;
    picture = fopen(fpath, "r");
    int size;
    fseek(picture, 0, SEEK_END);
    size = ftell(picture);
    fseek(picture, 0, SEEK_SET);

    // send picture size
    write(client_socket, &size, sizeof(size));

    // send picture as byte array
    char send_buffer[BUFSIZ];
    int nb = fread(send_buffer, 1, sizeof(send_buffer), picture);

    while (!feof(picture)) {
        write(client_socket, send_buffer, nb);
        nb = fread(send_buffer, 1, sizeof(send_buffer), picture);
    }

    // then close the sockets
    close(server_socket);
    close(client_socket);

}

当我在客户端收到一个文件时,它看起来大小相同,但与源文件不同,我无法对其执行某些压缩操作。

当 运行 cmp 在我的终端中比较源文件和目标文件时,我得到的结果是:

compressed.GPR out_compressed.GPR differ: char 1, line 1

如何让这些文件在字节方面完全相同?

谢谢!

在客户端中,您先发送文件大小,然后再发送内容。在服务器中,您假设只发送内容,即您将最初发送的大小信息作为内容存储在新文件中。

除此之外,您只是假设 write 实际上会写入给定的所有数据,这并不能保证。因此,您需要检查实际写入的字节数。如果你在 Windows 上,你最好将文件打开为二进制文件(即 "rb" 而不是 "r","wb" 而不是 "w"),因为你正在处理此处为二进制数据。

write(client_socket, &size, sizeof(size));

将图像文件的大小写入套接字(但没有检查写入是否成功。始终检查 return 代码以确保您写入了您认为的内容。)。客户端不使用此信息并将其集中到文件中。根据客户端当前的编码方式,套接字在完成文件后立即关闭,您可以丢弃计算和发送长度的代码。当套接字关闭时,文件就完成了。无需将长度作为助手发送。

另外,图像几乎可以肯定是二进制信息。使用

打开文件
FILE *image = fopen("out_compressed.GPR", "w");

picture = fopen(fpath, "r");

将以文本模式打开并可能执行一些转换(其中最著名的是 \r\n\n)并弄乱文件。分别用"wb""rb"打开。

主要有两个错误。

首先,服务器在内容之前写入文件大小,但客户端立即开始读取内容,因此文件大小成为客户端内容的一部分。客户端不需要大小,所以通过简单地不在服务器端发送文件大小来实现六个。

第二个问题是您对feof的使用:

int nb = fread(send_buffer, 1, sizeof(send_buffer), picture);

while (!feof(picture)) {
    write(client_socket, send_buffer, nb);
    nb = fread(send_buffer, 1, sizeof(send_buffer), picture);
}

fread 读取文件的最后一部分时,即小于缓冲区大小的部分,设置 EOF 标志。这意味着 feof return 为真,您在将文件的最后一段写入套接字之前退出循环。

处理此问题的更好方法是在从文件读取 1 个或多个字节时循环:

int nb = fread(send_buffer, 1, sizeof(send_buffer), picture);

while (nb > 0) {
    write(client_socket, send_buffer, nb);
    nb = fread(send_buffer, 1, sizeof(send_buffer), picture);
}

除此之外,您不对调用的任何函数执行错误检查。您应该检查所有套接字和文件相关函数的 return 值,如果失败则使用 perror 打印错误消息。