给一个类似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;
}
请注意:
- 上述代码中的图片、音频、文件等处理

浙公网安备 33010602011771号