(考察linux网络编程、系统编程、网络协议、网络传输协议等知识)

问:局域网内有A、B、C三台主机,A与B不知道相互之间的IP。A要向B传输一个1G的文件,怎么做?


  1. 大文件传输的优化:
  • 分块传输:将大文件分成多个小块(如4KB、8KB等),每次传输一块,避免占用过多内存。
  • 校验和(Checksum):在每一块传输后进行数据校验,确保数据的完整性。
  • 带宽控制:通过控制每次发送的数据量来避免一次性传输过多数据,控制网络负载。
  1. 断点续传的实现:
  • 记录传输进度:客户端和服务器都需要记录已经成功传输的数据块或字节的位置。
  • 支持断点请求:客户端在恢复传输时,应该告知服务器从哪个位置开始传输。
  • 校验和和确认机制:每次传输数据块后,都应该进行确认,确保数据正确传送。

步骤

  1. 使用局域网广播发现B的IP地址 由于A和B的IP地址不直接已知,A可以通过局域网广播来找到B的IP地址。A可以向网络中的所有主机发送一个UDP广播消息,所有主机都会接收到这个消息,B在接收到这个广播后,可以回复A,告知自己的IP地址。
  • UDP广播:A可以通过发送一个UDP广播包到特定的端口,让局域网中的所有主机收到该消息。B可以通过监听这个端口,收到消息后回应自己的IP地址。
  1. 使用TCP协议进行文件传输 一旦A得到了B的IP地址,就可以使用TCP协议进行文件传输。A通过TCP连接到B,建立数据通道,开始发送1GB的文件。

1. UDP广播发现B的IP地址

A使用UDP广播向局域网中的所有主机发送请求,B收到请求后会通过UDP回应自己的IP地址。

A端:发送UDP广播请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>

#define BROADCAST_PORT 12345

void send_broadcast_message() {
    int sockfd;
    struct sockaddr_in broadcast_addr;
    int broadcast_enable = 1;
    const char* message = "Are you there, B? Please reply with your IP address.";

    // 创建UDP套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("Socket creation failed");
        return;
    }

    // 允许广播
    if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast_enable, sizeof(broadcast_enable)) < 0) {
        perror("Setting broadcast option failed");
        close(sockfd);
        return;
    }

    memset(&broadcast_addr, 0, sizeof(broadcast_addr));
    broadcast_addr.sin_family = AF_INET;
    broadcast_addr.sin_port = htons(BROADCAST_PORT);
    broadcast_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);

    // 发送广播消息
    if (sendto(sockfd, message, strlen(message), 0, (struct sockaddr*)&broadcast_addr, sizeof(broadcast_addr)) < 0) {
        perror("Broadcast failed");
        close(sockfd);
        return;
    }

    std::cout << "Broadcast message sent!" << std::endl;

    close(sockfd);
}

int main() {
    send_broadcast_message();
    return 0;
}

B端:接收UDP广播并回应自己的IP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>

#define BROADCAST_PORT 12345
#define RESPONSE_PORT 12346

void listen_for_broadcasts() {
    int sockfd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[1024];

    // 创建UDP套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("Socket creation failed");
        return;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(BROADCAST_PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 绑定UDP套接字
    if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("Bind failed");
        close(sockfd);
        return;
    }

    // 接收广播消息
    while (true) {
        int recv_len = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&client_addr, &client_len);
        if (recv_len < 0) {
            perror("Failed to receive message");
            continue;
        }

        buffer[recv_len] = '\0';
        std::cout << "Received message: " << buffer << std::endl;

        // 如果消息包含特定请求,可以回复自己的IP地址
        std::string response = "IP Address of B: ";
        response += inet_ntoa(client_addr.sin_addr);
        sendto(sockfd, response.c_str(), response.length(), 0, (struct sockaddr*)&client_addr, client_len);
        std::cout << "Sent response with IP address: " << inet_ntoa(client_addr.sin_addr) << std::endl;
    }

    close(sockfd);
}

int main() {
    listen_for_broadcasts();
    return 0;
}

2. 使用TCP协议传输文件

一旦A得到了B的IP地址,A就可以通过TCP连接与B进行文件传输。

A端:通过TCP向B发送文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>
#include <fstream>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>

#define SERVER_PORT 8080
#define CHUNK_SIZE 4096 // 4KB per chunk

void send_file(const std::string &filename, const std::string &ip_address) {
    int sockfd;
    struct sockaddr_in server_addr;
    char buffer[CHUNK_SIZE];
    std::ifstream file(filename, std::ios::binary);
    off_t offset = 0;  // 记录已经发送的文件偏移量

    // 创建TCP套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("Socket creation failed");
        return;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr(ip_address.c_str());

    // 连接到B
    if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("Connection failed");
        close(sockfd);
        return;
    }

    // 发送文件的分块数据
    while (file.read(buffer, CHUNK_SIZE)) {
        // 发送文件块数据
        ssize_t bytes_sent = send(sockfd, buffer, file.gcount(), 0);
        if (bytes_sent < 0) {
            perror("Send failed");
            break;
        }
        offset += bytes_sent;

        // 发送每个块后,需要确认接收进度
        // 可以通过协议的方式要求B确认
        std::cout << "Sent chunk, current offset: " << offset << std::endl;
    }

    // 发送文件剩余的数据(如果有)
    if (file.gcount() > 0) {
        send(sockfd, buffer, file.gcount(), 0);
        offset += file.gcount();
    }

    std::cout << "File sent successfully! Total bytes sent: " << offset << std::endl;
    close(sockfd);
}

int main() {
    send_file("large_file.bin", "192.168.1.2");
    return 0;
}

B端:接收文件并保存

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <iostream>
#include <fstream>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>

#define SERVER_PORT 8080
#define CHUNK_SIZE 4096 // 4KB per chunk

void receive_file(const std::string &filename) {
    int sockfd, newsockfd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[CHUNK_SIZE];
    std::ofstream file(filename, std::ios::binary | std::ios::app);  // 以追加方式打开文件
    off_t offset = file.tellp();  // 获取文件当前的偏移量(即已经接收的字节数)

    // 创建TCP套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("Socket creation failed");
        return;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    // 绑定套接字
    if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("Bind failed");
        close(sockfd);
        return;
    }

    // 监听
    listen(sockfd, 1);
    std::cout << "Waiting for connection..." << std::endl;

    // 接受连接
    newsockfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
    if (newsockfd < 0) {
        perror("Accept failed");
        close(sockfd);
        return;
    }

    // 接收文件
    while (true) {
        int recv_len = recv(newsockfd, buffer, CHUNK_SIZE, 0);
        if (recv_len <= 0) break;

        // 写入文件(追加模式)
        file.write(buffer, recv_len);
        offset += recv_len;

        std::cout << "Received chunk, current offset: " << offset << std::endl;
    }

    std::cout << "File received successfully! Total bytes received: " << offset << std::endl;

    close(newsockfd);
    close(sockfd);
}

int main() {
    receive_file("received_large_file.bin");
    return 0;
}

断点续传实现说明:

  • A端:每发送完一个块,A会更新文件的偏移量(即offset),并传递该偏移量的信息。如果传输过程中发生中断,A可以记录上次发送的偏移量,从该位置开始重新传输。
  • B端:B端会在接收每个块时,记录接收到的字节数(即offset)。B端可以通过检查文件的大小来判断是否需要继续接收文件。如果B端关闭了连接,下次启动时会从文件尾部继续接收。

拓展:

MD5(Message Digest Algorithm 5)是一种广泛使用的加密哈希函数,它产生一个128位(16字节)的哈希值,通常用32个十六进制字符表示。MD5被设计用来接收任意长度的数据(通常是文件或消息)并生成一个固定长度的“摘要”或“指纹”,这个摘要用于验证数据的完整性。