整体的流程是这样:
用户点击一个树节点 → 请求远程机器该目录下的文件信息 → 显示在树控件和列表控件中。
🧱 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 |