转载请注明出处:http://blog.csdn.net/mxway/article/details/44889871
本文是在飞鸽传书源码v2.06的基础上进行分析的。
文件的发送是在发送对话框中进行的,首先找到发送对话框的快捷菜单。
File Transfer对应的菜单id为MENU_FILEADD,相应的command处理事件在Senddlg.cpp中的EvCommand函数中
BOOL TSendDlg::EvCommand(WORD wNotifyCode, WORD wID, LPARAM hWndCtl)
{
switch (wID)
{
case MENU_FILEADD:
{
char buf[MAX_PATH] = "";
if (TShareDlg::FileAddDlg(this, shareMng, shareInfo ? shareInfo : (shareInfo = shareMng->CreateShare(packetNo)), cfg))
{
SetFileButton(this, FILE_BUTTON, shareInfo);
EvSize(SIZE_RESTORED, 0, 0);
}
}
break;
}
}
BOOL TShareDlg::FileAddDlg(TDlg *dlg, ShareMng *shareMng, ShareInfo *shareInfo, Cfg *cfg)
{
char buf[MAX_BUF] = "", path[MAX_BUF];
//打开文件对话框,OpenFileDlg::MULTI_OPEN设置可以同时打开多个文件
if (OpenFileDlg(dlg, OpenFileDlg::MULTI_OPEN).Exec(buf, ADDFILE_MSGSTR, OPENFILEALL_MSGSTR, cfg->lastOpenDir) != TRUE)
return FALSE;
//获取打开文件的路径字符长度
int dirlen = strlen(cfg->lastOpenDir);
//如果只选择一个文件,直接将文件加入到待发送文件的缓存区shareInfo中
if (buf[dirlen])
return shareMng->AddFileShare(shareInfo, buf);
//如果选择了多个文件,循环将每个文件都加入到待发送文件缓存区中
for (char *fname=buf+dirlen+1; *fname; fname += strlen(fname) +1)
{
if (MakePath(path, buf, fname) >= MAX_PATH)
continue;
shareMng->AddFileShare(shareInfo, path);
}
return TRUE;
}
打开文件对话框的调用是在OpenFileDlg类中的Exec中调用Win32的API GetOpenFileName()函数实现的。
从TShareDlg::FileAddDlg函数中可以看到待发送的文件是存储到Sh
areInfo类中。ShareInfo的声明如下
struct ShareInfo : public TListObj {
int packetNo; //
Host **host; // 要发送的目的机器列表
int hostCnt; // 要发送的目的机器个数
char *transStat; //
FileInfo **fileInfo; // 要传输的文件信息
int fileCnt; // 要传输的文件个数
FILETIME attachTime;
...
};
class FileInfo : public TListObj {
int id; // 要传输文件的id
char *fname; //文件名
const char *fname_ext; // for recv dir thread
UINT attr; //文件的属性,如是文件或文件夹,只读等
_int64 size; //文件大小
time_t mtime; //文件最后一次修改时间
time_t atime; //文件最后一次访问时间
time_t crtime; //文件创建时间
BOOL isSelected; // for recvdlg
...
}
再回到TShareDlg::FileAddDlg中,看shareMng->AddFileShare(shareInfo, path);
BOOL ShareMng::AddFileShare(ShareInfo *info, char *fname)
{
//如果要发送的文件已经加入到列表中,不再进行处理
for (int cnt=0; cnt < info->fileCnt; cnt++)
if (strcmp(fname, info->fileInfo[cnt]->Fname()) == 0)
return FALSE;
FileInfo *fileInfo = new FileInfo;
//设置新的待发送文件信息,如文件大小,最后一次修改时间等
if (SetFileInfo(fname, fileInfo) == FALSE)
return FALSE;
//如果fileInfo分配的内存已经使用完,需要分配理多的内存,用于存放新的文件信息
if ((info->fileCnt % BIG_ALLOC) == 0)
info->fileInfo = (FileInfo **)realloc(info->fileInfo, (info->fileCnt + BIG_ALLOC) * sizeof(FileInfo *));
info->fileInfo[info->fileCnt] = fileInfo;
info->fileCnt++;
return TRUE;
}
(1)文件在网络上进行传时的格式。
id:文件名:文件大小:最后一次修改时间:文件属性:
如果多个文件则多个文件之间的信息加入一个’\a’字符进行分隔。
(2)在Senddlg.cpp中的SendMsg,即按”Send”按钮的触发事件,其代码如下
BOOL TSendDlg::SendMsg(void)
{
...
if (shareInfo && shareInfo->fileCnt)
command |= IPMSG_FILEATTACHOPT;
...
if (shareInfo && shareInfo->fileCnt)
{
char buf[MAX_UDPBUF / 2];
//将文件生成(1)中的格式
EncodeShareMsg(shareInfo, buf, sizeof(buf));
shareStr = new char [strlen(buf) + 1];
strcpy(shareStr, buf);
shareMng->AddHostShare(shareInfo, sendEntry, sendEntryNum);
}
SendMsgSub();
...
}
BOOL EncodeShareMsg(ShareInfo *info, char *buf, int bufsize)
{
int offset=0;
char fname[MAX_PATH];
*buf = 0;
for (int cnt=0; cnt < info->fileCnt; cnt++)
{
ForcePathToFname(info->fileInfo[cnt]->Fname(), fname);
info->fileInfo[cnt]->SetId(cnt);
offset += wsprintf(buf + offset, (info->fileInfo[cnt]->Size() >> 32) ? "%d:%s:%x%08x:%x:%s" : "%d:%s:%x%x:%x:", cnt, fname, (int)(info->fileInfo[cnt]->Size() >> 32), (int)info->fileInfo[cnt]->Size(), info->fileInfo[cnt]->Mtime());
offset += wsprintf(buf + offset, "%x:", info->fileInfo[cnt]->Attr());
offset += wsprintf(buf + offset, "%c", FILELIST_SEPARATOR);
if (offset + MAX_BUF > bufsize)
break;
}
return TRUE;
}
SendMsgSub()中会对待发送的消息进行加密(传输的文件信息不进行加密)。
BOOL TSendDlg::SendMsgSub(void)
{
for (int cnt=0; cnt < sendEntryNum; cnt++)
{
if (sendEntry[cnt].Status() == ST_MAKECRYPTMSG) {
MakeEncryptPacket(sendEntry + cnt);
}
...
if (sendEntry[cnt].Status() == ST_SENDMSG) {
const char *str = sendEntry[cnt].Msg() ? sendEntry[cnt].Msg() : msgBuf;
int len = sendEntry[cnt].Msg() ? sendEntry[cnt].MsgLen() : packetLen;
msgMng->UdpSend(sendEntry[cnt].Host()->hostSub.addr, sendEntry[cnt].Host()->hostSub.portNo, str, len);
}
...
}
}
下面是使用飞鸽传软件发送文件的一个截图
图中红色框中就是要发送的文件信息,从图中可以看出发送的文件名为open_data_structures.pdf,文件大小是十六进制的01817d0字节,最后一次修改时间为十六进制53f492fe。
现在只是将待发送的文件信息发送出去,但是还没有将真正的文件内容传出去。文件内容的传输是通过TCP进行的。
Mainwin接收到发送来的udp消息后会对udp的消息进行处理。udp的处理在Mainwin.cpp的UdpEvent函数中。
BOOL TMainWin::UdpEvent(LPARAM lParam)
{
MsgBuf msg;
if (WSAGETSELECTERROR(lParam) || msgMng->Recv(&msg) != TRUE)
return FALSE;
...
switch (GET_MODE(msg.command))
{
...
case IPMSG_SENDMSG:
//处理发送来的消息
MsgSendMsg(&msg);
break;
...
}
...
}
void TMainWin::MsgSendMsg(MsgBuf *msg)
{
...
RecvDlgOpen(msg);
...
}
BOOL TMainWin::RecvDlgOpen(MsgBuf *msg)
{
TRecvDlg *recvDlg;
...
if ((recvDlg = new TRecvDlg(msgMng, msg, &hosts, cfg, logmng)) == NULL)
return FALSE;
...
recvDlg->Create();
recvDlg->Show();
recvDlg->SetForceForegroundWindow();
...
}
当接收端收到了消息后就会打开接收对话框。下面看下接收对话框的构造函数及接收对话框的EvCreate对话框
TRecvDlg::TRecvDlg(MsgMng *_msgMng, MsgBuf *_msg, THosts *_hosts, Cfg *_cfg, LogMng *_logmng) : TListDlg(RECEIVE_DIALOG), editSub(_cfg, this)
{
...
//可选字段中有传输文件的选项命令
if (msg.command & IPMSG_FILEATTACHOPT)
{
//从接收到的消息中解析出文件的信息
if ((shareInfo = DecodeShareMsg(msg.msgBuf + msg.exOffset)) != NULL)
{
fileObj = new RecvFileObj;
memset(fileObj, 0, sizeof(RecvFileObj));
}
}
...
}
BOOL TRecvDlg::EvCreate(LPARAM lParam)
{
...
if (msg.command & IPMSG_SECRETOPT)
::ShowWindow(GetDlgItem(RECV_EDIT), SW_HIDE), ::ShowWindow(GetDlgItem(QUOTE_CHECK), SW_HIDE);
else {
::ShowWindow(GetDlgItem(OPEN_BUTTON), SW_HIDE), openFlg = TRUE;
//将文件名显示到按钮中
if (shareInfo)
SetFileButton(this, FILE_BUTTON, shareInfo);
}
...
}
RecvDlg如下
在出现的对话框中,点击”open_data_structures.pdf”这个按钮。会弹出一个保存文件的对话框。点保存后文件的传输就会开始。关于文件的具体传输代码分析,下一篇再详细解析。