您的位置:首页 > 其它

MFC文件传输【原创】

2013-04-01 14:37 267 查看
终于把该死的文件传输弄出来了,一开始传char,然后传byte,一开始只是对文件分包传,最后还是得定义包结构,折腾啊……

struct FILEMESSAGE

{

 char fileHead[4];

 int fileStart;

 int fileSize;

 BYTE filePacket[MAX_FILE_PACKET - 4 - 2*sizeof(int)];

};

这是包的结构体,fileHead起一个标志的作用,fileStart是包中数据在整个文件中的起始位置标志,fileSize是包中数据的长度,filePacket是包中所传输的文件数据,整个结构固定大小为MAX_FILE_PACKET,传输的时候也可以用这个常量来定义一次接收的大小,filePacket[MAX_FILE_PACKET - 4 - 2*sizeof(int)]中4表示fileHead是4位,2*sizeof(int)是因为各个平台对int的长度定义可能不同,所以没有直接用MAX_FILE_PACKET
- 4*3。

struct FILEINFO

{

 int filePacketNum;

 int filePacketIndex;

 int nFilePacketBuff;

 int fileStart;

 int fileSize;

 int lastStart;

 int lastSize;

 long fileLength;

};

这是一个在文件传输过程中要用到的结构体, filePacketNum记录了这个文件会被分为几个包,从而可以通过获得的包数来判断文件是否完成了传输;filePacketIndex记录了当前传输的包是第几个包;nFilePacketBuff是包中实际用于传输文件数据的大小,此处我定义为MAX_FILE_PACKET - 4 - 2*sizeof(int);fileStart、fileSize保存从包中读取的数字,lastStart、lastSize是当一个包被完整保存到文件后记录对应的信息的,通过判断fileStart和lastStart可以知道当前接收到的包是否是想要的包,而fileSize还可以有一个作用就是设置Receive函数要读取的长度。

进行文件传输之前应该首先传输文件的大小:

client端首先读取文件:在界面上定义一个edit控件IDC_EDIT_FILE,用打开对话框打开准备进行传输的文件;m_length是一个全局变量,专门用来存放文件的长度;m_tempBuff是一个全局byte指针,专门用来存放要进行传输的文件。

m_length = 0;

 CString FilePathName;

 GetDlgItemText(IDC_EDIT_FILE,FilePathName);

 while(true)

 {

  if(!strcmp(FilePathName,""))//判断edit控件是否为空,如果为空则调用标准打开对话框

  {

   CFileDialog dlg(TRUE);///TRUE为OPEN对话框,FALSE为SAVE AS对话框

   if(dlg.DoModal()==IDOK)

   FilePathName=dlg.GetPathName();

   SetDlgItemText(IDC_EDIT5,FilePathName);

   break;

  }

  else

  {

   CFileFind tempFind;    //如果edit控件不为空,则判断内容是否是一个可打开的文件,如果不是就置控件内容为空

   CString strFileName;

   BOOL bIsFinded;

   bIsFinded = (BOOL)tempFind.FindFile(FilePathName);   

   if(!bIsFinded)   

   {

    MessageBox("目录不存在!","提示");

    SetDlgItemText(IDC_EDIT5,"");

   }

   else

    break;

  }

 }

 FILE *pFile;

 if(NULL == (pFile=fopen(FilePathName,"rb")))               //打开文件

 {

  MessageBox("无法打开文件!","提示");

  return false;

 }

 fseek(pFile,0,SEEK_END);     //将文件指针从文件头移动到文件尾,则指针移动的长度就是文件的长度

 m_length = ftell(pFile);

 char szDrive[_MAX_DRIVE],szDir[_MAX_DIR],szFname[_MAX_FNAME],szExt[_MAX_EXT];

 _splitpath(FilePathName,szDrive,szDir,szFname,szExt);

 m_tempBuff = new BYTE[m_length+1];   //申请指针空间

 if(NULL == m_tempBuff)

 {

  return false;

 }

 fseek(pFile,0,SEEK_SET);

 fread(m_tempBuff,sizeof(BYTE),m_length,pFile);    //将文件读入m_tempBuff

 m_tempBuff[m_length] = '\0';

 fclose(pFile);

 return true;

通过一个send函数将m_length传给server,至于如何传输如何接收如何判断则见仁见智了。

server接收到m_length后首先为要传输的文件申请一个保存空间:

首先要声明一个FILEINFO结构体全局变量m_fileInfo用来保存在传输过程中要用到的相关信息,我试过将这些信息分开声明使用,但是因为在传输过程中使用到指针和反复使用的缓冲变量,有的时候莫名其妙的这些信息就被改了,我怀疑和内存有关,因为可能会反复申请内存空间,这些信息在传输过程中又经常被改动,所以为了保险起见就专门定义了一个结构体用来保存这些信息。

接着要定义一个全局byte指针m_fileBuffer,用来存放传输过来的文件数据。

 void CMySocket::CreateFileBuff(long length)     

 //得到文件大小后调用这个函数,申请一个文件空间,并初始化m_fileInfo

{

 if(m_fileBuffer != NULL)

 {

  delete[] m_fileBuffer;

  m_fileBuffer = NULL;

 }

 m_fileInfo.fileLength = length;       //在变量m_fileInfo中保存文件长度信息

 if(m_fileInfo.fileLength != 0)         //申请文件保存空间

 {

  if(m_fileInfo.fileLength < 10)

  {

   //注:此处申请内存过小时会出现DAMAGE after Normal block错误

   m_fileBuffer = new BYTE[16];

  }

  else

  {

   m_fileBuffer = new BYTE[m_fileInfo.fileLength+1];

  }

  m_fileInfo.filePacketIndex = 0;

  m_fileInfo.fileStart = 0;

  m_fileInfo.lastStart = 0;

  m_fileInfo.lastSize = 0;

  m_fileInfo.nFilePacketBuff = MAX_FILE_PACKET - 4 - 2*sizeof(int);

  m_fileInfo.filePacketNum = m_fileInfo.fileLength/m_fileInfo.nFilePacketBuff + 1;

//考虑到文件分包可能出现只传一个包的情况,所以 m_fileInfo.fileSize在初始化的时候要考虑两种情况

  if(m_fileInfo.filePacketNum == 1)

  {

   m_fileInfo.fileSize  = m_fileInfo.fileLength + 4 + 2*sizeof(int) + 1;

  }

  else

  {

   m_fileInfo.fileSize = MAX_FILE_PACKET+1;   

   //因为我在调试的时候发现有一种情况就是接收的包的首个字节是'\0',后面就正常了,所以在这里初始化的时候专门给 m_fileInfo.fileSize的长度定义为想要接收的长度+1

  }

  memset(m_fileBuffer,0,sizeof(m_fileBuffer));

 }

}

接下来就是client发送文件包,server接收文件包,为了对传输过程中丢包的情况进行处理,我这里的设计是当server正常接收了一个包后,要向client发送一个确认包信息,这个确认包里有fileHead,fileStart,fileSize,然后client根据接收到的确认包里的信息来判断接下来要发送的包信息。

在client端,我重载了OnRecive函数,然它向View发一个消息,并将接收到的数据都用这个消息发送给View

LRESULT CClient::OnMyReceive(WPARAM wParam, LPARAM lParam)  //这是那个消息响应函数

{

CString message = (char*)lParam;

 int i = (int)wParam;            //在我实际的代码中这是一个标志,用来标识消息的类型

char m_fileHead[4];

FILEMESSAGE* fileMsg;

fileMsg = (FILEMESSAGE*)lParam;

m_fileHead[0] = fileMsg->fileHead[0];

m_fileHead[1] = fileMsg->fileHead[1];

m_fileHead[2] = fileMsg->fileHead[2];

m_fileHead[3] = '\0';        

 //这个空字符很重要,我在调试中发现,如果没有这个空字符后面的很多变量很容易被奇怪的数据覆盖,原因则不得而知

int m_fileStart = fileMsg->fileStart;

int m_fileSize = fileMsg->fileSize;

sendFileInfo(m_fileStart,m_fileSize,m_fileHead);

}

第一次发送文件包的时候调用函数sendFileInfo(0,0,“0”);

 m_lastStart ,m_lastSize是private变量,用来记录发送的上一包的信息;m_errorIndex 是传输出错标志,这是一个private变量,当传输出错次数过多的时候就要重新进行传输了;

用不同的fHead表示用户发送的不同的确认信息

sendFileInfo(int fStart,int fSize,CString fHead)

{

int nFilePacketBuff = MAX_FILE_PACKET - 4 - 2*sizeof(int);

 FILEMESSAGE fileMsg;              //定义用于发送文件数据的文件包,fileHead定义为“FFF”

 fileMsg.fileHead[0] = 'F';

 fileMsg.fileHead[1] = 'F';

 fileMsg.fileHead[2] = 'F';

 fileMsg.fileHead[3] = '\0';

 fileMsg.fileStart = 0;

 fileMsg.fileSize = 0;

if(fStart==m_lastStart && fSize==m_lastSize)

 {

if(fStart==0 && fSize==0 && !strcmp(fHead,"END"))     //最后一个包发送完成

{

 CString temp="";

   GetDlgItemText(IDC_EDIT_CLIENT,temp);

   temp += "\r\n";

   temp += "文件传输完毕";

   SetDlgItemText(IDC_EDIT_CLIENT,temp);

}

  else if(fStart==0 && fSize==0 && !strcmp(fHead,"0"))    //发送第一个包

  {

   m_errorIndex = 0;                          //传输出错标志

   if(m_length <= nFilePacketBuff)    //考虑只有一个包的情况

   {

    fileMsg.fileStart = 0;

    fileMsg.fileSize = m_length;

    m_lastStart = 0;

    m_lastSize = m_length;

   }

   else

   {

    fileMsg.fileStart = 0;

    fileMsg.fileSize = nFilePacketBuff;

    m_lastStart = 0;

    m_lastSize = nFilePacketBuff;

   }

  }

  else if(fStart==m_lastStart && fSize==m_lastSize && !strcmp(fHead,"OK!"))//接收到正常确认包的时候

  {

   m_errorIndex = 0;

   if((m_length-fStart-nFilePacketBuff) <= nFilePacketBuff)     //当要发送最后一个包的时候

   {

    fileMsg.fileStart = fStart + nFilePacketBuff;

    fileMsg.fileSize = m_length % nFilePacketBuff;

    m_lastStart = fileMsg.fileStart;

    m_lastSize = fileMsg.fileSize;

   }

   else

   {

    fileMsg.fileStart = fStart + nFilePacketBuff;

    fileMsg.fileSize = nFilePacketBuff;

    m_lastStart = fileMsg.fileStart;

    m_lastSize = fileMsg.fileSize;

   }

  }

  else if(fStart==m_lastStart && fSize==m_lastSize && !strcmp(fHead,"ERR"))  

//确认包表示接收到的包有错误,则重新发送上一个包

  {

   m_errorIndex++;

   fileMsg.fileStart = m_lastStart;

   fileMsg.fileSize = m_lastSize;

  }

  else if(!strcmp(fHead,"CCL"))

//确认包表示用户取消了文件传输

  {

   CString temp="";

   GetDlgItemText(IDC_EDIT_CLIENT,temp);

   temp += "\r\n";

   temp += "对方取消了文件传输";

   SetDlgItemText(IDC_EDIT_CLIENT,temp);

  }

  else   //接收到的信息不正常时默认出错,重新发送上一个包

  {

   m_errorIndex++;

   fileMsg.fileStart = m_lastStart;

   fileMsg.fileSize = m_lastSize;

  }

 }

 else //接收到的信息不正常时默认出错,重新发送上一个包

 {

  m_errorIndex++;

  fileMsg.fileStart = m_lastStart;

  fileMsg.fileSize = m_lastSize;

 }

 if(m_errorIndex > 10)       //这里可以进行出错处理

 {

  fileMsg.fileHead[0] = 'E';

  fileMsg.fileHead[1] = 'R';

  fileMsg.fileHead[2] = 'R';

  fileMsg.fileHead[3] = '\0';

 }

//m_clientSocket是这个类的一个全局变量,继承了MFC封装的socket,其中m_clientSocket->m_tempBuffer是一个缓冲变量byte m_tempBuffer[MAX_FILE_PACKET],m_clientSocket->m_nLength是一个int型变量

 ZeroMemory(m_clientSocket->m_tempBuffer, sizeof(BYTE)*(MAX_FILE_PACKET));

  memcpy(fileMsg.filePacket,m_tempBuff+fileMsg.fileStart,fileMsg.fileSize);

  fileMsg.filePacket[fileMsg.fileSize] = '\0';

  BYTE* filePacket = (BYTE*)&fileMsg;

  memcpy(m_clientSocket->m_tempBuffer,filePacket,fileMsg.fileSize+4+2*sizeof(int));

  m_clientSocket->m_nLength = fileMsg.fileSize+4+2*sizeof(int);

  m_clientSocket->Send(m_clientSocket->m_tempBuffer,m_clientSocket->m_nLength,0);

}

server端的接收工作是在CMySocket类中完成的,class CMySocket : public CAsyncSocket

重载OnReceive函数

void CMySocket::OnReceive(int nErrorCode)

{

 ReceiveFilePacket(FILE_PACKET_OK);

 CAsyncSocket::OnReceive(nErrorCode);

}

定义下列函数:

void ReceiveFilePacket(int nErrorCode)     //从Socket的接收缓冲区中读取数据

void SendFilePacketEcho(int nFlag);          //向Client发送文件响应信息

 bool StoreFilePacket();                              //保存接收到的文件包

 bool IsFilePacket();                                    //判断并保证文件包完整

FILE_PACKET_OK表示接收正常,FILE_PACKET_ERROR表示接收异常,FILE_PACKET_CANCEL表示用户取消了文件传输,这是3个全局常量。

在CMySocket类中有两个变量,m_fileTemp是调用Receive函数时用的, m_filePacket是当接收到的数据是一个完整的文件包时用的

BYTE m_fileTemp[MAX_FILE_PACKET+1];  //文件传输接收缓存

BYTE m_filePacket[MAX_FILE_PACKET+1]; //接收文件包缓存

void CMySocket::ReceiveFilePacket(int nErrorCode)

{

 if(nErrorCode == FILE_PACKET_OK)

 {

  Sleep(1);      //很奇怪,如果不调用Sleep,这里会出现很多奇怪的错误,原因未知

  ZeroMemory(m_fileTemp, sizeof(BYTE)*(MAX_FILE_PACKET+1));

  if(m_fileInfo.fileSize > 0)

 //要接收的数据长度不能为0,因为在传输过程中可能出现各种各样的错误,其中有一种就是m_fileInfo.fileSize被修改为了负数,很奇怪的错误,原因未知

  {

   m_recvLen = CAsyncSocket::Receive(m_fileTemp,m_fileInfo.fileSize,0);

  }

  while((m_recvLen==1 && m_fileTemp[0] == '\0')||!IsFilePacket())//有时候接收文件包之前会接收到一个空字符

  {

   if(m_fileInfo.fileSize > 0)

{

    m_recvLen = CAsyncSocket::Receive(m_fileTemp,m_fileInfo.fileSize,0);

//有时候调用一次Receive不能把所有的缓冲区里的数据都读出来,这里处理这种情况

}

   if(m_recvLen <= 0)

   {

    SendFilePacketEcho(FILE_PACKET_ERROR);  //如果接收到的包不正常,就发送一个确认包告诉Client有错误

    break;

   }

  }

  if(m_fileInfo.fileSize == 0)  //调用IsFilePacket()后,如果接收到的包正常,则把m_fileInfo.fileSize置为0

  {

   if(StoreFilePacket())        //接收到的包正常,则保存这个包

   {

    if(m_fileInfo.filePacketIndex<m_fileInfo.filePacketNum && m_fileInfo.filePacketIndex>0)

    {

     SendFilePacketEcho(FILE_PACKET_OK);    //如果接收到的包不是最后一个包则发一个普通确认信息

    }

    else if(m_fileInfo.filePacketIndex == m_fileInfo.filePacketNum)

    {

     SendFilePacketEcho(FILE_PACKET_END);  //如果接收到的包是最后一个包则通知Client

    }

   }

  }

 }

 else if(nErrorCode == FILE_PACKET_ERROR)

 {

  SendFilePacketEcho(FILE_PACKET_ERROR);  //接收到的包不正常,就发送一个确认包告诉Client有错误

 }

 else if(nErrorCode == FILE_PACKET_CANCEL)

 {

  SendFilePacketEcho(FILE_PACKET_CANCEL); //用户取消传输,就发送一个确认包告诉Client

 }

}

bool CMySocket::IsFilePacket()      //m_recvNum是CMySocekt的一个public变量,用来记录接收到的数据大小

{

 if(m_recvLen > 0 && m_recvLen <= MAX_FILE_PACKET+1)

 {

  if(m_fileTemp[0]=='\0'&&m_recvLen == MAX_FILE_PACKET+1)

 //接收到的包长度为MAX_FILE_PACKET+1,首字节为'\0'

  {

   m_recvNum=MAX_FILE_PACKET;

   BYTE m_newPacket[MAX_FILE_PACKET];

   ZeroMemory(m_newPacket, sizeof(BYTE)*(MAX_FILE_PACKET));

   memcpy(m_newPacket,m_fileTemp+1,MAX_FILE_PACKET);

   ZeroMemory(m_filePacket, sizeof(BYTE)*(MAX_FILE_PACKET+1));

   memcpy(m_filePacket,m_newPacket,MAX_FILE_PACKET);  //将除了首字节以外的其它信息存到包缓冲区内

   m_fileInfo.fileSize= 0;

   return true;

  }

  else if(m_recvLen >= MAX_FILE_PACKET)

  {

   m_recvNum=MAX_FILE_PACKET;

   ZeroMemory(m_filePacket, sizeof(BYTE)*(MAX_FILE_PACKET+1));

   memcpy(m_filePacket,m_fileTemp,MAX_FILE_PACKET);   //默认接收到的数据是一个完整的包

   m_fileInfo.fileSize= 0;

   return true;

  }

  else if(m_recvLen <= m_fileInfo.fileSize)   //当接收到的数据不满足一个包的时候

  {

   m_recvNum += m_recvLen;

   if(m_fileInfo.fileSize - m_recvLen >= 0)

   {

    m_fileInfo.fileSize -= m_recvLen;

   }

if(m_recvNum > MAX_FILE_PACKET)

   {

    m_recvNum = 0;

    m_recvNum += m_recvLen;

   }

   else if(m_recvNum<MAX_FILE_PACKET && m_recvNum>=0)

   {

    memcpy(m_filePacket+m_recvNum-m_recvLen,m_fileTemp,m_recvLen); //将接收的数据按顺序存入_filePacket
   }

   if(m_recvNum==MAX_FILE_PACKET || m_fileInfo.fileSize == 0)

   {

    memcpy(m_filePacket+m_recvNum-m_recvLen,m_fileTemp,m_recvLen);//将接收的数据按顺序存入_filePacket

    return true;

   }

   

   m_recvLen = 0;

  }

 }

 return false;

}

bool CMySocket::StoreFilePacket()

{

 FILEMESSAGE* fileMsg;

 fileMsg = (FILEMESSAGE*)m_filePacket;

 m_fileHead[0] = fileMsg->fileHead[0];

 m_fileHead[1] = fileMsg->fileHead[1];

 m_fileHead[2] = fileMsg->fileHead[2];

 m_fileHead[3] = '\0';

 if(!strcmp(m_fileHead,"FFF")||!strcmp(m_fileHead,"ERR"))

 {

  m_fileInfo.fileStart = fileMsg->fileStart;

  m_fileInfo.fileSize = fileMsg->fileSize;

  if(m_fileInfo.fileStart>=0 && m_fileInfo.fileStart<m_fileInfo.fileLength && m_fileInfo.fileSize>0 && m_fileInfo.fileSize<MAX_FILE_PACKET)

  {

   m_fileInfo.filePacketIndex++;

   if(m_fileInfo.fileStart==m_fileInfo.lastStart && m_fileInfo.filePacketIndex!=1)

   {

    m_fileInfo.fileSize += 0;

   }

   memcpy(m_fileBuffer+m_fileInfo.fileStart,m_filePacket+4+2*sizeof(int),m_fileInfo.fileSize);    
   //将接收到的文件数据存入文件空间 

   m_fileInfo.lastStart = m_fileInfo.fileStart;

   m_fileInfo.lastSize = m_fileInfo.fileSize;  

   return true;

  }

 }

 return false;

}

void CMySocket::SendFilePacketEcho(int nFlag)    //向Client发送确认包,m_fileMsgSend是CMySocket的变量

{

 if(nFlag == FILE_PACKET_OK)

 {

  m_fileMsgSend.fileHead[0] = 'O';

  m_fileMsgSend.fileHead[1] = 'K';

  m_fileMsgSend.fileHead[2] = '!';

  m_fileMsgSend.fileHead[3] = '\0';

 }

 else if(nFlag == FILE_PACKET_ERROR)

 {

  m_fileMsgSend.fileHead[0] = 'E';

  m_fileMsgSend.fileHead[1] = 'R';

  m_fileMsgSend.fileHead[2] = 'R';

  m_fileMsgSend.fileHead[3] = '\0';

 }

 else if(nFlag == FILE_PACKET_END)

 {

  m_fileMsgSend.fileHead[0] = 'E';

  m_fileMsgSend.fileHead[1] = 'N';

  m_fileMsgSend.fileHead[2] = 'D';

  m_fileMsgSend.fileHead[3] = '\0';

//这时全局byte指针m_fileBuffer中存放了全部的文件信息,进行一些保存工作即可

 }

 else if(nFlag == FILE_PACKET_CANCEL)

 {

  m_fileMsgSend.fileHead[0] = 'C';

  m_fileMsgSend.fileHead[1] = 'C';

  m_fileMsgSend.fileHead[2] = 'L';

  m_fileMsgSend.fileHead[3] = '\0';

 }

 if(m_fileInfo.lastSize>m_fileInfo.nFilePacketBuff || m_fileInfo.lastSize<0 || m_fileInfo.lastStart>m_fileInfo.fileLength || m_fileInfo.lastStart<0 || m_fileInfo.filePacketIndex>m_fileInfo.filePacketNum || m_fileInfo.filePacketIndex<0)

 {

  return;

 }

 m_fileMsgSend.fileStart = m_fileInfo.lastStart; 

 m_fileMsgSend.fileSize = m_fileInfo.lastSize;

 BYTE* newPacket = (BYTE*)&m_fileMsgSend;

 memcpy(m_fileTemp,newPacket,4+2*sizeof(int));

 CAsyncSocket::Send(m_fileTemp,4+2*sizeof(int));

 if(m_fileInfo.filePacketIndex == m_fileInfo.filePacketNum-1) //定义下一次接收数据的大小

 {

  m_fileInfo.fileSize = m_fileInfo.fileLength%m_fileInfo.nFilePacketBuff + 4 + 2*sizeof(int);

 }

 else

 {

  m_fileInfo.fileSize = MAX_FILE_PACKET;

 }

 m_recvNum = 0;

 m_recvLen = 0;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  MFC 文件传输