epoll非阻塞情况下数据分片的处理
写在前面
本文是博主在处理resp 协议时,发现每次客户端数据都分段传输,导致多次的数据校验失败,多次尝试,终于解决了这个问题!
对于处理接收到的数据,尤其是在使用基于事件的I/O模型(如epoll)进行网络编程时,数据的分片处理是一个常见且关键的问题。以下是对数据分片处理相关代码的整理和说明,这段代码主要用于处理接收到的分片数据,直到一个完整的消息被接收和处理。
数据分片处理核心函数
1. is_message_complete
函数
这个函数的作用是判断当前缓冲区中的数据是否构成了一个完整的消息。它的实现依赖于redis 的resp的协议,其中消息以一个星号(*)开始,紧接着是表示数组大小的数字,每个数组项后面跟着一个换行符’\n’。
resp 协议应以”\r\n“结尾,但window端调试只能使用’\n‘!
具体的请求格式如下:
*2\n$3\nget\n$6\norange\n
int is_message_complete(const std::string &msg, size_t &pos) {
if (msg.empty() || msg.front() != '*') return -1;
pos = msg.find('\n');
if (pos == std::string::npos) return -1;
int array_size = std::stoi(msg.substr(1, pos - 1)) * 2;
pos += 1;
for (int i = 0; i < array_size; ++i) {
pos = msg.find('\n', pos + 1);
if (pos == std::string::npos) return 0;
}
return 1;
}
参数解释:
msg
: 当前缓冲区的内容。pos
: 如果找到一个完整的消息,pos
将被更新为该消息末尾的位置。
返回值:
-1
: 消息格式错误或者缓冲区为空。0
: 消息不完整,需要等待更多数据。1
: 消息完整。
2. process_client_message
函数
此函数用于处理客户端发送的消息。它首先使用is_message_complete
函数检查消息是否完整。如果是,它将处理该消息并从缓冲区中删除。
bool process_client_message(std::string& msg) {
size_t pos = 0;
try {
int flag = is_message_complete(msg, pos);
if (flag == 1) {
std::cout << msg.substr(0, pos) << std::endl; // 处理消息
msg.erase(0, pos); // 删除已处理的消息
} else if (flag == 0) {
return false; // 消息不完整,等待更多数据
} else {
throw std::runtime_error("请求命令错误");
}
} catch (const std::exception &e) {
std::cerr << "请求命令错误 " << e.what() << std::endl;
msg.clear();
return false;
}
return true;
}
使用场景说明
在基于epoll
的服务器程序中,每当接收到客户端的数据时,该数据可能是分片传输的。由于TCP是基于流的协议,因此一个消息可能被分成多个包进行发送,这就需要在服务器端实现消息的重组。
当epoll_wait
检测到可读事件时,服务器程序将尝试从对应的客户端套接字读取数据。由于使用了非阻塞I/O,需要处理EAGAIN
或EWOULDBLOCK
错误,这两个错误表明当前没有更多的数据可供读取。
读取到的数据将被追加到与客户端套接字关联的缓冲区字符串中。之后,process_client_message
函数将尝试处理缓冲区中的数据。如果数据表示一个完整的消息,该消息将被处理并从缓冲区中移除;如果数据不完整,则保留在缓冲区中,等待接收更多的数据片段。
结语
以上代码和解释为您提供了一个处理接收到的分片数据直到构成完整消息的示例。这是网络编程中一个常见且关键的环节,特别是在实现基于事件的非阻塞I/O模型的服务器时。正确地处理分片数据对于确保网络通信的可靠性和效率至关重要。