序言
我这里给出的是一个简单的协议Demo,可以根据我注释里面的说明,自己丰富完善协议
关键代码
发送端
这里我放在了一个线程函数里面,加入了自己的协议结构,如果有兴趣可以自己提取出来使用这个模式。思考的大脑是你前行的灯,这里就没有摆出你直接Copy的接口。但是全部内容已在其中,不懂可以留言,我看到就回复。
typedef struct _FtProto
{
BOOL IsDone;
DWORD TotalSize;
DWORD CurNo;
BYTE CurData[2048];
}FtProto,*PFtProto;
//发送文件的线程函数,lParameter 参数 是一个自定义结构体,里面包含了文件名等信息
DWORD frdTalkDlg::packFile(LPVOID lParameter)
{
//转换成结构体指针
PsendInfo msg = (PsendInfo)lParameter;
//1. 打开文件
CString fname;
fname.Format(L"%s", msg->fileMsg.Bytes);
//::MessageBox(NULL,fname, L"大小", MB_OK);
ifstream infile(fname,ios::binary);
if(!infile.is_open())
::MessageBox(NULL, L"文件打开失败", L"大小", MB_OK);
//2.获取大小
infile.seekg(0, ios::end);
int length = (int)infile.tellg();
CString test;
test.Format(L"%d", length);
//测试大小是否获取成功, 问题? 为啥不能使用MessageBox()??
//::MessageBox(NULL,test,L"大小",MB_OK);
//3. 切片发送
//3.1 指针设置回到文件开始的地方
infile.seekg(0, ios::beg);
//3.2 确定切片个数
int totalNo = length % 1892 ? length / 1892 + 1 : length / 1892;
test.Format(L"%d", totalNo);
//::MessageBox(NULL, test, L"大小", MB_OK);
int i = 0;
//3.2 发送每一个切片
while (i<totalNo)
{
//一个切片
PsendInfo piece = new sendInfo;
//切片信息
piece->hWnd = msg->hWnd;
piece->type = protoType::fileMsgTrp;
//重新设置来源 和 目标
memcpy(piece->fileMsg.fromID, msg->fileMsg.fromID, 64);
memcpy(piece->fileMsg.toID, msg->fileMsg.toID, 64);
//设置目标窗口的句柄
piece->fileMsg.toWnd = msg->fileMsg.toWnd;
//设置是否是最后一个的标志位
piece->fileMsg.isdone = (i == totalNo - 1 )? 1 : 0;
//设置切片的序号
piece->fileMsg.No = i;
//设置文件的总大小
piece->fileMsg.totalLength = length;
//拷贝当前切片的数据
infile.read((char *)piece->fileMsg.Bytes, (i == totalNo - 1) ? length%1892:1892);
//发送数据,通过Send函数发送
::SendMessage(my->logDlg->GetSafeHwnd(), UM_SEND, NULL, (LPARAM)piece);
i++;
}
::MessageBox(my->GetSafeHwnd(),L"文件传输完成!注意查收.",L"提醒",MB_OK);
infile.close();
return 0;
}
接收端
这里的代码是放在了MFC的一个消息处理函数里面,大家有兴趣的话提取出来放在你的代码里。大家要思考,思考才是进步的源泉。
//文件处理
afx_msg LRESULT frdTalkDlg::OnUmFlemsg(WPARAM wParam, LPARAM lParam)
{
PsendInfo fileInfo = (PsendInfo)lParam;
//初始化文件指针
if (fileInfo->fileMsg.No == 0)
{
//如果以前传送过文件,则清理空间
int totalNO = fileInfo->fileMsg.totalLength % 1892
? fileInfo->fileMsg.totalLength / 1892 + 1 : fileInfo->fileMsg.totalLength / 1892;
fileP = new char[totalNO *1892];
ZeroMemory(fileP, totalNO*1892);
}
//拷贝数据
memcpy(fileP + 1892 * fileInfo->fileMsg.No, fileInfo->fileMsg.Bytes, 1892);
//如果标志位已经完成则存储文件
if (fileInfo->fileMsg.isdone == 1)
{
//以时间为名字命名
SYSTEMTIME systime;
GetSystemTime(&systime);
CString fname;
//以时间命名
fname.Format(L"%d-%d-%d-%d-%d-%d", systime.wYear, systime.wMonth, systime.wDay,
systime.wHour, systime.wMinute, systime.wSecond);
//给文件名添加扩展名
fname = fname + exName;
//打开文件流
ofstream outfile(fname, ios::binary);
outfile.write(fileP, fileInfo->fileMsg.totalLength);
outfile.close();
MessageBox(L"文件接收完成:文件名- " + fname);
delete[]fileP;
fileP = nullptr;
}
return 0;
}
自定义文件协议
协议主体
传输大型文件的时候,肯定的一点是,你不可能通过一次数据包的发送 接收就实现文件的传输。所以,需要用到切片的思想。给每一个切片数据一个编号,每个编号确定在文件的唯一位置。在接收方进行拼接组合最终保存。
typedef struct _FtProto
{
BOOL IsDone; //【可选】标志是否完成,可以本地借助编号和总大小计算是否完成但是有这个标着更直观
DWORD TotalSize;//【可选】文件的总大小,可以自定义复杂的协议,在传输发起前协商好大小
DWORD CurNo;//【必选】标志着当前数据包,是出于目标文件的那个位置
BYTE CurData[2048];//【必选】这个是当前数据包 -- 目标文件的一个切片数据,大小可以自定义
}FtProto,*PFtProto;
如果只是简单的传送文件,这个协议(结构体)完全已经能够满足。但是在大型项目中,想要形成完整的体系,稳定的系统,这是不够的。
完整的协议思想
这里的完整的协议,只是个人的经验总结,并不是标准规定,不能看了本文 反而局限 你的思想发散。
01 协议的协商
发送文件之前,需要先协商好文件的相关信息。
比如:
- 文件的大小
- 文件的类型
- 文件的名称
- 等等
02 协议数据包主体
即每个切片的数据包形式。
比如:
- 切片的编号(便于在另一端组合文件的时候使用,以及检测是否有丢包)
- 切片的数据
03 协议的完成
在我方数据包部分或全部发送完成后,另一端应该遵循一定的规则检测是否有丢包,如果有丢包应该请求再次发送。
比如:
- 可以全部切片传输完毕后,最后确定是否有丢包。
- 也可以每发送一部分,检测是否有丢包。
- 如果有丢包,发起重发对应切片的请求。
- 核对完毕,保存文件
以上三个主要步骤,都需要发送数据包。而每一种包又是一种格式(协议),如有需请自行设计相关字段。