尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
VC++远程控制源码实战:Winsock通信与MFC功能解析
简介用Visual C开发远程控制软件的源码包适合需要学习Windows网络编程和系统控制原理的初学者及中级开发者能够帮助快速搭建基于TCP/IP的远程控制原型。压缩包内共30个文件其中10个头文件与8个C源代码文件构成了程序主体负责接口声明与具体功能实现其余的图标、对话框资源以及工程文件支撑起整个项目的界面和编译体系整套源码仅37KB体积小巧非常便于逐行阅读和深入调试。源码中“获取信息”模块演示了通过套接字请求来获取远程计算机系统状态与硬件配置的完整方法“登录验证”部分则涉及用户登录会话和权限管理体现了远程控制场景下对安全性的关注同时还可看到图形设备接口截屏、键盘鼠标事件模拟、多线程并发处理以及数据加密等关键技术点。整体代码结构清晰将网络传输、控制指令和界面资源进行分层组织方便使用者按模块复用或二次扩展。目前已有687人学习下载这份源码框架不仅能让学习者快速理解远程控制底层的实现思路也为后续自主开发提供了现成且可改写的模板性价比高值得研究。1. 拿到 VC 远程控制源码后先想清楚这三件事看到 “visual c vc编写远程控制软件源码” 这个标题我第一反应是这不就是十几年前那批“远控工具”的经典套路但别急着往灰色方向想。现在公司机房维护服务器、给同事远程排除演示机故障、在家操作实验室那台没接显示器的工控机这类需求依然很硬。用 visual c 写远程控制最可靠的方案不是去啃什么高大上的框架而是 Winsock 通信 MFC/对话框界面 管道调命令行这套组合在局域网管理场景里比上全套物联网平台轻得多。动手读源码前先确认三件事源码跑在哪个 Windows 版本、被控端要不要常驻、控制端和被控端之间是同一局域网还是跨网段。这三件事定了后面读代码、改功能才不会迷路。2. 骨架先用 Winsock 把双向链路跑通2.1 为什么优先 Winsock 阻塞多线程而不是 CAsyncSocket很多人拿到 VC 远程控制源码一看里面有 MFC第一个念头就是把 CAsyncSocket 搬出来用理由是网络事件到了会自动回调不用自己开线程。实际一跑就露馅回调发生在系统的工作线程里你直接在回调里弹一个对话框界面线程没准备好程序要么闪退要么消息乱序。我的习惯是 Winsock 裸接口 监听线程 每连接一个工作线程UI 只接收工作线程 PostMessage 过来的通知。逻辑是直线的出错看调用栈就知道卡在哪一行。阻塞模型对远程控制这种“一问一答”为主的交互足够用吞吐量根本不是瓶颈。WSAStartup 负责初始化 Winsocksocket 创建套接字bind 固定端口listen 进入监听状态accept 接收新连接recv/send 收发数据。控制端只需 socket connect比服务端少三个步骤。下面两段代码分别是被控端监听和控制端连接的最小实现可以直接照着搭。2.2 被控端监听循环核心代码与逐行说明// 被控端初始化 Winsock 并开始监听 #include winsock2.h #include ws2tcpip.h #pragma comment(lib, ws2_32.lib) #define CTRL_PORT 5566 SOCKET StartListen() { WSADATA ws; WSAStartup(MAKEWORD(2, 2), ws); // 1. 初始化必须最先调用 SOCKET listenSock socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (listenSock INVALID_SOCKET) { return INVALID_SOCKET; } SOCKADDR_IN addr; addr.sin_family AF_INET; addr.sin_port htons(CTRL_PORT); // 2. 端口转网络字节序 addr.sin_addr.s_addr htonl(INADDR_ANY); // 3. 绑定本机所有网卡 if (bind(listenSock, (SOCKADDR*)addr, sizeof(addr)) SOCKET_ERROR) { closesocket(listenSock); return INVALID_SOCKET; } if (listen(listenSock, SOMAXCONN) SOCKET_ERROR) { // 4. 开始监听 closesocket(listenSock); return INVALID_SOCKET; } return listenSock; }注意 bind 成功后可以立刻 setsockopt 打开 SO_REUSEADDR方便你反复重启被控端调试时端口不被 TIME_WAIT 占住。listen 的第二个参数是内核帮排队的连接数量局域网场景直接给 SOMAXCONN 就好。这段代码里我没写 accept留给服务端线程循环里调用accept 返回一个新的 socket后续收发都走这个新 socketlistenSock 继续等下一路连接。2.3 控制端连接与简单握手// 控制端连接被控端并验证握手口令示意 SOCKET ConnectToHost(const char* ip, const char* password) { WSADATA ws; WSAStartup(MAKEWORD(2, 2), ws); SOCKET sock socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); SOCKADDR_IN srv; srv.sin_family AF_INET; srv.sin_port htons(5566); inet_pton(AF_INET, ip, srv.sin_addr); // 点分十进制转 in_addr if (connect(sock, (SOCKADDR*)srv, sizeof(srv)) SOCKET_ERROR) { closesocket(sock); return INVALID_SOCKET; } // 握手先把固定长度的口令发给被控端 send(sock, password, strlen(password) 1, 0); char ack[16] {0}; recv(sock, ack, sizeof(ack), 0); // 等被控端确认 if (strcmp(ack, OK) ! 0) { closesocket(sock); return INVALID_SOCKET; } return sock; }握手的作用不是高安全而是避免局域网里有人猜个 IP 把你的被控端连走。口令先写死在源码里后期再改成配置项。inet_pton 这个函数在老 VC6 里没有如果你手里的源码包是 VC6 工程建议换用较新版本的 visual studio 打开socket 部分调用的是平台 API源码改动面积很小只把 inet_pton 换成 inet_addr 即可。send 和 recv 的返回值这里没做长度循环下一章讲协议时会补上这是代码能跑和能稳定跑的分水岭。提示开发阶段 Windows 防火墙会拦截入站连接先在防火墙设置里放行 5566 端口或者在被控端弹窗时点允许。交付前记得收敛规则别留下全校广播用的开放端口。3. 指令协议控制端和被控端靠什么对齐3.1 三段式包结构Magic Type Length现在两端能连通了但如果直接裸 send 一段字符串“dir”或者“screenshot”收方很容易拆错。TCP 是字节流不保证一次 recv 就收到完整一包。在远程控制源码里我习惯定义一个固定头部魔数Magic、指令类型Type、载荷长度Length。#pragma pack(push, 1) typedef struct _PKT_HEADER { DWORD magic; // 固定 0x5A5A, 用来过滤错包 DWORD type; // 指令类型: 1Shell, 2Screen, 3File DWORD length; // 载荷长度, 不包含头部自身 } PKT_HEADER; #pragma pack(pop)头部固定 12 字节所有字段用本机字节序还是网络字节序只在你自己的两端之间传输本机字节序省事但如果你将来要写一个 C# 控制端或者接入其他语言建议发送前统一 htonl接收后再转回来。pack(1) 字节对齐是为了让结构体大小可预测否则编译器按 4 字节对齐后结构体可能变成 16 字节两端定义不一致直接导致解析错位。3.2 粘包和半包recv 的正确姿势远程控制源码里最容易翻车的就是收数据。一次 recv 可能只收到半个包也可能一次收到两个包必须靠循环收够指定长度再用头部里的 length 字段切出完整一包。// 完整接收 n 字节处理半包 int recvn(SOCKET s, char* buf, int n) { int got 0; while (got n) { int ret recv(s, buf got, n - got, 0); if (ret 0) return -1; // 对端关闭连接 if (ret SOCKET_ERROR) return -1; got ret; } return got; } // 收一包先收头再收体 BOOL RecvPacket(SOCKET s, PKT_HEADER* hdr, char** payload) { if (recvn(s, (char*)hdr, sizeof(PKT_HEADER)) 0) return FALSE; if (hdr-magic ! 0x5A5A) return FALSE; // 魔数不对丢弃 if (hdr-length MAX_PAYLOAD_SIZE) return FALSE; // 防恶意长度 *payload new char[hdr-length 1]; (*payload)[hdr-length] \0; if (recvn(s, *payload, hdr-length) 0) { delete[] *payload; return FALSE; } return TRUE; }核心逻辑就是 while (got n)recv 单次返回长度小于请求长度是正常的尤其包大或者网络有波动必须循环收。粘包则靠“先收足头部、再收足载荷”天然消化因为下一次调用 recvn 会从下一个包边界开始。MAX_PAYLOAD_SIZE 我一般设成 8MB防止被控端发来一个异常长度把内存吃爆。这两个函数是后面所有功能模块的地基先运行一遍再往下接功能。3.3 指令分发一条连接上跑多个功能协议定了主循环就不难写// 被控端主循环收包 - 分发 - 应答 while (RecvPacket(sock, hdr, payload)) { switch (hdr.type) { case CMD_SHELL: HandleShell(sock, payload, hdr.length); break; case CMD_SCREEN: HandleScreen(sock); break; case CMD_FILE: HandleFile(sock, payload, hdr.length); break; default: break; } delete[] payload; }循环里每处理完一包建议 Sleep(1) 让出时间片不然被控端单核老机器上 CPU 占用直接拉满。屏幕抓取这类耗时指令控制端一次只发一个请求就好不要做成多指令交错流水线否则协议要加 seq 序号和 ACK 队列复杂度直接翻倍。远程控制这个定位一问一答最稳。指令名和常量对应关系写在源码头部注释里方便两个端同步维护。4. 往源码里塞功能Shell、截屏、传文件的三段实现4.1 远程 ShellCreatePipe 接管 cmd 的标准输入输出远程控制最常用的功能就是让控制端能敲命令。常见做法是启动一个 cmd.exe把它的 stdin 和 stdout 重定向到管道控制端发命令进去读输出回来。void HandleShell(SOCKET sock) { HANDLE hReadPipe NULL, hWritePipe NULL; SECURITY_ATTRIBUTES sa { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; CreatePipe(hReadPipe, hWritePipe, sa, 0); STARTUPINFOA si { sizeof(si) }; si.hStdOutput hWritePipe; // cmd 输出进管道写端 si.hStdError hWritePipe; // 错误输出也走同一条管道 si.hStdInput hReadPipe; // 键盘输入从管道读端来 si.dwFlags STARTF_USESTDHANDLES; PROCESS_INFORMATION pi; BOOL ok CreateProcessA(NULL, cmd.exe /Q, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, si, pi); if (!ok) { // 创建失败返回错误码给控制端 return; } // 之后开一个线程: 循环 ReadFile(hReadPipe) 拿到输出, 打包发给 sock // 再开一个线程: 循环 recv(sock) 拿到命令, WriteFile(hWritePipe) }两个坑必须提前避开。第一管道缓冲区只有 64KBcmd 跑 dir 输出一大坨时管道会满cmd 就僵住所以读管道必须单独开线程边读边发。第二CREATE_NO_WINDOW 标志让控制台窗口不弹出来被控端用户看不到黑框但用了这个标志后cmd 里执行的交互式命令比如需要按 y 确认的 del会不好处理初版远程 Shell 只接受非交互命令比较省心。CreateProcess 第一个参数传 NULL 时第二个参数的命令行必须以可执行名开头写 cmd.exe /Q 而不是 cmd.exe /Q否则返回 87 号参数错误这是老源码注释里经常被一笔带过的坑。4.2 屏幕抓取BitBlt 抓屏再压 JPEG远程看屏幕是另一个高频功能。被控端用 BitBlt 把整块屏幕复制到位图转成 JPEG 再发过去。void HandleScreen(SOCKET sock) { int w GetSystemMetrics(SM_CXSCREEN); int h GetSystemMetrics(SM_CYSCREEN); HDC hScreen GetDC(NULL); HDC hMem CreateCompatibleDC(hScreen); HBITMAP hBmp CreateCompatibleBitmap(hScreen, w, h); SelectObject(hMem, hBmp); BOOL ok BitBlt(hMem, 0, 0, w, h, hScreen, 0, 0, SRCCOPY); if (ok) { // 转 JPEG: 把 HBITMAP 转成 DIB 数据交给 GDI 编码 // 质量参数设 70一帧约 100~300KB // 组装成 CMD_SCREEN_DATA 包发给控制端 } DeleteObject(hBmp); DeleteDC(hMem); ReleaseDC(NULL, hScreen); }BitBlt 抓到的是设备相关位图直接裸传一张 1920x1080 的 BMP 是 8MB 级别局域网线速传输也吃力。常见做法是抓完立刻转 JPEG质量 70 左右足够看清文字。帧率控制在每秒 2~5 帧远程看点按钮、看个报错信息不需要 60 帧刷新率太高不只是带宽问题被控端 CPU 占用会难看得离谱。如果只需要看某个窗口可以用 GetWindowDC 替代 GetDC(NULL)抓指定窗口区域帧率可以提上去不少。4.3 文件传输分块 校验注意方向传输文件时最怕发到一半连接断了两端都不知道传了多少。#define FILE_CHUNK (64 * 1024) void SendFileBegin(SOCKET sock, const char* path) { // 先发文件名和总大小元数据然后循环发内容 HANDLE f CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (f INVALID_HANDLE_VALUE) return; char buf[FILE_CHUNK]; DWORD n 0; while (ReadFile(f, buf, FILE_CHUNK, n, NULL) n 0) { // 每 block 发一个 CMD_FILE_DATA 包等 ACK 再发下一块 if (SendPacketAndWaitAck(sock, CMD_FILE_DATA, buf, n) FALSE) { break; } n 0; } CloseHandle(f); }64KB 一块走的是“发一块等一个 ACK”的停止等待协议。局域网延迟只有 1ms 级别吞吐损失可以忽略好处是任何一块丢了重传位置很好定位。断点续传看起来体面但要在控制端维护每个文件的偏移量、校验和、剩余块列表源码工程量翻倍初版不建议上。另一个方向是从控制端往被控端发文件逻辑对称注意收端要先创建目录、再写入文件文件名用宽字符处理中文路径。5. 避坑记录远程控制代码在 Windows 上常见的 5 个翻车现场5.1 界面假死MFC 消息循环与阻塞收发打架现象控制端一按“截屏”按钮窗口转圈拖一下标题栏就白屏。原因把 recvn 循环写在了按钮的 BN_CLICKED 处理函数里UI 线程在阻塞等包消息循环转不动了。解决按钮处理函数只负责往工作线程里塞任务工作线程完成后 PostMessage 回 UI 线程更新画面。网络线程绝不能直接读写控件这是 VC 远程控制源码里最常见的改造点。5.2 被控端空闲一段时间就“断连”现象什么都不操作两三分钟后再发指令没反应抓包发现连接已经 RST。原因Windows 电源策略或中间设备把闲置连接关了而源码里没有心跳机制。解决被控端每 5 秒发一个心跳包控制端连续 3 个心跳没收到就判定连接断开并重连。心跳包在协议里给独立 type0和功能指令分开处理。5.3 传大文件时其他指令排队卡住现象传一个 2GB 镜像期间再发 Shell 指令十几秒才有回应。原因一条 TCP 连接上数据流串行文件块把连接占满了。解决把文件传输拆到第二条独立连接控制指令继续走原连接。初版如果不拆连接也要在控制端 UI 上把“文件传输中”状态显示清楚避免使用者以为程序挂了。5.4 截屏黑块会话 0 隔离和安全桌面现象被控端锁屏或弹出 UAC 授权窗口时截屏回来是黑块或者只剩壁纸。原因安全桌面和会话隔离机制普通 BitBlt 拿不到高权限窗口内容。解决检测被控端当前会话状态发现前台窗口属于安全桌面或者会话 ID 为 0直接返回“屏幕当前不可见”的明文提示比发一帧黑图让使用方困惑好得多。合盖笔记本截屏也是常见黑屏源不要在电源选项里强制关屏。5.5 中文乱码且不定时触发现象远程执行 dir 命令文件名的中文变成“????”。原因cmd 管道输出按系统代码页GBK编码而控制端界面按 Unicode 直接解析。解决读管道拿到的字节先按本地代码页转成 UTF-8 再打包。工程里统一写一个 ToUtf8(buf, len) 包装函数所有文本型输出都过它。还有一个玄学点cmd 切代码页是滞后生效的命令下发后 Sleep 200ms 再读输出乱码会少很多。提示第 5.4 条和第 5.5 条是“环境相关”问题换机器复现不出来很正常。调试时把系统版本、屏幕状态、代码页一起写进日志比瞎猜高效得多。6. 验证与进阶跑通后先把日志和自启动管控做好6.1 日志远程控制的第一等公民void LogEvent(const char* fmt, ...) { // 本地写文件, 每次会话一行: 时间, 指令类型, 对端IP, 结果 FILE* f fopen(ctrl_audit.log, a); if (f) { // 拼时间戳 格式化字符串 fputs(line, f); fclose(f); } }日志是我的强迫症。远程控制天然敏感被控端每次收到的指令都应该有据可查谁在什么时候连过、发过什么命令、传输过哪个文件。调试时它是救命稻草部署后它是合规凭据。建议在连接建立、指令分发、文件传输完成这三个节点各打一条日志字段统一别随手写。6.2 开机自启别做成静默自启HKEY key NULL; RegOpenKeyExA(HKEY_CURRENT_USER, Software\\Microsoft\\Windows\\CurrentVersion\\Run, 0, KEY_SET_VALUE, key); RegSetValueExA(key, RemoteControlAgent, 0, REG_SZ, (BYTE*)exePath, strlen(exePath)); RegCloseKey(key);这是写 HKCU Run 键实现当前用户开机自启的标准做法。但自启功能不能偷偷来部署脚本里要显式声明“该程序会开机自启”并在设置界面留一个“取消自启动”按钮调用 RegDeleteValueA 删除刚才的键。很多翻车现场就是自启装上之后找不到清除入口被控端用户只能手动去注册表删体验极差。6.3 验证方法两台虚拟机跑一遍全链路最小可用版本的验证步骤两台虚拟机同一网段被控端防火墙放行端口控制端命令行带 IP 启动。依次验证连接握手 → 远程 Shell 执行 dir → 截屏一帧 → 传一个小文件 → 断开重连。全链路通了你再扩展协议加新指令。每加一个功能我习惯先把旧功能回归一遍远程控制这玩意儿改一处挂三处的概率特别高。日志开着命令一条条发等返回看时间戳——这三个习惯救过我很多次希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

基于YOLOV8的道路裂缝检测:从数据治理到工程落地

基于YOLOV8的道路裂缝检测:从数据治理到工程落地

1. 为什么裂缝检测这件事值得用一个目标检测框架来做1.1 路面裂缝的形态差异与检测难点我做过一段时间道路病害巡检相关的方向,最开始其实没打算一上来就上目标检测,而是先认真考虑过传统的数字图像处理方案。说实话,如果只是处理那种“背景干…

📅 2026/10/22 3:01:50
ReAct模式从原理到实战:构建高效LLM Agent的完整指南

ReAct模式从原理到实战:构建高效LLM Agent的完整指南

做LLM应用开发的同行,最近应该都被Agent这个词刷屏了。翻开源代码,无论是成熟框架还是个人Demo,大部分都绕不开ReAct模式。我最初接触ReAct时,以为是什么高深莫测的新架构,真正啃完论文、动手写过几轮之后才发现&#…

📅 2026/10/22 3:01:50
8款AI文献综述工具,从检索到润色全流程指南

8款AI文献综述工具,从检索到润色全流程指南

作为一个帮学弟学妹改过多年综述、自己也经历过毕业论文文献综述毒打的人,我太清楚“文献综述”这四个字对本科生的杀伤力了。它不光是写作文,也不只是读书笔记,而是一篇要在几千字里完成“提出问题—梳理脉络—评述立场”的学术论证。很多同…

📅 2026/10/22 3:01:50
MORE NEWS

更多资讯

📰

VB与三菱Q系列PLC通讯源码解析:从MC协议到稳定收发

简介:一份面向工业自动化上位机开发者的VB与三菱Q系列PLC通讯数据传输源码包,聚焦串口与网络通信场景,适合具备VB基础并希望掌握PLC数据交换的开发新手,也适合需要实际工程参考的进阶人员。整个压缩包仅277KB,包含51个…

📰

AI辅助教材写作:从初稿到低查重的完整实操指南

1. 先搞明白:AI写教材和查重之间的真实关系1.1 教材写作的难点到底在哪里教材这个品类很特别,它不像自媒体文章,写错了改一改就行,也不像论文,只需要把论点论证清楚。教材要面对的是“教”和“学”两个场景&#xff0c…

📰

AI编程智能体分级选型与全行业落地实践指南

1. 智能体分级体系到底在分什么1.1 从"能聊天"到"能干活"的五个台阶很多人第一次接触AI编程智能体,脑子里浮现的还是那个"你问我答"的聊天框。但真正在工程一线用过智能体的人都知道,聊天只是最表层的能力。行业里目前比较…

📰

无需训练即可评估神经网络架构:NASWOT原理与实践

1. 这篇论文到底想解决什么问题第一次看到"Neural Architecture Search without Training"这个标题,我的反应是:不训练怎么搜索?这不是悖论吗?毕竟NAS(神经网络架构搜索)这个领域从诞生之初就带着…

📰

Linux swap 启停操作与 swappiness 调优实战指南

1. 从一次线上告警说起:swap 到底在什么情况下会咬人凌晨两点,监控面板上一条内存使用率的曲线突然拉满,紧接着几台业务节点的响应时间从几十毫秒飙到两秒以上。登录机器一看,free -h显示物理内存还剩一点,但 swap 已经…

📰

HBase建表源码深度解析:从客户端到Master的完整链路与Procedure状态机

1. 从一次建表超时说起:为什么要啃 HBase 建表源码如果你在生产环境维护过 HBase 集群,大概率遇到过这样的场景:客户端调用createTable()之后卡住不动,或者返回超时异常,但表实际上已经建好了;又或者建表请…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬