MVC流程讲解——以文件下载为例

发布于:2025-04-16 ⋅ 阅读:(18) ⋅ 点赞:(0)

整体的流程是这样:

用户点击一个树节点 → 请求远程机器该目录下的文件信息 → 显示在树控件和列表控件中。

🧱 MCV 模式简介(针对这个场景)

模块 代表什么 主要职责
Model(模型) 数据结构和逻辑 表示你传输的数据结构、接收包内容、文件信息等
Control(控制器) 逻辑处理层 控制整个“请求 + 等待 + 接收 + 通知”的流程
View(视图) 用户界面 树控件、列表控件,处理 UI 展示和用户点击

分模块讲解整体流程

💡 1. 用户界面交互:LoadFileInfo()

View:视图侧只敢显示相关的工作

你在主界面 CRomoteClientDlg::LoadFileInfo() 中处理用户点击行为:

HTREEITEM hTreeSelected = m_Tree.HitTest(ptMouse, 0);  // 获取用户点击的树节点

如果节点合法,获取该路径并清空已有显示:

CString strPath = GetPath(hTreeSelected);  // 获取路径字符串
m_List.DeleteAllItems();                   // 清空文件列表
mDeleteTreeChildrenItem(hTreeSelected);   // 清空树控件中的子项

 👉 职责:

  • 获取用户点击操作

  • 准备显示控件(清空列表、树子项)

  • 调用控制器接口发出请求

CClientController::getInstance()->SendCommandPacket(...);

💡 发送命令请求:SendCommandPacket()

这一句是你主动发送命令请求文件目录信息:

int nCmd = CClientController::getInstance()->SendCommandPacket(
    2,                  // 命令编号(代表“请求文件信息”)
    FALSE,              // bAutoClose = FALSE(说明你想收多个包再处理)
    (BYTE*)(LPCTSTR)strPath,  // 数据内容(请求的路径)
    strPath.GetLength(),      // 数据长度
    &lstPackets);             // 存储返回包的容器

💡2. Control 控制层:负责流程调度和逻辑控制

💡 创建发送包 + 等待响应:SendPacket(...)

发送命令是Control端的逻辑,将其与业务端分离使得业务可以更具有可移植性

SendCommandPacket() 内部通过 SendPacket(...) 发出数据包,并阻塞等待返回

需要通过事件控制线程的访问这里会出现一定问题

int nCmd = 2;
SendPacket(nCmd, strData, strDataLen, hEvent);
WaitForSingleObject(hEvent, INFINITE);  // 🔥 等待后台线程处理完毕

👉 职责:

  • 根据用户命令号、路径拼接数据包

  • 调用 Model 层发包接口

  • 等待后台线程处理完毕(通过事件 hEvent)

  • 收到响应后将数据包返回给 UI

这是 MCV 中最重要的“连接者”,负责:

  • 管理数据发送/接收流程;

  • 将数据打包交给 Model 层去传输;

  • 同时协调 View 和 Model。

3. Model 模型层:负责网络传输 & 数据结构

典型类:

  • CClientSocket:负责 线程收发 和网络通信

  • CPacket:自定义的 数据结构

  • PFILEINFO:你传输的文件结构体

void CClientSocket::threadFunc() {
    // ⬇️ 从 m_lstSend 中取出要发送的数据包
    Send(head);

    // ⬇️ 接收服务端发来的多个响应数据包
    int nRecv = recv(...);
    CPacket pack((BYTE*)pBuffer, size);
    
    PFILEINFO pInfo = (PFILEINFO)(pack.strData.c_str());

    if (pInfo->HasNext == false) {
        SetEvent(head.hEvent);  // 唤醒主线程
    }
}

 💡 后台线程处理数据包:CClientSocket::threadFunc()

你的后台网络线程会循环处理发送队列 m_lstSend,把里面的包取出来发送到服务器,并等着 recv()

CPacket& head = m_lstSend.front();
Send(head);  // 发送包

然后你在 recv() 循环中不断接收数据:

int Length = recv(m_server, pBuffer, BUFFER_SIZE - index, 0);
// 接收到数据后构建成 CPacket 对象:
CPacket pack((BYTE*)pBuffer, size);
pack.hEvent = head.hEvent;

再通过它内部的 strData 拿出 PFILEINFO

PFILEINFO pInfo = (PFILEINFO)(pack.strData.c_str());

📌 模拟整体流转

步骤 触发 所在模块 功能
1 用户点击树节点 View LoadFileInfo() 获取路径并准备界面
2 调用 Controller View → Control SendCommandPacket() 发出请求
3 数据打包并发送 Control → Model 创建 CPacket,调用 socket 发出
4 阻塞等待响应 Control WaitForSingleObject(hEvent)
5 线程接收数据 Model threadFunc() 收包、判断结束、SetEvent()
6 主线程被唤醒 Control → View 取回文件数据,更新 UI


网站公告

今日签到

点亮在社区的每一天
去签到