情况一
当收到数据时,系统会提醒,然后把该socket的缓冲区的所有数据一次收完 如current_client_sock->readAll();
c++
#define HEAD_SIZE 2
#define HEAD_BODY_SIZE 4096
struct Head{
unsigned int data_len;
// unsigned char other_data[1020];
};
unsigned int head_p;
unsigned char head[HEAD_SIZE];
unsigned int body_p;
unsigned char head_body[HEAD_BODY_SIZE];
std::queue<std::string> pack_queue;
QObject::connect(current_client_sock,&QTcpSocket::readyRead,this,[=](){
QByteArray q_data = current_client_sock->readAll();
int q_have_read = 0; //记录当前这个q_data读到哪儿了
//1 q_data: a包的包头包体
//2 q_data: a包的部分内容 + b包的部分内容
//3 q_data: a包的部分内容 + b包头包体 + c包的部分内容
//看看q_data 还剩多少可以读 可以读的部分是否大于一个包头
while(q_data.size() - q_have_read >= HEAD_SIZE){
//先看看包头缓冲区读满没有 如果没读满 说明还在读当前这个包
if(head_p != HEAD_SIZE){
//还可以读一个包头
int read = HEAD_SIZE - head_p;
memcpy(head + head_p,q_data.data() + q_have_read,read);
q_have_read += read;
head_p += read;
//这时包头读满了
}
//如果发现包头读满了 说明在读上一个包 接着读包体
//先检查上一个包的包头中,记录包体长度的字段 h.data_len可能需要转换字节序
Head h = *(Head*)head;
//如果body_p 没有达到h.data_len 说明上一个包还在读
if(body_p < h.data_len){
//计算还差多少
int cha = h.data_len - body_p;
//如果这个差值 大于q_data.size() 说明这个包体只能在下一个readyRead触发时候完成 本次完成不了 只能减小这个差值
if(cha > q_data.size()){
memcpy(head_body+body_p,q_data.data()+q_have_read,q_data.size());
body_p += q_data.size();
q_have_read += q_data.size();
}
else if(cha == q_data.size()){ //说明读完q_data 正好能读完这个包体
memcpy(head_body+body_p,q_data.data()+q_have_read,cha);
body_p += cha;
q_have_read += cha;
}else{ //如果q_data.size() 大于 cha 说明q_data里不仅有当前这个包的包体 还有下一个包的内容
//所以按cha读完以后
memcpy(head_body+body_p,q_data.data()+q_have_read,cha);
body_p += cha;
q_have_read += cha;
//当前包已经读完 打包给业务层
std::string head_string = std::string((char*)head,HEAD_SIZE);
std::string body_string = std::string((char*)head_body,body_p);
//此时如果要读包尾 从body_string中读即可
qDebug()<<u8"包体长: " << ((Head*)head)->data_len <<u8"包体: "<< QString::fromStdString(body_string);
pack_queue.push(head_string+body_string);
//清空包头包体指针 读下一个包
head_p = 0;
body_p = 0;
//但是此时q_data中 q_have_read < q_data.size();
//你必须得从这行开始读下一个包 所以进入下一个while循环 或者进不去while循环
}
}else{//说明上一个包已经读完 把上一个包的包头包体打包发给业务层
std::string head_string = std::string((char*)head,HEAD_SIZE);
std::string body_string = std::string((char*)head_body,body_p);
//此时如果要读包尾 从body_string中读即可
qDebug()<<u8"包体长: " << ((Head*)head)->data_len <<u8"包体: "<< QString::fromStdString(body_string);
pack_queue.push(head_string + body_string);
//清空包头包体指针 读下一个包
head_p = 0;
body_p = 0;
}
//我可以保证 每次while循环以后 包头包体_p 都是0
}
//说明q_data的内容 已经读不了一个完整包头了
int leave = q_data.size() - q_have_read; //q_data里还剩多少数据
//但是剩多少 就得读多少 你不能说不要了 或者想放回读缓冲区 这是不切实际的
memcpy(head, q_data.data() + q_have_read, leave);
q_have_read += leave;
head_p += leave;
});情况二
只要缓冲区有数据 操作系统就会通知应用程序 就会触发lambda表达式
cpp
QObject::connect(current_client_sock,&QTcpSocket::readyRead,this,[=](){
//先读出两个字节的包头
QByteArray head = current_client_sock->read(2);
//然后再读包体
int data_len = ((Head*)head.data())->data_len;
QByteArray body = current_client_sock->read(data_len);
}
这样当socket是非阻塞时会有问题,因为假设对端是 Send("5hello");这样发过来的
但是在TCP层面可能会被拆成很多小块到达你的接收缓冲区 比如到达"he"以后会触发一次
readyRead 到达"l"以后会触发一次,以此类推。 这样会导致你的缓冲区里面不一定有一个完整
的包体。 所以这种代码只适合socket是阻塞接收数据的情况。如果用非阻塞socket 还得按情况一来处理。