zoukankan      html  css  js  c++  java
  • MFC文件传输【原创】

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

  • 相关阅读:
    C#中的取整函数
    java代码(8) ---guava字符串工具
    java代码(7) ---guava之Bimap
    java代码(6) ---guava之multimap
    java代码(5) ---guava之Multiset
    java代码(4)---guava之Immutable(不可变)集合
    java代码(2)---Java8 Stream
    java代码(3)----guava复写Object常用方法
    java代码(1)---Java8 Lambda
    看完这篇HTTP,跟面试官扯皮就没问题了(转)
  • 原文地址:https://www.cnblogs.com/jinsedemaitian/p/5589158.html
Copyright © 2011-2022 走看看