MFC文件传输【原创】

来源:互联网 发布:java不允许多继承 编辑:程序博客网 时间:2024/06/08 02:12

终于把该死的文件传输弄出来了,一开始传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;
}

原创粉丝点击