MST

星途 面试题库

面试题:C语言UDP编程的可靠性增强

在Linux C语言UDP编程中,由于UDP本身是不可靠的传输协议,假设要传输重要数据,如何通过代码实现一定程度上的可靠性机制,比如数据确认和重传。请详细描述思路并给出主要的代码框架。
34.3万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

思路

  1. 数据确认:发送方发送数据时,为每个数据包分配一个唯一的标识符(如序列号)。接收方接收到数据包后,向发送方回发一个确认包(ACK),ACK 包中包含接收到的数据包的序列号。
  2. 重传机制:发送方在发送数据包后,启动一个定时器。如果在定时器超时之前没有收到对应的 ACK 包,就重传该数据包。可以设置重传次数限制,防止无限重传。

主要代码框架

  1. 发送方代码框架
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 8080
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024
#define TIMEOUT_SECONDS 1

typedef struct {
    int seq_num;
    char data[BUFFER_SIZE];
} Packet;

typedef struct {
    int seq_num;
} AckPacket;

int main() {
    int sockfd;
    struct sockaddr_in servaddr, cliaddr;

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

    memset(&servaddr, 0, sizeof(servaddr));
    memset(&cliaddr, 0, sizeof(cliaddr));

    // 填充服务器和客户端信息
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    servaddr.sin_addr.s_addr = INADDR_ANY;

    cliaddr.sin_family = AF_INET;
    cliaddr.sin_port = htons(PORT);
    inet_pton(AF_INET, SERVER_IP, &cliaddr.sin_addr);

    Packet packet;
    AckPacket ack;
    socklen_t len = sizeof(cliaddr);
    int seq_num = 0;
    int max_retries = 3;

    while (1) {
        // 填充数据包
        packet.seq_num = seq_num++;
        printf("Enter message to send: ");
        fgets(packet.data, sizeof(packet.data), stdin);
        packet.data[strcspn(packet.data, "\n")] = '\0';

        int retries = 0;
        while (retries < max_retries) {
            // 发送数据包
            sendto(sockfd, (const Packet *)&packet, sizeof(Packet), MSG_CONFIRM, (const struct sockaddr *) &cliaddr, len);
            printf("Packet sent with seq num: %d\n", packet.seq_num);

            // 设置接收 ACK 的超时
            struct timeval timeout;
            timeout.tv_sec = TIMEOUT_SECONDS;
            timeout.tv_usec = 0;
            setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout));

            // 接收 ACK
            int n = recvfrom(sockfd, (AckPacket *)&ack, sizeof(AckPacket), MSG_WAITALL, (struct sockaddr *) &cliaddr, &len);
            if (n > 0 && ack.seq_num == packet.seq_num) {
                printf("ACK received for seq num: %d\n", ack.seq_num);
                break;
            } else {
                printf("ACK not received for seq num: %d, retrying...\n", packet.seq_num);
                retries++;
            }
        }

        if (retries == max_retries) {
            printf("Max retries reached, unable to send packet with seq num: %d\n", packet.seq_num);
        }
    }

    close(sockfd);
    return 0;
}
  1. 接收方代码框架
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 8080
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024

typedef struct {
    int seq_num;
    char data[BUFFER_SIZE];
} Packet;

typedef struct {
    int seq_num;
} AckPacket;

int main() {
    int sockfd;
    struct sockaddr_in servaddr, cliaddr;

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_DUDP, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    memset(&cliaddr, 0, sizeof(cliaddr));

    // 填充服务器信息
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    servaddr.sin_addr.s_addr = INADDR_ANY;

    // 绑定套接字到服务器地址
    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    Packet packet;
    AckPacket ack;
    socklen_t len = sizeof(cliaddr);

    while (1) {
        // 接收数据包
        int n = recvfrom(sockfd, (Packet *)&packet, sizeof(Packet), MSG_WAITALL, (struct sockaddr *) &cliaddr, &len);
        if (n > 0) {
            printf("Received packet with seq num: %d, data: %s\n", packet.seq_num, packet.data);

            // 发送 ACK
            ack.seq_num = packet.seq_num;
            sendto(sockfd, (const AckPacket *)&ack, sizeof(AckPacket), MSG_CONFIRM, (const struct sockaddr *) &cliaddr, len);
            printf("ACK sent for seq num: %d\n", ack.seq_num);
        }
    }

    close(sockfd);
    return 0;
}

注意:上述代码仅是一个基本框架,实际应用中还需处理更多的边界情况,如网络拥塞、数据校验等。并且代码中的IP地址和端口号等配置可根据实际情况调整。同时,在接收和发送数据时,错误处理可进一步完善。