Qt---处理粘包

思路

一个数据包由包头跟包体组成,包头中包含包体长度,包体为要发送的数据。发送端将数据打包,接收端将数据接收到缓冲区后,从缓冲区起始处解析数据,先找到包头,然后找到包头中表示包体大小的字段,根据包体大小找到包体数据。

一个粗糙的演示

粘包处理前

服务器端发送数据:

//出现一个新连接时调用
void myTcpServer::incomingConnection(int socketDescriptor)
{
    clientConnection = new QTcpSocket;
    clientConnection->setSocketDescriptor(socketDescriptor);

	clientConnection->write("data1");
    clientConnection->write("data2");
    clientConnection->write("data3");
}

客户端接收数据

void myTcpClient::slotRead()
{
    while(tcpSocket->bytesAvailable()>0)
    {
        int length = tcpSocket->bytesAvailable();
        char buf[length];
        tcpSocket->read(buf, length);
        printf("%s\n", buf);
    }
}

出现粘包:
这里写图片描述

粘包处理后

服务器端

#pragma pack(push, 1) //按照1字节对齐
typedef struct
{
    int len;          //包头,包体长度
    char data[1024];  //包体
}NetPacket;
#pragma pack(pop)

//出现一个新连接时调用
void myTcpServer::incomingConnection(int socketDescriptor)
{
    clientConnection = new QTcpSocket;
    clientConnection->setSocketDescriptor(socketDescriptor);

    char *d1 = "data1";
    char *d2 = "data2";
    char *d3 = "data3";

    NetPacket p1, p2, p3;

    p1.len = sizeof("data1");     //封装第一个数据包
    memcpy(p1.data, d1, p1.len);

    p2.len = sizeof("data2");
    memcpy(p2.data, d2, p2.len);

    p3.len = sizeof("data3");
    memcpy(p3.data, d3, p3.len);

    clientConnection->write((char *)&p1, sizeof(int) + p1.len);  //发送数据包
    clientConnection->write((char *)&p2, sizeof(int) + p2.len);
clientConnection->write((char *)&p3, sizeof(int) + p3.len);
}

客户端

void myTcpClient::slotRead()
{
    while(tcpSocket->bytesAvailable()>0)
    {

        int len;
        char buf[1024];    //接收数据的缓冲区
        char tmpBuf[1024]; //存放包体
        int nOffset = 0;   //偏移

        int n = tcpSocket->bytesAvailable(); //接收到的字节数
        tcpSocket->read(buf, n);             

        memcpy(&len, buf, sizeof(int));    //包头:包体长度
        nOffset += sizeof(int);  
        memcpy(tmpBuf, buf+nOffset, len);  //包体
        nOffset += len;
        printf("%s\n", tmpBuf);            //打印包体

        memcpy(&len, buf, sizeof(int));
        nOffset += sizeof(int);
        memcpy(tmpBuf, buf+nOffset, len);
        nOffset += len;
        printf("%s\n", tmpBuf);

        memcpy(&len, buf, sizeof(int));
        nOffset += sizeof(int);
        memcpy(tmpBuf, buf+nOffset, len);
        nOffset += len;
        printf("%s\n", tmpBuf);

    }
}

运行结果:
这里写图片描述

完整版

服务器端和客户端共有的文件

databuffer.h

#ifndef NETDATABUFFER_H
#define NETDATABUFFER_H

#define BUFFER_SIZE  1024  //初始缓冲区大小

class DataBuffer
{
public:
    char *m_pBuffer;    //缓冲区
    int m_nBufferSize;  //缓冲区大小
    int m_nOffset;      //缓冲区中当前数据大小

    int getDataLen();         //获得缓冲区中数据大小
    bool reBufferSize(int nLen); //调整缓冲区大小
    bool addMsg(char *pBuf, int nLen);  //添加消息到缓冲区
    void reset();          //缓冲区复位
    void poll(int nLen);   //移除缓冲区首部的第一个数据包

 public:
    DataBuffer();
    ~DataBuffer();
};

#endif // NETDATABUFFER_H

databuffer.cpp

#include "databuffer.h"
#include "string.h"
#include 

//构造
DataBuffer::DataBuffer()
{
    m_nBufferSize = BUFFER_SIZE;  //缓冲区大小
    m_nOffset = 0;      //缓冲区当前现有数据大小
    m_pBuffer = new char[m_nBufferSize];      //分配缓冲区
    memset(m_pBuffer, 0, sizeof(m_pBuffer));  //清空缓冲区
}

//析构
DataBuffer::~DataBuffer()
{
    delete [] m_pBuffer;  //释放缓冲区
    m_pBuffer = NULL;
    m_nBufferSize = 0;
    m_nOffset = 0;
}

//获得缓冲区中数据大小
int DataBuffer::getDataLen()
{
    return m_nOffset;
}

//重置缓冲区大小
bool DataBuffer::reBufferSize(int nLen)
{
    char *oBuffer = m_pBuffer;  //保存原缓冲区地址
    try
    {
        nLen = nLen < 64 ? 64: nLen;  //保证最小大小
        while(m_nBufferSize < nLen)
        {
            m_nBufferSize *= 2;
        }
        m_pBuffer = new char[m_nBufferSize]; //分配新缓冲区
        memset(m_pBuffer, 0, sizeof(m_pBuffer));
        memcpy(m_pBuffer, oBuffer, m_nOffset); //将原缓冲区中的内容拷贝到新缓冲区
        delete []oBuffer;  //释放原缓冲区
    }
    catch(QException e)
    {
        return false;
    }
    return true;
}

//向缓冲区中添加消息
/*
 * pBuf,要添加的数据
 * nLen,数据长度
 * 成功返回true,失败返回false
 */
bool DataBuffer::addMsg(char *pBuf, int nLen)
{
    try
    {
        if(m_nOffset + nLen > m_nBufferSize)        //如果缓冲过小,重新调整其大小
            reBufferSize(m_nOffset + nLen);
        memcpy(m_pBuffer + m_nOffset, pBuf, nLen); //将新数据拷贝到缓冲区尾
        m_nOffset += nLen;  //修改数据偏移
    }
    catch(QException e)
    {
        return false;
    }
    return true;
}

//缓冲区复位
void DataBuffer::reset()
{
    if(m_nOffset > 0)
    {
        memset(m_pBuffer, 0, sizeof(m_pBuffer));
        m_nOffset = 0;
    }
}

//移除缓冲区首部第一个数据包
//nLen:一个数据包的大小
void DataBuffer::poll(int nLen)
{
    if(m_nOffset == 0 || m_pBuffer == NULL)
        return;

   if(m_nOffset >= nLen)
   {
        memcpy(m_pBuffer, m_pBuffer + nLen, m_nOffset - nLen);
        m_nOffset -= nLen;
   }
}

netcom.h

#ifndef NETTEMPLATE_H
#define NETTEMPLATE_H

#include 
#include 
#include "databuffer.h"

#pragma pack(push, 1) //采用1字节对齐方式

//包头
typedef struct
{
    int nLen;  //包体长度
}PacketHead;

//封包对象:包头 + 包体
typedef struct
{
    PacketHead head;  //包头
    char *body;       //包体
}Packet;

#pragma pack(pop)

class NetComTemplate
{
 public:
    QTcpSocket *m_tcpSocket;  //通信套接字
    DataBuffer m_Buffer;      //套接字关联的缓冲区

    void packData(char *data, int nLen);   //封包,发送
    void unpackData(char *data, int nLen); //将接收到的数据放在缓冲区后,解包
    virtual void recv(char *data);         //每解完一包之后的处理,留给继承的类去实现

    NetComTemplate();
    ~NetComTemplate();
};

#endif // NETTEMPLATE_H

netcom.cpp

#include "netcom.h"

NetComTemplate::NetComTemplate()
{

}

NetComTemplate::~NetComTemplate()
{

}

//封包,发送
//data: 要发送的数据
//nLen: 要发送数据的长度
void NetComTemplate::packData(char *data, int nLen)
{
    Packet p;
    int headLen = sizeof(PacketHead);         //包头大小
    p.head.nLen = nLen;                       //包体大小
    char *buf = new char[headLen + nLen];
    memcpy(buf, &p.head, headLen);            //包头
    memcpy(buf + headLen, data, nLen);        //包体
    if(m_tcpSocket != NULL)
        m_tcpSocket->write(buf, headLen + nLen);  //发包
    else
        qDebug() << "socket 未建立!";
}

//解包
//data: 要发送的数据
//nLen: 要发送数据的长度
void NetComTemplate::unpackData(char *data, int nLen)
{
    m_Buffer.addMsg(data, nLen);              //添加数据到缓冲区
    int totalLen = m_Buffer.getDataLen();  //缓冲区中数据大小
    int headLen = sizeof(PacketHead);         //包头大小
    while(totalLen > 0)
    {
        //不够包头,不处理
        if(totalLen < headLen)
        {
            break;
        }

        Packet pack;                      //接收到的包
        memcpy(&pack.head, m_Buffer.m_pBuffer, headLen);   //包头
        int bodyLen = pack.head.nLen;     //包体大小
        int packLen = headLen + bodyLen;  //一包数据大小
        if(totalLen < packLen)            //不够一包数据,等够了再解析
        {
            break;
        }

        //数据足够多
        pack.body = new char[bodyLen];
        memcpy(pack.body, m_Buffer.m_pBuffer + headLen, bodyLen);  //包体
        recv(pack.body);         //处理得到的包体

        m_Buffer.poll(packLen);  //移除缓冲区中第一个数据包
        totalLen -= (packLen);
    }
}

//留给继承的类去实现
//buf: 解包后得到的包体
void NetComTemplate::recv(char *data)
{

}

服务器端

tcpserver.h

#ifndef TCPSERVER_H
#define TCPSERVER_H

#include 
#include 
#include 
#include "netcom.h"

class myTcpServer : public QTcpServer, public NetComTemplate
{
    Q_OBJECT

public:
    myTcpServer(QObject *parent, int port);
    ~myTcpServer();

protected:
    void incomingConnection(int socketDescriptor);
};

#endif // TCPSERVER_H

tcpserver.cpp

#include "tcpserver.h"
#include 
#include 

myTcpServer::myTcpServer(QObject *parent, int port): QTcpServer(parent)
{
    listen(QHostAddress::Any, port);
}

myTcpServer::~myTcpServer()
{
    
}

//出现一个新连接时调用
void myTcpServer::incomingConnection(int socketDescriptor)
{
    m_tcpSocket = new QTcpSocket;
    m_tcpSocket->setSocketDescriptor(socketDescriptor);

    char *d1 = "data1";
    char *d2 = "data2";
    char *d3 = "data3";

    packData(d1, sizeof("data1"));   //封包,发送
    packData(d2, sizeof("data2"));
    packData(d3, sizeof("data3"));
}

客户端

tcpclient.h

#ifndef MYTCPCLIENT_H
#define MYTCPCLIENT_H

#include 
#include 
#include "netcom.h"

class myTcpClient : public QObject, public NetComTemplate
{
    Q_OBJECT

public:
myTcpClient(QObject *parent = 0);
    ~myTcpClient();

    void recv(char *data);           //每解完一包之后的处理

public slots:
    void slotRead();
};

#endif // MYTCPCLIENT_H

tcpclient.cpp

#include "tcpclient.h"

myTcpClient::myTcpClient(QObject *parent) : QObject(parent)
{
    m_tcpSocket = new QTcpSocket;
    connect(m_tcpSocket, SIGNAL(readyRead()), this, SLOT(slotRead()));
    m_tcpSocket->connectToHost(QHostAddress("127.0.0.1"), 8180);
}

myTcpClient::~myTcpClient()
{
 
}

void myTcpClient::slotRead()
{
    while(m_tcpSocket->bytesAvailable()>0)
    {
        int n = m_tcpSocket->bytesAvailable();  //接收到的字节数
        char *buf = new char[n];
        m_tcpSocket->read(buf, n);   //读取数据
        unpackData(buf, n);   //解包
	delete []buf;
    }
}

//解包之后的处理
void myTcpClient::recv(char *data)
{
    printf("%s\n", data);
}

完善版(使用环形缓冲区)

相对于“完整版”所改动的地方

databuffer.h

#ifndef NETDATABUFFER_H
#define NETDATABUFFER_H

#define BUFFER_SIZE  1024  //初始缓冲区大小

class DataBuffer
{
public:
    char *m_pBuffer;    //缓冲区
    int m_nBufferSize;  //缓冲区大小
    int m_nStart;       //数据开始位置
    int m_nEnd;         //数据结束位置
    bool m_isFull;      //缓冲区是否已满
    bool m_isEmpty;     //缓冲区是否为空

    int getDataLen();            //获得缓冲区中数据大小
    bool reBufferSize(int nLen); //调整缓冲区大小
    bool addMsg(char *pBuf, int nLen);  //添加消息到缓冲区
    void poll(int nLen);   //移除缓冲区首部的第一个数据包
    void reset();          //缓冲区复位

 public:
    DataBuffer();
    ~DataBuffer();
};

#endif // NETDATABUFFER_H

databuffer.cpp

#include "databuffer.h"
#include "string.h"
#include 

//构造
DataBuffer::DataBuffer()
{
    m_nBufferSize = BUFFER_SIZE;  //缓冲区大小
    m_nStart = 0;                 //数据开始位置
    m_nEnd = 0;                   //数据结束位置
    m_isFull = false;             //缓冲区是否已满
    m_isEmpty = true;             //缓冲区是否为空
    m_pBuffer = new char[m_nBufferSize];      //分配缓冲区
    memset(m_pBuffer, 0, sizeof(m_pBuffer));  //清空缓冲区
}

//析构
DataBuffer::~DataBuffer()
{
    delete [] m_pBuffer;  //释放缓冲区
    m_pBuffer = NULL;
    m_nBufferSize = 0;
}

//获得缓冲区中数据大小
int DataBuffer::getDataLen()
{
    if(m_isFull)
    {
        return m_nBufferSize;
    }
    else if(m_nEnd < m_nStart)
    {
        return (m_nBufferSize - m_nStart) + m_nEnd;
    }
    else
    {
        return m_nEnd - m_nStart;
    }
}


//重置缓冲区大小
bool DataBuffer::reBufferSize(int nLen)
{
    char *oBuffer = m_pBuffer;  //保存原缓冲区地址
    try
    {
        nLen = nLen < 64 ? 64: nLen;  //保证最小大小
        while(m_nBufferSize < nLen)
        {
            m_nBufferSize *= 2;
        }
        m_pBuffer = new char[m_nBufferSize]; //分配新缓冲区
        memset(m_pBuffer, 0, sizeof(m_pBuffer));

        //将原缓冲区中的内容拷贝到新缓冲区
        if(m_nStart < m_nEnd)
        {
            memcpy(m_pBuffer, oBuffer + m_nStart, m_nEnd - m_nStart);
        }
        else
        {
            int len1 = m_nBufferSize - m_nStart;
            memcpy(m_pBuffer, oBuffer + m_nStart, len1);
            memcpy(m_pBuffer + len1, oBuffer, m_nEnd);
        }

        delete []oBuffer;  //释放原缓冲区
    }
    catch(QException e)
    {
        return false;
    }
    return true;
}

//向缓冲区中添加消息
/*
 * pBuf,要添加的数据
 * nLen,数据长度
 * 成功返回true,失败返回false
 */
bool DataBuffer::addMsg(char *pBuf, int nLen)
{
    try
    {
        if(nLen == 0 || pBuf == NULL)
        {
            return false;
        }
        if(getDataLen() + nLen > m_nBufferSize)       //如果缓冲区过小,重新调整其大小
        {
            reBufferSize(getDataLen() + nLen);
            memcpy(m_pBuffer + m_nEnd, pBuf, nLen);   //将数据添加到缓冲区尾
            m_nEnd += nLen;
            m_isFull = m_nStart == m_nEnd;
        }
        else if(m_nStart <= m_nEnd)
        {
            int rightLen = m_nBufferSize - m_nEnd;    //缓冲区右半部分长度
            if(nLen <= rightLen)
            {
                memcpy(m_pBuffer + m_nEnd, pBuf, nLen);
                m_nEnd += nLen;
                m_isFull = m_nStart == m_nEnd;
            }
            else
            {
                int leftLen = nLen - rightLen;        //剩余数据长度
                memcpy(m_pBuffer + m_nEnd, pBuf, rightLen);
                memcpy(m_pBuffer, pBuf, leftLen);
                m_nEnd = leftLen;
                m_isFull = m_nStart == m_nEnd;
            }
        }
        else
        {
            memcpy(m_pBuffer + m_nEnd, pBuf, nLen);
            m_nEnd += nLen;
            m_isFull = m_nStart == m_nEnd;
        }
        m_isEmpty = false;
    }
    catch(QException e)
    {
        return false;
    }
    return true;
}

//缓冲区复位
void DataBuffer::reset()
{
    if(!m_isEmpty)
    {
        memset(m_pBuffer, 0, sizeof(m_pBuffer));
        m_nStart = 0;
        m_nEnd = 0;
    }
}

//移除缓冲区首部第一个数据包
//nLen:一个数据包的大小
void DataBuffer::poll(int nLen)
{
    if(m_isEmpty || getDataLen() < nLen || nLen == 0 || m_pBuffer == NULL)
    {
        return;
    }

    if(m_nStart < m_nEnd)
    {
        m_nStart += nLen;
        m_isEmpty = m_nStart == m_nEnd;
    }
    else
    {
        int rightLen;
        if(m_nStart == m_nEnd)
        {
            rightLen = m_nBufferSize - m_nEnd;    //缓冲区右半部分长度
        }
        else
        {
            rightLen = m_nBufferSize - m_nStart;  //右半部分数据长度
        }

        if(nLen <= rightLen)   //如果数据包大小 < 缓冲区右面的数据
        {
            m_nStart += nLen;
            m_isEmpty = m_nStart == m_nEnd;
        }
        else
        {
            int leftLen = nLen - rightLen;
            m_nStart = leftLen;
            m_isEmpty = m_nStart == m_nEnd;
        }
    }
}

netcom.cpp

//解包
//data: 要发送的数据
//nLen: 要发送数据的长度
void NetComTemplate::unpackData(char *data, int nLen)
{
    m_Buffer.addMsg(data, nLen);              //添加数据到缓冲区
    int totalLen = m_Buffer.getDataLen();     //缓冲区中数据大小
    int headLen = sizeof(PacketHead);         //包头大小
    while(totalLen > 0)
    {
        //不够包头,不处理
        if(totalLen < headLen)
        {
            break;
        }

        Packet pack;                      //接收到的包
        memcpy(&pack.head, m_Buffer.m_pBuffer + m_Buffer.m_nStart, headLen);   //包头
        int bodyLen = pack.head.nLen;     //包体大小
        int packLen = headLen + bodyLen;  //一包数据大小
        if(totalLen < packLen)            //不够一包数据,等够了再解析
        {
            break;
        }

        //数据足够多
        pack.body = new char[bodyLen];
        memcpy(pack.body, m_Buffer.m_pBuffer + m_Buffer.m_nStart + headLen, bodyLen);  //包体
        recv(pack.body);         //处理得到的包体

        m_Buffer.poll(packLen);  //移除缓冲区中第一个数据包
        totalLen -= (packLen);
    }
}

参看:
http://blog.csdn.net/pi9nc/article/details/17165171
http://www.aiuxian.com/article/p-1732805.html#t0
http://www.cnblogs.com/alon/archive/2009/04/16/1437599.html
http://www.aiuxian.com/article/p-1732805.html#t0
http://blog.163.com/qimo601@126/blog/static/1582209320121169244219/

你可能感兴趣的:(Qt)