UDP文件传输
一、开发软件:
QT4.7.0(32bit)
二、UDP协议的几个特性:
1.UDP是一个无连接协议,传输数据之前原断和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。
2.由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务机可同时向多个客户机传输相同的消息。
3.UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包的额外开销很小。
4.吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。
5.UDP使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态表(这里面有许多参数)。
6.UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。
三、UDP文件传输的重难点:
使用UdpSocket进行文件传输时,重难点就是UDP只负责发送一个一个的数据包,但是接收时UDP它不负责检查数据包的大小、内容是否丢失以及是不是你要接受的数据包,也不会把数据包按照原来的顺序进行排序,因此在发送数据包的时候必须人为的给每个数据包添加一个验证码(可以看成是自定义的一个端口号)和一个数据包号,在接受的时候要通过判断验证码是否于发送的想吻合,来确定这是否是你要接受的数据,简单的对数据包进行过滤。
四、UDP文件传输的详细步骤:
1.(客户端)用FILE指针以“只读“的方式打开一个本地文件,作为发送的数据源,通过文件游标来获取文件的大小,并设置数据包号的初值为1,通过QUdpSocket指针来邦定服务器回馈信息的端口号(9001)具体代码如下:
fp = fopen("/home/akaedu/desktop/C语言.pdf","r");
if(fp == NULL)
{
qDebug() << "打开文件失败!";
exit(-1);
}
fseek(fp,0,SEEK_END);
file_size = ftell(fp);
fseek(fp,0,SEEK_SET);
qDebug() << "文件大小为:" <<file_size;
datanumber = 1;
m_udpclient = new QUdpSocket;
this->m_udpclient->bind(9001);
connect(ui->pB_sendfile,SIGNAL(clicked()),this,SLOT(sendfile()));
connect(this->m_udpclient,SIGNAL(readyRead()),this,SLOT(answerserver()))
2.(客户端)通过一个按钮点击来触发一个发送文件的槽函数,这个槽函数完成发送文件(sendfile()),并且在每个数据包前面加上验证码(4个字节)+数据包号(4个字节)。读取文件时判断文件是否读完了,如果读完了改变验证码数字,只发送验证码+数据包号,如果没有读完就把原来的验证码+数据包号+文件中的内容发送给服务器,文件内容是从文件中通过fread读取的,然后用数组存储起来的,再放在QByteArray类型的数据中,其中部分代码是进度条显示发送进度的,具体代码如下:
QByteArray send_data;
int len;
char arr[4088];
send_data.resize(4096);
int *ptr = (int *)send_data.data();
*ptr = 10101;
*(ptr+1) = datanumber;
len = fread(arr,1,4088,fp);//把读到的数据放到arr数组中,每次读一个字节,读4088次,并将读到的字节数赋给len,len是小于等于4088的数字
qDebug() << "len =" << len;
qDebug() << "arr =" << arr;
int i = file_size/4088;
qDebug() << "数据包的个数:" << i;
int j = file_size%4088;
int k = i/100;
int static l = 1;
if(len == 0 && file_size%4096!=0)
{
qDebug()<<"文件已经读完了!";
fclose(fp);
*ptr = 10102;
this->m_udpclient->writeDatagram(send_data,len+8,QHostAddress("127.0.0.1"),9000);
ui->pB_sendfile->setEnabled(false);
}
else
{ qDebug()<<"文件读取中!";
memmove((char *)(ptr+2),arr,4088);//把arr数组中的值作为数据源,传给send_data.data()中,从第9个字节开始,传4088个字节
qDebug()<<"arr ="<<arr;
qDebug()<<"*ptr ="<<*ptr;
this->m_udpclient->writeDatagram(send_data,len+8,QHostAddress("127.0.0.1"),9000);
qDebug() << "发送的数据包数" << datanumber;
if(datanumber >= l*k)
{
ui->progressBar->setValue(l);
l++;
}
else if(datanumber < l*k)
{
ui->progressBar->setValue(l);
}
}
3.(服务器)用FILE指针以”写追加”的方式打开一个文件,这个文件必须于客户端的源文件的文件类型相同,创建一个QudpSocket类型的指针邦定客户端发送数据包的端口(即9000),并通过这个指针来接受一个readyRead()信号来触发接受文件这个槽函数(recievefile()),具体代码如下:
fp = fopen("/home/akaedu/desktop/li.pdf","w+");
if(fp == NULL)
{
qDebug() << "新文件创建失败!";
exit(-1);
}
this->m_udpserver = new QUdpSocket;
this->m_udpserver->bind(9000);
connect(this->m_udpserver,SIGNAL(readyRead()),this,SLOT(recievefile()));
4.接受文件这个槽函数具体功能是通过监听这个端口判断是否有数据包发送过来,如果有数据包,那就读取这个数据包,并且通过指向这个数据包的int指针获取其中的验证码,通过验证码来判断客户端的数据报是否发送完成,如果正在发送过程中,就获取其中的数据包号+数据包中的内容(存储在数组中),再通过数组中的内容fwrite到服务器打开的文件中去并回馈给客户端一个验证码+数据包号,告诉客户端接受到数据包,如果发送完成关闭服务器以打开的文件,具体代码如下:
QByteArray msg;
QByteArray data;
int datanumber;
char arr[4088];
while(this->m_udpserver->hasPendingDatagrams())
{
msg.resize(this->m_udpserver->pendingDatagramSize());
this->m_udpserver->readDatagram(msg.data(),msg.size());
int *ptr = (int *)msg.data();
if(*ptr == 10101)
{
qDebug() << "接受到客户端的数据包!";
datanumber = *(ptr+1);
memmove(arr,(char *)(ptr+2),4088);
qDebug() << "接受到的数据包数目" << datanumber/*<<"数据" <<arr*/;
fwrite(arr,1,msg.size()-8,fp);
data.resize(8);
int *tmp = (int *)data.data();
*tmp = 11111;
*(tmp+1) = datanumber;
this->m_udpserver->writeDatagram(data.data(),8,QHostAddress("127.0.0.1"),9001);
}
if(*ptr == 10102)
{
fclose(fp);
}
5.(客户端)通过监听readyRead()这个信号来触发接受服务器回馈信息的函数(answerserver())。通过QudpSocket指针来判断是否有数据包,如果有数据包,再用指向数据包的int指针来获取数据包中的验证码+数据包号,用if判断这是不是服务器回馈过来的数据包,如果是,那再判断获取得到的数据包号是否等于发送的数据包号,是的话,把数据包号+1,继续发送文件,不是的话说明数据包丢失重发文件,如果不是,说明数据包丢失,重发文件,具体代码如下:
qDebug() << "客户端响应服务器!";
QByteArray msg;
while(this->m_udpclient->hasPendingDatagrams())
{
msg.resize(this->m_udpclient->pendingDatagramSize());
this->m_udpclient->readDatagram(msg.data(),msg.size());
int *tmp = (int *)msg.data();
if(*tmp == 11111)
{
if(*(tmp+1) == datanumber)
{
datanumber++;
this->sendfile();
}
else
{
this->sendfile();
}
}
else
{
this->sendfile();
}
}