Qt 利用TCP/IP socket通信 发送与接收结构体(简单通信协议解析)

简述:

在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();
    }

总的来说,思路很简单,但是坑也很多,尤其是数据转换问题,稍不注意就出错。

注:

QT版本 5.9.9

编译器:MinGW 32bit

  • 22
    点赞
  • 208
    收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:岁月 设计师:pinMode 返回首页
评论 4

打赏作者

每木昔月

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值