飞鸽传书源码分析五-文件传输

转载请注明出处:http://blog.csdn.net/mxway/article/details/44889871
本文是在飞鸽传书源码v2.06的基础上进行分析的。

1.添加要发送的文件

文件的发送是在发送对话框中进行的,首先找到发送对话框的快捷菜单。

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()函数实现的。

2. 待发送文件的存储结构

从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;
}

3. 在网络中进行文件传输

(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。

4.接收端的处理

现在只是将待发送的文件信息发送出去,但是还没有将真正的文件内容传出去。文件内容的传输是通过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如下
飞鸽传书源码分析五-文件传输_第1张图片
在出现的对话框中,点击”open_data_structures.pdf”这个按钮。会弹出一个保存文件的对话框。点保存后文件的传输就会开始。关于文件的具体传输代码分析,下一篇再详细解析。

你可能感兴趣的:(源码,源码分析,文件,文件传输,飞鸽传书)