Skip to content

情况一 ​

当收到数据时,系统会提醒,然后把该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 还得按情况一来处理。

学 习 记 录