MST

星途 面试题库

面试题:C语言Linux套接字编程应对复杂网络环境

假设在一个复杂的网络环境中,存在网络拥塞、丢包、延迟抖动等问题,使用C语言进行Linux套接字编程实现一个可靠的应用层协议。要求描述协议设计思路,包括如何处理拥塞控制、丢包重传、抖动缓冲等关键问题,并给出核心功能的代码框架以及关键函数的详细说明。
16.4万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

协议设计思路

  1. 拥塞控制
    • 采用类似TCP的拥塞控制机制,如慢启动、拥塞避免、快速重传和快速恢复。
    • 维护一个拥塞窗口(cwnd),初始值可以设置为一个较小的值(如1个最大段大小MSS)。
    • 慢启动阶段,每收到一个ACK,cwnd增加一个MSS;当cwnd达到慢启动门限(ssthresh)时,进入拥塞避免阶段,每收到cwnd个ACK,cwnd增加一个MSS。
    • 当检测到丢包(超时或3个冗余ACK),调整ssthresh和cwnd,进入快速重传或快速恢复阶段。
  2. 丢包重传
    • 为每个发送的数据包分配一个唯一的序列号。
    • 发送方维护一个重传定时器,当定时器超时且对应的ACK未收到时,重传该数据包。
    • 接收方通过序列号来判断数据包是否重复,丢弃重复的数据包。
  3. 抖动缓冲
    • 接收方维护一个缓冲区,用于存储接收到但顺序不正确的数据包。
    • 根据序列号对数据包进行排序,按正确顺序交付给应用层。
    • 为了避免缓冲区无限增长,设置一个合理的缓冲区大小,当缓冲区满且有新的数据包到达时,丢弃最早到达的数据包。

核心功能代码框架

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <pthread.h>

#define PORT 8080
#define MAX_SEQ 65535
#define MSS 1024
#define TIMEOUT 2 // 重传超时时间(秒)

// 数据包结构
typedef struct {
    unsigned short seq_num;
    unsigned short ack_num;
    unsigned short length;
    char data[MSS];
} Packet;

// 发送方状态
typedef struct {
    int sockfd;
    struct sockaddr_in servaddr;
    unsigned short cwnd;
    unsigned short ssthresh;
    unsigned short next_seq_num;
    unsigned short expected_ack_num;
    Packet *send_buffer[MAX_SEQ];
    int buffer_head;
    int buffer_tail;
    int timer_running;
    pthread_t timer_thread;
} Sender;

// 接收方状态
typedef struct {
    int sockfd;
    struct sockaddr_in cliaddr;
    unsigned short expected_seq_num;
    Packet *recv_buffer[MAX_SEQ];
    int buffer_head;
    int buffer_tail;
} Receiver;

// 初始化发送方
void init_sender(Sender *sender, const char *ip) {
    sender->sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sender->sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    memset(&sender->servaddr, 0, sizeof(sender->servaddr));
    memset(&sender->send_buffer, 0, sizeof(sender->send_buffer));
    sender->servaddr.sin_family = AF_INET;
    sender->servaddr.sin_port = htons(PORT);
    sender->servaddr.sin_addr.s_addr = inet_addr(ip);

    sender->cwnd = 1 * MSS;
    sender->ssthresh = 65535;
    sender->next_seq_num = 0;
    sender->expected_ack_num = 0;
    sender->buffer_head = 0;
    sender->buffer_tail = 0;
    sender->timer_running = 0;
}

// 初始化接收方
void init_receiver(Receiver *receiver) {
    receiver->sockfd = socket(AF_INET, SOCK_DUDP, 0);
    if (receiver->sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    memset(&receiver->cliaddr, 0, sizeof(receiver->cliaddr));
    memset(&receiver->recv_buffer, 0, sizeof(receiver->recv_buffer));
    receiver->cliaddr.sin_family = AF_INET;
    receiver->cliaddr.sin_port = htons(PORT);
    receiver->cliaddr.sin_addr.s_addr = INADDR_ANY;

    if (bind(receiver->sockfd, (const struct sockaddr *)&receiver->cliaddr, sizeof(receiver->cliaddr)) < 0) {
        perror("bind failed");
        close(receiver->sockfd);
        exit(EXIT_FAILURE);
    }

    receiver->expected_seq_num = 0;
    receiver->buffer_head = 0;
    receiver->buffer_tail = 0;
}

// 发送数据包
void send_packet(Sender *sender, Packet *packet) {
    sendto(sender->sockfd, (const Packet *)packet, sizeof(Packet), MSG_CONFIRM, (const struct sockaddr *)&sender->servaddr, sizeof(sender->servaddr));
    sender->send_buffer[sender->next_seq_num] = packet;
    sender->next_seq_num = (sender->next_seq_num + 1) % MAX_SEQ;
    if (!sender->timer_running) {
        // 启动重传定时器
    }
}

// 接收数据包
void receive_packet(Receiver *receiver) {
    Packet packet;
    socklen_t len = sizeof(receiver->cliaddr);
    recvfrom(receiver->sockfd, (Packet *)&packet, sizeof(Packet), MSG_WAITALL, (struct sockaddr *)&receiver->cliaddr, &len);
    if (packet.seq_num == receiver->expected_seq_num) {
        // 按序交付给应用层
        receiver->expected_seq_num = (receiver->expected_seq_num + 1) % MAX_SEQ;
        // 从缓冲区中查找并交付后续按序的数据包
    } else {
        // 存入缓冲区
    }
    // 发送ACK
}

// 重传定时器处理函数
void *timer_handler(void *arg) {
    Sender *sender = (Sender *)arg;
    while (sender->timer_running) {
        sleep(TIMEOUT);
        if (sender->timer_running) {
            // 重传未确认的数据包
            // 调整拥塞控制参数
        }
    }
    return NULL;
}

关键函数详细说明

  1. init_sender(Sender *sender, const char *ip)
    • 功能:初始化发送方的套接字、服务器地址以及拥塞控制和发送状态相关的变量。
    • 参数:
      • sender:指向发送方状态结构体的指针。
      • ip:服务器的IP地址。
  2. init_receiver(Receiver *receiver)
    • 功能:初始化接收方的套接字、绑定地址以及接收状态相关的变量。
    • 参数:
      • receiver:指向接收方状态结构体的指针。
  3. send_packet(Sender *sender, Packet *packet)
    • 功能:将数据包发送到服务器,并更新发送方的状态,如将数据包存入发送缓冲区,更新序列号,并在必要时启动重传定时器。
    • 参数:
      • sender:指向发送方状态结构体的指针。
      • packet:指向要发送的数据包的指针。
  4. receive_packet(Receiver *receiver)
    • 功能:从套接字接收数据包,根据序列号进行处理,按序交付给应用层或存入缓冲区,并发送ACK。
    • 参数:
      • receiver:指向接收方状态结构体的指针。
  5. timer_handler(void *arg)
    • 功能:作为重传定时器的线程函数,定时检查是否有未确认的数据包需要重传,并调整拥塞控制参数。
    • 参数:
      • arg:指向发送方状态结构体的指针。