给一个类似qq聊天协议,可以发文字,表情,图片,音频,文件等内容。要考虑文件,图片或者音频内容超过了udp最大有效载荷

以下是一个较为完整的C++实现的类似QQ聊天协议的代码示例,涵盖了发送文字、表情、图片、音频、文件等多种消息类型,考虑了数据量超过UDP最大有效载荷时的处理方式,并且加入了相对完善的可靠性机制(如校验和、确认与重传、超时处理等)。代码中使用了之前介绍的内存池来优化内存分配与管理,提升性能(内存池部分代码可复用之前的实现)。

1. 相关头文件和命名空间

#include <iostream>
#include <fstream>
#include <cstring>
#include <ctime>
#include <cstdlib>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <vector>
#include <map>
#include <thread>
#include <mutex>
#include <queue>
#include "MemoryPool.h"  // 假设之前的内存池代码放在MemoryPool.h和MemoryPool.cpp中,这里包含头文件
using namespace std;

2. 定义数据包首部结构体

// 自定义数据包首部结构体
struct ChatPacketHeader {
    unsigned short version;  // 协议版本号,用于后续协议升级兼容等情况,初始设为1
    unsigned char msg_type;  // 消息类型,如1表示文字,2表示表情,3表示图片,4表示音频,5表示文件等
    unsigned short total_packets;  // 当消息被分割成多个UDP数据包时,此为总数据包数量,若无需分割则为1
    unsigned short packet_seq;  // 当前数据包在整个消息中的序号,从0开始
    unsigned int msg_id;  // 消息唯一标识,用于区分不同的消息,比如可以用时间戳结合随机数生成
    unsigned int sender_id;  // 发送者的唯一标识,比如用户账号对应的编号等
    unsigned int receiver_id;  // 接收者的唯一标识,与发送者对应
    unsigned short data_length;  // 当前数据包中数据部分的长度(即除去首部后的有效数据长度)
};

3. 计算校验和的函数(这里采用简单的CRC16校验算法示例,可替换为更合适的算法)

// CRC16校验算法函数(简单示例,可优化)
unsigned short calculateCRC16(const char* data, int length) {
    unsigned short crc = 0xFFFF;
    for (int i = 0; i < length; ++i) {
        crc ^= (unsigned short)data[i] << 8;
        for (int j = 0; j < 8; ++j) {
            if (crc & 0x8000) {
                crc = (crc << 1) ^ 0x1021;
            } else {
                crc <<= 1;
            }
        }
    }
    return crc;
}

4. 发送端类的定义及实现

class ChatSender {
public:
    ChatSender(const char* destination_ip, int destination_port, size_t pool_size) : 
        dest_ip(destination_ip), dest_port(destination_port), 
        memoryPool(pool_size), unconfirmedPacketsMutex(), unconfirmedPackets(),
        ackReceivedMutex(), ackReceived()
    {
        // 创建UDP套接字
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0) {
            perror("Socket creation failed");
            exit(1);
        }

        // 配置服务器地址结构体
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(destination_port);
        server_addr.sin_addr.s_addr = inet_addr(destination_ip);

        // 启动一个线程用于处理未确认数据包的超时和重传
        thread retransmitThread(&ChatSender::handleUnconfirmedPacketsThread, this);
        retransmitThread.detach();
    }

    ~ChatSender() {
        close(sockfd);
    }

    // 发送文字消息
    void sendTextMessage(const string& text, unsigned int sender_id, unsigned int receiver_id) {
        ChatPacketHeader header;
        header.version = 1;
        header.msg_type = 1;  // 文字消息类型为1
        header.total_packets = 1;
        header.packet_seq = 0;
        header.msg_id = generateMessageId();
        header.sender_id = sender_id;
        header.receiver_id = receiver_id;
        header.data_length = text.length();

        char* buffer = static_cast<char*>(memoryPool.allocate(sizeof(header) + text.length() + sizeof(unsigned short)));
        if (!buffer) {
            cerr << "Memory allocation failed for text message" << endl;
            return;
        }

        memcpy(buffer, &header, sizeof(header));
        memcpy(buffer + sizeof(header), text.c_str(), text.length());

        // 计算校验和并添加到数据包末尾
        unsigned short checksum = calculateCRC16(buffer, sizeof(header) + text.length());
        memcpy(buffer + sizeof(header) + text.length(), &checksum, sizeof(checksum));

        // 发送数据包
        if (sendto(sockfd, buffer, sizeof(header) + text.length() + sizeof(checksum), 0, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
            perror("Sendto failed");
        } else {
            // 记录已发送的数据包信息
            UnconfirmedPacket packet;
            packet.header = header;
            packet.data = buffer;
            packet.send_time = time(nullptr);
            {
                lock_guard<mutex> guard(unconfirmedPacketsMutex);
                unconfirmedPackets.push(packet);
            }
        }
    }

    // 发送图片消息(假设图片以二进制形式读取发送,这里简单示意,可优化图片处理逻辑)
    void sendImageMessage(const char* image_path, unsigned int sender_id, unsigned int receiver_id) {
        ifstream imageFile(image_path, ios::binary);
        if (!imageFile) {
            cerr << "Failed to open image file" << endl;
            return;
        }

        imageFile.seekg(0, ios::end);
        long int image_size = imageFile.tellg();
        imageFile.seekg(0, ios::beg);

        const int max_data_size = 1472 - sizeof(ChatPacketHeader);  // 假设基于以太网类似场景,预留首部空间
        int total_packets = image_size / max_data_size + (image_size % max_data_size > 0? 1 : 0);

        ChatPacketHeader header;
        header.version = 1;
        header.msg_type = 3;  // 图片消息类型为3
        header.total_packets = total_packets;
        header.msg_id = generateMessageId();
        header.sender_id = sender_id;
        header.receiver_id = receiver_id;

        char* buffer = nullptr;
        int packet_seq = 0;
        while (!imageFile.eof()) {
            header.packet_seq = packet_seq;
            imageFile.read(buffer + sizeof(header), max_data_size);
            int read_size = imageFile.gcount();
            header.data_length = read_size;

            memcpy(buffer, &header, sizeof(header));

            // 计算校验和并添加到数据包末尾
            unsigned short checksum = calculateCRC16(buffer, sizeof(header) + read_size);
            memcpy(buffer + sizeof(header) + read_size, &checksum, sizeof(checksum));

            // 发送数据包
            if (sendto(sockfd, buffer, sizeof(header) + read_size + sizeof(checksum), 0, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
                perror("Sendto failed");
            } else {
                // 记录已发送的数据包信息
                UnconfirmedPacket packet;
                packet.header = header;
                packet.data = buffer;
                packet.send_time = time(nullptr);
                {
                    lock_guard<mutex> guard(unconfirmedPacketsMutex);
                    unconfirmedPackets.push(packet);
                }
            }

            packet_seq++;
        }

        imageFile.close();
    }

    // 其他类型消息(如表情、音频、文件等)发送函数可以类似地按照上述思路编写,这里省略

    // 简单的消息ID生成函数(示例,可改进)
    unsigned int generateMessageId() {
        return static_cast<unsigned int>(time(nullptr)) * 1000 + rand();
    }

    // 处理未确认数据包的超时和重传的线程函数
    void handleUnconfirmedPacketsThread() {
        while (true) {
            this_thread::sleep_for(chrono::milliseconds(500));  // 每隔一定时间检查一次,可调整间隔时间
            vector<UnconfirmedPacket> toRetransmit;
            {
                lock_guard<mutex> guard(unconfirmedPacketsMutex);
                time_t current_time = time(nullptr);
                while (!unconfirmedPackets.empty()) {
                    // 假设超时时间为5秒(可根据实际调整)
                    if (current_time - unconfirmedPackets.front().send_time > 5) {
                        toRetransmit.push_back(unconfirmedPackets.front());
                        unconfirmedPackets.pop();
                    } else {
                        break;
                    }
                }
            }

            for (const auto& packet : toRetransmit) {
                // 重传数据包
                if (sendto(sockfd, packet.data, sizeof(packet.header) + packet.header.data_length + sizeof(unsigned short), 0, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
                    perror("Resend failed");
                } else {
                    // 更新发送时间
                    UnconfirmedPacket new_packet = packet;
                    new_packet.send_time = time(nullptr);
                    {
                        lock_guard<mutex> guard(unconfirmedPacketsMutex);
                        unconfirmedPackets.push(new_packet);
                    }
                }
            }
        }
    }

    // 接收确认消息的函数,用于处理接收端发送回来的确认信息
    void handleAck(const ChatPacketHeader& ack_header) {
        lock_guard<mutex> guard(ackReceivedMutex);
        ackReceived.push(ack_header.msg_id);
    }

private:
    int sockfd;
    struct sockaddr_in server_addr;
    const char* dest_ip;
    int dest_port;
    MemoryPool memoryPool;  // 使用内存池来分配数据包内存

    // 定义结构体用于记录已发送未确认的数据包信息
    struct UnconfirmedPacket {
        ChatPacketHeader header;
        char* data;
        time_t send_time;  // 记录发送时间,用于超时判断
    };

    mutex unconfirmedPacketsMutex;  // 互斥锁,用于保护未确认数据包队列的并发访问
    queue<UnconfirmedPacket> unconfirmedPackets;  // 存储已发送未确认的数据包队列

    mutex ackReceivedMutex;  // 互斥锁,用于保护已接收确认消息队列的并发访问
    queue<unsigned int> ackReceived;  // 存储已接收的确认消息(以消息ID标识)队列
};

5. 接收端类的定义及实现

class ChatReceiver {
public:
    ChatReceiver(int local_port, size_t pool_size) : 
        localPort(local_port), memoryPool(pool_size), receivedMessagesMutex(), receivedMessages()
    {
        // 创建UDP套接字并绑定端口
        sockfd = socket(AF_INET, SOCK_DUDP, 0);
        if (sockfd < 0) {
            perror("Socket creation failed");
            exit(1);
        }

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

        if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
            perror("Bind failed");
            close(sockfd);
            exit(1);
        }

        // 启动一个线程用于处理接收到的消息
        thread receiveThread(&ChatReceiver::receiveAndProcessMessagesThread, this);
        receiveThread.detach();
    }

    ~ChatReceiver() {
        close(sockfd);
    }

    // 接收并处理消息的线程函数
    void receiveAndProcessMessagesThread() {
        while (true) {
            char buffer[1472];
            struct sockaddr_in client_addr;
            socklen_t client_addr_len = sizeof(client_addr);
            int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_addr, &client_addr_len);
            if (n < 0) {
                perror("Recvfrom failed");
                continue;
            }

            ChatPacketHeader header;
            memcpy(&header, buffer, sizeof(header));

            // 验证校验和
            unsigned short received_checksum = *((unsigned short *)(buffer + sizeof(header) + header.data_length));
            unsigned short calculated_checksum = calculateCRC16(buffer, sizeof(header) + header.data_length);
            if (received_checksum!= calculated_checksum) {
                continue;  // 校验和不一致,丢弃数据包
            }

            switch (header.msg_type) {
            case 1:  // 文字消息
                handleTextMessage(buffer, header);
                break;
            case 3:  // 图片消息
                handleImageMessage(buffer, header);
                break;
            // 其他类型消息(如表情、音频、文件等)的处理函数可以类似地按照各自逻辑编写,这里省略
            default:
                cerr << "Unknown message type" << endl;
                break;
            }

            // 发送确认消息给发送端
            sendAck(header.msg_id, client_addr);
        }
    }

private:
    int sockfd;
    struct sockaddr_in server_addr;
    int localPort;
    MemoryPool memoryPool;  // 使用内存池来分配临时存储消息的内存

    mutex receivedMessagesMutex;  // 互斥锁,用于保护已接收消息数据结构的并发访问
    map<unsigned int, vector<string>> receivedMessages;  // 以消息ID为键,存储对应数据包内容

    // 处理文字消息(简单示例,可完善显示等功能)
    void handleTextMessage(const char* buffer, const ChatPacketHeader& header) {
        string text(buffer + sizeof(header), header.data_length);
        cout << "Received text message: " << text << endl;
        // 这里可以将文字消息添加到对应的聊天窗口等显示区域,略具体UI相关逻辑
    }

    // 处理图片消息(简单示意,可优化图片组装和显示逻辑)
    void handleImageMessage(const char* buffer, const ChatPacketHeader& header) {
        unsigned int msg_id = header.msg_id;
        {
            lock_guard<mutex> guard(receivedMessagesMutex);
            if (receivedMessages.find(msg_id) == receivedMessages.end()) {
                receivedMessages[msg_id] = vector<string>();
            }
            receivedMessages[msg_id].push_back(string(buffer + sizeof(header), header.data_length));
        }

        if (receivedMessages[msg_id].size() == header.total_packets) {
            // 所有数据包已接收,组装图片(这里省略具体组装图片的代码,可使用图像处理库等)
            cout << "Received image message" << endl;
            // 可以进行图片显示等后续操作,略具体UI相关逻辑
            {
                lock_guard<mutex> guard(receivedMessagesMutex);
                receivedMessages.erase(msg_id);
            }
        }
    }

    // 发送确认消息给发送端
    void sendAck(unsigned int msg_id, const struct sockaddr_in& client_addr) {
        ChatPacketHeader ack_header;
        ack_header.version = 1;
        ack_header.msg_type = 0;  // 自定义一个表示确认消息的类型
        ack_header.total_packets = 1;
        ack_header.packet_seq = 0;
        ack_header.msg_id = msg_id;
        ack_header.sender_id = 0;  // 这里可根据实际设置合适的标识,暂简单设为0
        ack_header.receiver_id = 0;  // 同理,暂简单设为0
        ack_header.data_length = 0;

        char buffer[sizeof(ChatPacketHeader)];
        memcpy(buffer, &ack_header, sizeof(ack_header));

        if (sendto(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) {
            perror("Send ack failed");
        }
    }
};

6. 主函数示例

int main() {
    // 发送端示例
    ChatSender sender("127.0.0.1", 8888, 1024 * 1024);  // 初始化内存池大小为1MB,可调整
    sender.sendTextMessage("Hello, this is a test text message!", 1, 2);
    sender.sendImageMessage("test.jpg", 1, 2);

    // 接收端示例
    ChatReceiver receiver(8888, 1024 * 1024);  // 初始化内存池大小为1MB,可调整
    // 这里可以让程序持续运行,比如通过一些条件来控制退出,略具体退出逻辑

    return 0;
}

请注意:

  1. 上述代码中的图片、音频、文件等处理
posted @ 2025-01-02 18:09  MarsCactus  阅读(67)  评论(0)    收藏  举报