简述:
在Qt里利用TCP/IP协议,socket套接字设计实现结构体的收发,类似实现简单的自定义通信协议。
描述:
发送的结构体包含帧头header(占两字节)、数据长度dataLength(占一字节)、数据my_data(不多于64字节)、校验和check_sum(前面所有数据所占字节和,本身只占一个字节)。
发送方的结构体:
这里要特别注意== #pragma pack(1) ==的使用,涉及到结构体内存对齐,使用这行可以设置结构体对齐方式为1字节,这点特别重要,我在这个坑里绕了好久才走出来!!这样设置主要是因为后面要使用到结构体的大小sizeof(senddata)。
#define DATA_LEN 64
#pragma pack(1) //设置结构体为1字节对齐
typedef struct sendData
{
uchar header[2]; //帧头(2字节) uchar才能存十六进制数
uchar dataLength; //数据个数(1字节),小于64
char my_data[DATA_LEN]; //数据(小于64字节)
uchar check_sum; //校验和(1字节) 前面所有字节累加和
}senddata;
#pragma pack() //结束结构体对齐设置
注意:结构体中的数据存储最好不要用 char* 类型,在后面用到结构体强转、结构体转QByteArray数组时容易出错。转的时候可能只拷贝了char的地址,没有拷贝到数据;也有可能由于char数据长度不定,发送的时候出现问题;也有可能接收方收到解析的时候出现问题。
客户端发送:
总体思路:先封装填好帧头部分,然后从界面获取用户输入的数据,将其存进结构体my_data[ ]数组中(具体操作:获取的数据是字符串,要借助QByteArray作为中间桥梁进行转换),填好数据长度和校验和,至此要发送的结构体就封装好了。然后再将封装好的结构体转为QByteArray数组(因为传输都是Byte类型数据,直接发结构体会报错),然后由于发送的数据长度每次不同,my_data数组可能就因此没有占满,此处对校验和这个数据的放置位置有个处理细节,将其放在了my_data数据后面,下面有解释。
QString转char[ ]
char data[64];
QString str="12fff";
QByteArray ba=str.toLatin1();
char *temp=ba.data();
memcpy(data,temp,ba.length());
sendData st_senddata;
QByteArray get_data, sendTcpData;
char *temp;
QString str;
//senddata.header.resize(2);
st_senddata.header[0] = 0x55; //假设帧头就是0X55 0XAA
st_senddata.header[1] = 0xAA;
str = ui->textEdit_Send->toPlainText().toLocal8Bit();
//数据超长提醒
if(str.length() > 64)
{
QMessageBox::information(this,tr("提示"),tr("数据长度限制为64!"),QMessageBox::Yes);
ui->textEdit_Send->clear();
return;
}
//填好数据
get_data=(QByteArray)ui->textEdit_Send->toPlainText().toLocal8Bit(); //直接获取用户输入的同时将QString转成QByteArray
temp=get_data.data(); //将QByteArray转成char*
memcpy(st_senddata.my_data,temp,get_data.length()); //不拷贝内存传结构体时就只会传一个指针过去
//填好数据长度
st_senddata.dataLength = get_data.length();
//填好校验和,就是my_data长度+header两字节+datalength一字节
st_senddata.check_sum = get_data.length() + 3;
//使用字节数组,将结构体转为字符数组,发送的是字符数组(数据在传输过程中都是byte类型的)
//直接sizeof(senddata)内存会变小,设置了对齐方式解决,,,只给他赋予数据长度加帧头、校验和所占字节
sendTcpData.resize((get_data.length()+3));
//将封装好的结构体转为QByteArray数组,因为传输都是Byte类型
memcpy(sendTcpData.data(),&st_senddata,(get_data.length()+3));
/*因为数据长度可能没有占满64字节,校验和又是存在数据之后的,所以有一段内存可能是空的,因此此处手动
把校验和值添加在QByteArray数组最后,这样发送出去的数据就是连续的*/
sendTcpData.append(st_senddata.check_sum);
//发送完整的QByteArray数组,包含所有结构体信息
socket->write(sendTcpData);
ui->textEdit_Recv->insertPlainText("send:"+str+"\n");
socket->flush(); //释放socket缓存
ui->textEdit_Send->clear(); //发送出去后将发送文本清空
//释放指针、清空QByteArray数组
free(temp);
temp = NULL;
get_data.clear();
get_data.squeeze();
sendTcpData.clear();
sendTcpData.squeeze();
服务端接收:
总体思路:与发送端类似,此时收到的是QByteArray数组,需要将它再转成与发送方同样的结构体。先定义一个结构体指针,将收到的QByteArray数组强转为结构体,再利用结构体指针读取里面的值,存在新的结构体变量里,方便值的读取,最后再把结构体里的my_data数据显示在接收方的文本里。校验和的读取下面也有说明。
接收方的结构体:
#define DATA_LEN 64
#pragma pack(1)
//接收数据的格式
typedef struct receiveData
{
uchar header[2]; //帧头
uchar dataLength; //数据个数(1字节),小于64
char my_data[DATA_LEN]; //数据(小于64字节)
uchar check_sum; //校验和(1字节) 前面所有字节累加和
}st_receivedata;
#pragma pack()
receiveData st_receiveTcpData, *get_Data;
QByteArray buffer;
QString str;
//读取缓冲区数据
buffer = socket->readAll();
if(!buffer.isEmpty())
{
memset(&st_receiveTcpData,0,sizeof (st_receiveTcpData));
get_Data = (receiveData*)buffer.data(); //强转为结构体,需要用结构体指针接收
//读取帧头
for(int i = 0; i < sizeof (st_receiveTcpData.header); i++)
{
st_receiveTcpData.header[i] = get_Data->header[i];
}
//读取数据长度
st_receiveTcpData.dataLength = get_Data->dataLength;
//读取数据
for(int i = 0; i < buffer.length() - 4; i++)//buffer的长度减去header、datalength、check_sum的长度就是数据的长度
{
st_receiveTcpData.my_data[i] = get_Data->my_data[i];
}
//读取校验和,因为发送的时候避免my_data有空内容,所以把校验和放在了my_data后面,所以此处只
//需要读取my_data后面的值就是校验和了。
get_Data->check_sum = get_Data->my_data[buffer.length()-4];
st_receiveTcpData.check_sum = get_Data->check_sum;
//将my_data数据转为QString
str = QString(QLatin1String(st_receiveTcpData.my_data));
//将my_data在文本框显示
ui->textEdit_Recv->insertPlainText("receive:"+str+"\n");
//释放内存
free(get_Data);
buffer.clear();
buffer.squeeze();
}
总的来说,思路很简单,但是坑也很多,尤其是数据转换问题,稍不注意就出错。