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
  • 31
    点赞
  • 297
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
要经过TCP/IP协议栈发送802.1Q报文,需要在数据包中添加802.1Q VLAN标签,并将其发送到网络接口。以下是一个简单的示例程序,演示如何使用TCP/IP协议栈发送带有802.1Q VLAN标签的数据包: ```c++ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <arpa/inet.h> #include <net/ethernet.h> #include <net/if.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/tcp.h> #include <netinet/udp.h> #include <netinet/ip_icmp.h> #include <linux/if_packet.h> #include <linux/if_ether.h> #include <linux/if_vlan.h> int main() { int sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (sock < 0) { perror("socket"); exit(1); } char ifname[IFNAMSIZ] = "eth0"; struct ifreq ifr; strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) { perror("ioctl"); exit(1); } struct sockaddr_ll addr; memset(&addr, 0, sizeof(addr)); addr.sll_family = AF_PACKET; addr.sll_ifindex = ifr.ifr_ifindex; // 构造802.1Q VLAN标签 struct vlan_tag { uint16_t tci; uint16_t ether_type; } __attribute__((packed)); struct vlan_tag vlan; vlan.tci = htons(0x100); vlan.ether_type = htons(ETH_P_IP); // 构造IP数据包 char ip_packet[ETH_FRAME_LEN]; struct iphdr *iph = (struct iphdr *)ip_packet; iph->version = 4; iph->ihl = 5; iph->tos = 0; iph->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr)); iph->id = htons(0); iph->frag_off = htons(0); iph->ttl = 64; iph->protocol = IPPROTO_UDP; iph->check = htons(0); iph->saddr = inet_addr("192.168.1.100"); iph->daddr = inet_addr("192.168.1.1"); // 构造UDP数据包 char udp_packet[ETH_FRAME_LEN]; struct udphdr *udph = (struct udphdr *)udp_packet; udph->source = htons(1234); udph->dest = htons(5678); udph->len = htons(sizeof(struct udphdr)); udph->check = htons(0); // 将IP头和UDP数据拷贝到数据包中 memcpy(ip_packet + sizeof(struct iphdr), udph, sizeof(struct udphdr)); // 将802.1Q VLAN标签和IP/UDP数据拷贝到数据包中 char packet[ETH_FRAME_LEN]; memcpy(packet, "\xff\xff\xff\xff\xff\xff", ETH_ALEN); memcpy(packet + ETH_ALEN, "\x00\x11\x22\x33\x44\x55", ETH_ALEN); memcpy(packet + 2 * ETH_ALEN, &vlan, sizeof(struct vlan_tag)); memcpy(packet + 2 * ETH_ALEN + sizeof(struct vlan_tag), ip_packet, sizeof(struct iphdr) + sizeof(struct udphdr)); // 发送数据包 if (sendto(sock, packet, 2 * ETH_ALEN + sizeof(struct vlan_tag) + sizeof(struct iphdr) + sizeof(struct udphdr), 0, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("sendto"); exit(1); } close(sock); return 0; } ``` 该程序使用socket API创建了一个PF_PACKET类型的socket,并将其绑定到网络接口上。然后,程序构造了一个带有802.1Q VLAN标签的IP/UDP数据包,并将其封装成一个以太网帧。在构造802.1Q VLAN标签时,程序使用了vlan_tag结构体来定义标签格式,并将其添加到数据包的头部。最后,程序调用sendto函数将数据包发送到目标MAC地址。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值