关于同训协议的设计,主要分为三个部分:弹性结构体、通讯协议设计、数据收发测试
目录
B、假如在定义的结构体里面加上int d[],是一个没有大小的整型数组
B、我们申请的空间并不是这个结构体,但是将这块控件的首地址强制转换成了PDU,来给到PDU的指针 *pdu
D、也即是产生一块空间,并不是产生这样一个结构体,而是借助结构体来访问空间的相应位置,malloc只是申请了一块没有任何类型的空间,可以转换成任意结构体来用(student\teacher...)
E、如果不是空,我们可以看到write的数据是char*类型的,然后发送数据必须封装到协议数据单元里面,在发送出去。所以在tcpclient.h文件里面将协议包含进来
I、那么这个时候消息就封装好啦,封装到pdu里面去了。那么我们把他发送出去
D、当socket有数据过来了,发出readyread信号,那么我们就用槽函数来进行接收
E、定义槽函数recvMsg(),打印当前可读的数据大小,bytesAvailable();获得数据大小
J、(char*)pdu +sizeof (uint)从这儿开始放,放uiPDULen-sizeof (uint)些数据
B、产生一个socket,然后把socketDescriptor描述符放在socket里面,就可以进行收发数据
1、弹性结构体
也就是说将结构体最后一个成员设计为空的,int caData[ ]
一般定义的结构体都是有固定的大小的,但是弹性结构体是没有固定大小的,它的大小是变化的,实际的原理就是借助一个弹性数组来实现的
1.1、我们创建一个纯C或者纯C++的项目
A、点击文件——新建项目
B、写上名称,我们叫他structTest
C、根据自己的情况选择,我们选择64位的
D、创建项目如下
1.2、定义一个结构体
typedef struct PDU
{
int a;
int b;
int c;
}PDU;
A、打印他的大小
int main()
{
//将他的数据打印出来
printf("%ld\n", sizeof (PDU));
printf("Hello World!\n");
return 0;
}
它得到的大小就是三个整型的大小,是12(3X4=12)
B、假如在定义的结构体里面加上int d[],是一个没有大小的整型数组
int d[];
运行得到结果还是12,实际上并没有占据空间,因为我们并没有对他进行分配空间
1.3、进行弹性空间分配
那没有对他分配空间,那我们如何通过它实现弹性空间分配呢?
A、我们使用malloc申请一块没有任何类型的数据
PDU *pdu = (PDU*) malloc(sizeof (PDU)+100*sizeof(int));
B、我们申请的空间并不是这个结构体,但是将这块控件的首地址强制转换成了PDU,来给到PDU的指针 *pdu
C、那么就可以借助pdu指针来访问空间
D、也即是产生一块空间,并不是产生这样一个结构体,而是借助结构体来访问空间的相应位置,malloc只是申请了一块没有任何类型的空间,可以转换成任意结构体来用(student\teacher...)
E、注意:
也就是说printf("%ld\n", sizeof (PDU));运行这句代码的时候,实际上只是abc的大小,那么在malloc的时候加了abc的大,又加了100个int,也即是说这个空间有103个int大小,那么我们将这块空间的地址转换成PDU类型的,借助PDU里面的数据成员的类型,访问到这块空间的相应位置。
它的弹性就在一后面的100可以修改为任意数字
int main()
{
printf("%ld\n", sizeof (PDU));
PDU*pdu = (PDU*) malloc(sizeof (PDU)+100*sizeof(int));
printf("Hello World!\n");
return 0;
}
F、对指针变量进行赋值
pdu->a=90;
pdu->b =89;
pdu->c=88;
G、int d[ ],需要将将数据拷贝进去
//将数据拷贝进去
memcpy(pdu->d,"you jump i jump", 16) ;
H、将数据打印出来
//第四个数组是将字符串打印出来
printf("a=%d ,b=%d, c=%d, %s\n", pdu->a, pdu->b, pdu->c,(char*)(pdu->d));
I、释放空间
//释放空间
free(pdu);
pdu=NULL;
2、通讯协议的设计
关于通讯协议的设计,我们发送的数据,应该包含四个内容:总的消息大小、消息类型、实际消息大小、实际消息
总的数据大小:uiPDULen (protocol data unity协议数据单元),目的就是在发送的时候数据量不一样的时候,接收数据的时候不能像固定大小那样去接收,首先得知道有多少数据过来了,然后我要申请一个多大的空间去接收它。
消息类型:uiMsgType,我收到你的消息过来的时候,那我要知道你这个数据是干什么的,是登陆还是注册等等
实际消息大小:uiMsgLen,也即是除了前面3个字段外(也就是1里面的abc),其他的数据(也即是you jump i jump).,其实也可以不写,因为我们可以根据总的数据大小减去前面三个字节的大小,就得到了实际消息大小。当然我们也可以加上其他的,上传下载文件,加一个caFileName,可以写成一个数组形式caFData[32](登录注册的时候可以用)
实际消息: aMsg[],这里并不是说将实际消息放在这个数组里面,而是动态去访问这个数据
2.1、使用代码展示通讯协议
A、点击add new新建一个C++的类
B、写协议
#ifndef PROTOCAL_H
#define PROTOCAL_H
typedef unsigned int unit;
struct PDU{
unit uiPDULen; //总的协议数据单元大小
unit uiMsgType; //消息类型,数据是干嘛的
char caData[64]; //文件名
unit uiMsgLen; //实际消息长度
int caMsg[]; //实际消息
};
#endif // PROTOCAL_H
C、协议操作函数,创建一个.cpp文件
D、定义一些函数,存储一个实际消息的长度
PDU *mkPDU(unit uiMsgLen);
E、计算实际长度
首先引入头文件
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
F、在cpp里面定义,协议动态控件申请一下
PDU *mkPDU(unit uiMsgLen)
{
/*
sizeof(PDU)只会计算 以下的大小
unit uiPDULen; //总的协议数据单元大小
unit uiMsgType; //消息类型,数据是干嘛的
char caData[64]; //文件名
unit uiMsgLen; //实际消息长度
*/
//所以总的数据大小就是前面的,加上实际大小
unit uiPDULen = sizeof(PDU) +uiMsgLen;
PDU *pdu = (PDU*) malloc(uiPDULen);
//清空一下
if(NULL == pdu){
//结束程序
exit(EXIT_FAILURE);
}
memset(pdu, 0,uiPDULen);
//重置
pdu->uiPDULen = uiPDULen;
pdu->uiMsgLen = uiMsgLen;
return pdu;
}
2.2、收发数据
在客户端做一个简单的测试
A、拖入下图的控件进行测试
B、右键发送控件,转到槽
C、首先获得输入框的信息
QString strMsg = ui->lineEdit->text();
D、判断是否为空,空的话就没有必要发送了
if(!strMsg.isEmpty())
{
}
else
{
QMessageBox::warning(this, "信息发送", "发送的信息不能为空");
}
E、如果不是空,我们可以看到write的数据是char*类型的,然后发送数据必须封装到协议数据单元里面,在发送出去。所以在tcpclient.h文件里面将协议包含进来
//将协议包含进来
#include "protocal.h"
F、在.cpp文件里面产生一个PDU,这样就把空间申请了
strMsg.size()+1加一个\0
PDU *pdu = mkPDU(strMsg.size()+1);
G、/拷贝数据 先换成C++的字符串,再获得数据的首地址
memcpy(pdu->caMsg, strMsg.toStdString().c_str(),strMsg.size());
H、临时给一个消息类型,做测试
pdu->uiMsgType=8888;
I、那么这个时候消息就封装好啦,封装到pdu里面去了。那么我们把他发送出去
m_tcpSocket.write((char*)pdu, pdu->uiPDULen);
J、发送完之后,将空间free掉,释放空间
//释放空间
free (pdu);
pdu =NULL;
K、总的代码
void TcpClient::on_sendBt_clicked()
{
//首先获得输入框的信息
QString strMsg = ui->lineEdit->text();
if(!strMsg.isEmpty()){
//直接发送出去
//协议
//strMsg.size()+1加一个\0
PDU *pdu = mkPDU(strMsg.size()+1);
//消息类型
pdu->uiMsgType=8888;
//拷贝数据 先换成C++的字符串,再获得数据的首地址
memcpy(pdu->caMsg, strMsg.toStdString().c_str(),strMsg.size());
m_tcpSocket.write((char*)pdu, pdu->uiPDULen);
//释放空间
free (pdu);
pdu =NULL;
}
else {
QMessageBox::warning(this, "信息发送", "发送的信息不能为空");
}
}
2.3、服务器接收数据
在这里,我们自己封装一个TcpSocket,假如直接new一个TcpSocket的话,保存socketDescriptor描述符的话,到时候发送数据过来的话,就不知道是谁发的
A、在TcpServer这里,添加新文件类
B、将头文件包含进来,并加上Q_OBJECT
#include "protocal.h"
C、定义一个槽函数
private slots:
//定义槽函数
void recvMsg();
D、当socket有数据过来了,发出readyread信号,那么我们就用槽函数来进行接收
MyTcpSocket::MyTcpSocket()
{
connect(this, SIGNAL(readyRead()), this, SLOT(recvMsg()));
}
E、定义槽函数recvMsg(),打印当前可读的数据大小,bytesAvailable();获得数据大小
qDebug()<<this->bytesAvailable();
但是这个容易发生混乱,比如有两个数据一起过来了,直接读两个数据没有分开读的话,就会发生数据混乱,所以我们读数据的时候,先读取PDU的大小,申请空间,接着再读取剩余的数据。所以将前面的protocol.h和.cpp文件都放在里面
F、收数据
uint uiPDULen =0;
G、先收4个字节大小的数据
this->read((char*)&uiPDULen, sizeof (uint));
H、根据总的大小,计算实际消息长度
uint uiMsgLen = uiPDULen-sizeof (PDU);
I、根据实际消息长度,产生一个pdu,接收剩余的数据
PDU *pdu = mkPDU(uiMsgLen);
J、(char*)pdu +sizeof (uint)从这儿开始放,放uiPDULen-sizeof (uint)些数据
this->read((char*)pdu +sizeof (uint),uiPDULen-sizeof (uint));
K、打印一下
qDebug()<<pdu->uiMsgType<<(char*)pdu->caMsg;
2.3、与客户端连接
Tcpserver监听之后,一旦有客户端过来,他就会自动去调用incomingConnection。
A、定义一个链表,再放到链表里面取
在mytcoserver.h里面添加头文件
#include <QList>
#include "mytcpsocket.h"
private:
QList <MyTcpSocket*> m_tcpSocketList;
B、产生一个socket,然后把socketDescriptor描述符放在socket里面,就可以进行收发数据
MyTcpSocket *pTcpSocket = new MyTcpSocket;
pTcpSocket->setSocketDescriptor(socketDescriptor);
C、再放到链表里面取
m_tcpSocketList.append(pTcpSocket);
2.4运行测试
A、先将服务器运行起来
B、再运行客户端
C、客户端连接成功
D、给服务器发送一个数据
接收成功