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