Qt—处理粘包
思路
一个数据包由包头跟包体组成,包头中包含包体长度,包体为要发送的数据。发送端将数据打包,接收端将数据接收到缓冲区后,从缓冲区起始处解析数据,先找到包头,然后找到包头中表示包体大小的字段,根据包体大小找到包体数据。
一个粗糙的演示
粘包处理前
服务器端发送数据:
//出现一个新连接时调用
void myTcpServer::incomingConnection(int socketDescriptor)
{
clientConnection = new QTcpSocket;
clientConnection->setSocketDescriptor(socketDescriptor);
clientConnection->write("data1");
clientConnection->write("data2");
clientConnection->write("data3");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
客户端接收数据
void myTcpClient::slotRead()
{
while(tcpSocket->bytesAvailable()>0)
{
int length = tcpSocket->bytesAvailable();
char buf[length];
tcpSocket->read(buf, length);
printf("%s\n", buf);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
出现粘包:
粘包处理后
服务器端
#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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
客户端
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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
运行结果:
完整版
服务器端和客户端共有的文件
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
databuffer.cpp
#include "databuffer.h"
#include "string.h"
#include <QException>
//构造
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
netcom.h
#ifndef NETTEMPLATE_H
#define NETTEMPLATE_H
#include <QTcpSocket>
#include <QDebug>
#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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
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)
{
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
服务器端
tcpserver.h
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <QTcpServer>
#include <QTcpSocket>
#include <QObject>
#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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
tcpserver.cpp
#include "tcpserver.h"
#include <QDataStream>
#include <stdlib.h>
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"));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
客户端
tcpclient.h
#ifndef MYTCPCLIENT_H
#define MYTCPCLIENT_H
#include <QTcpSocket>
#include <QtNetwork>
#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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
完善版(使用环形缓冲区)
相对于“完整版”所改动的地方
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
databuffer.cpp
#include "databuffer.h"
#include "string.h"
#include <QException>
//构造
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;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
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, 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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
参看:
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/
(function () {
(function () {
('pre.prettyprint code').each(function () { var lines =
(this).text().split(′\n′).length;var
(
t
h
i
s
)
.
t
e
x
t
(
)
.
s
p
l
i
t
(
′
\n
′
)
.
l
e
n
g
t
h
;
v
a
r
numbering = $('
-
').addClass('pre-numbering').hide();
(this).addClass(′has−numbering′).parent().append(
(
t
h
i
s
)
.
a
d
d
C
l
a
s
s
(
′
h
a
s
−
n
u
m
b
e
r
i
n
g
′
)
.
p
a
r
e
n
t
(
)
.
a
p
p
e
n
d
(
numbering); for (i = 1; i
思路
一个数据包由包头跟包体组成,包头中包含包体长度,包体为要发送的数据。发送端将数据打包,接收端将数据接收到缓冲区后,从缓冲区起始处解析数据,先找到包头,然后找到包头中表示包体大小的字段,根据包体大小找到包体数据。
一个粗糙的演示
粘包处理前
服务器端发送数据:
//出现一个新连接时调用
void myTcpServer::incomingConnection(int socketDescriptor)
{
clientConnection = new QTcpSocket;
clientConnection->setSocketDescriptor(socketDescriptor);
clientConnection->write("data1");
clientConnection->write("data2");
clientConnection->write("data3");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
客户端接收数据
void myTcpClient::slotRead()
{
while(tcpSocket->bytesAvailable()>0)
{
int length = tcpSocket->bytesAvailable();
char buf[length];
tcpSocket->read(buf, length);
printf("%s\n", buf);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
出现粘包:
粘包处理后
服务器端
#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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
客户端
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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
运行结果:
完整版
服务器端和客户端共有的文件
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
databuffer.cpp
#include "databuffer.h"
#include "string.h"
#include <QException>
//构造
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
netcom.h
#ifndef NETTEMPLATE_H
#define NETTEMPLATE_H
#include <QTcpSocket>
#include <QDebug>
#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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
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)
{
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
服务器端
tcpserver.h
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <QTcpServer>
#include <QTcpSocket>
#include <QObject>
#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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
tcpserver.cpp
#include "tcpserver.h"
#include <QDataStream>
#include <stdlib.h>
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"));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
客户端
tcpclient.h
#ifndef MYTCPCLIENT_H
#define MYTCPCLIENT_H
#include <QTcpSocket>
#include <QtNetwork>
#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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
完善版(使用环形缓冲区)
相对于“完整版”所改动的地方
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
databuffer.cpp
#include "databuffer.h"
#include "string.h"
#include <QException>
//构造
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;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
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, 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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
参看:
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/
- ').addClass('pre-numbering').hide(); (this).addClass(′has−numbering′).parent().append( ( t h i s ) . a d d C l a s s ( ′ h a s − n u m b e r i n g ′ ) . p a r e n t ( ) . a p p e n d ( numbering); for (i = 1; i