一. 远程控制系统
-
编译代码,生成可执行程序。注意Debug和Release的区别。
Debug我们称调试版本,编译链接生成程序时,包含了调试信息,程序的体积比较大。
调试程序比较方便,但是运行时,没有Release版本快。通常在开发程序时,选择Debug
模式。
Release我们称发布版本,编译链接生成程序时,不包含调试信息,编译器也会对代码优化,
程序的体积比较小。由于没有调试信息,所以在调试程序时不方便,但是运行的速度快。
通常开发完成后,生成Release版本交付用户使用。 -
首先启动Server端(被控端),注意设置它的屏幕分辨率为1024*768。启动后,看到Server端的IP地址。然后从client目录中查找client.ini文件,拷贝到解决方案下面的debug目录下,也就是说和client.exe同一目录下,修改该文件中的IP地址为Server端地址。启动客户端,点击 "截屏"按钮,然后在客户端就可以控制Server端了。
-
项目描述
远程控制系统是一个典型的C/S架构系统,功能类似于windows自带的远程连接和QQ上
的远程协助。具体实现时,使用的是TCP通信。TCP服务器端作为被控端首先启动,在
指定的端口监听,等待客户端连接。TCP客户端作为控制端启动后,连接服务器。连接后,
服务器将屏幕数据发送到客户端,在客户端的视图客户区显示;用户在客户端操作键盘或
鼠标,产生键盘/鼠标的消息。把该消息的数据发送给服务器,服务器收到后,调用相关的
API函数,执行键盘/鼠标的消息的处理。
二、解决方案设置
- 新建项目,在其它项目类型中,选择Visual Studio解决方案,名称为RemoteCtrl。
- 右击解决方案,选择添加新建项目。选择Win32项目,名称为Server,直接点击完成。
三、远程控制Server端步骤
1.使用socket库
- 在stdafx.h中加入以下代码:
#include <winsock2.h>
#pragma comment(lib,“ws2_32.lib”)
2. 在项目中添加GlobalHead.h和GlobalHead.cpp文件用于定义一些全局变量
–编译工程–
2.在server.cpp中完成互斥、事件并初始化socket库
首先包含头文件,#include “globalhead.h”。
- 创建互斥体,保证程序只运行一个实例
if ((hOnlyInstance=CreateMutex(NULL,false,"JFY's PeerYou"))==NULL) return -1; else if (GetLastError()==ERROR_ALREADY_EXISTS) return -1; ```
- 创建用于退出程序的事件
if ((hExitEvent=WSACreateEvent())==WSA_INVALID_EVENT) { CloseHandle(hOnlyInstance); return -1; } ```
- 初始化socket库
WSADATA wsaData; if (WSAStartup(MAKEWORD(2,2),&wsaData)!=0) { CloseHandle(hOnlyInstance); CloseHandle(hExitEvent); return -1; }
- 在InitInstance()函数中,如果创建窗口失败,关闭句柄,卸载socket库
if (!hWnd) { CloseHandle(hOnlyInstance); WSACloseEvent(hExitEvent); WSACleanup(); return FALSE; }
- 在最后程序退出前,关闭句柄,卸载socket库
CloseHandle(hOnlyInstance);
WSACloseEvent(hExitEvent);
WSACleanup();
–编译工程–
3.在窗口处理函数中处理
WM_CREATE、WM_PAINT、WM_CLOSE消息
-
case WM_CREATE:
// 1 获取主机名称和IP
// 2 创建线程 -
case WM_PAINT:
// 显示主机名称和IP -
case WM_CLOSE:
// 资源清理工作
4.socket通信处理
在工程中添加PeerSocket.h和PeerSocket.cpp文件完成服务器端通信函数的实现
#define PEER_STREAM SOCK_STREAM
#define PEER_DGRAM SOCK_DGRAM
#define PEER_RAW SOCK_RAW
bool PeerCreateSocket(SOCKET *pNewSocket,int iSockType);//创建
bool PeerBindSocket(SOCKET BindSocket,char *szHostAddr,int iHostPort);//绑定
bool PeerListenSocket(SOCKET ListenSocket);//监听
bool PeerAcceptSocket( SOCKET *pTravelSocket, SOCKADDR_IN *psockAddr, SOCKET ListenSock );//接收客户端连接
bool PeerShutDownSocket(SOCKET nowSocket);//断开连接
bool PeerCloseSocket(SOCKET nowSocket);//关闭连接
bool PeerSendData(SOCKET socket,char *data,DWORD len,DWORD *retlen);//发送数据
bool PeerRecvData(SOCKET socket,char *data,DWORD len,DWORD *retlen);//接收数据
bool PeerSendDataS(SOCKET socket,char *data,DWORD len,DWORD *retlen);//服务器端发送数据,调用发送数据函数
bool PeerRecvDataS(SOCKET socket,char *data,DWORD len,DWORD *retlen);//服务器端接收数据,调用接收数据函数
5.添加屏幕捕获的处理
在工程中添加CaptureScreen.h和CaptureScreen.cpp文件
void CleanScreenInfo(void);
bool GetScreenData(int nBits);
6.添加鼠标键盘和屏幕处理函数
在工程中添加ScreenCtrl.h和ScreenCtrl.cpp文件
bool PeerScreenMouseKey(SOCKET s);
bool PeerScreenGet(SOCKET s);
7.线程处理
- 在工程中添加command.h文件,定义用于封装传输信息的结构体
- 添加PeerThread.头文件,添加与线程相关的结构体、列表和线程函数的声明
- 添加PeerThread.cpp文件,添加线程函数的实现
8.在server.cpp和stdafx.h中添加相关的头文件包含
四、远程控制端Client步骤
右击解决方案,选择添加新建项目。
选择MFC项目,单文档、MFC标准、不使用unicode库,在用户界面设置,选择使用经典菜单。
1.使用TrueColorToolBar类创建工具栏
- 将TrueColorToolBar.h和TrueColorToolBar.cpp拷贝到Client目录下,并包含到当前项目中。
- 在CMainFrame类中包含头文件#include “TrueColorToolBar.h”,添加成员变量CTrueColorToolBar m_wndToolBar;
- 插入工具栏资源,修改工具栏按钮大小为32*32.将3个相关图片拷贝到当前目录,并导入到项目,修改图片的ID。
- 在OnCreate函数中完成工具栏的创建
2.添加socket库,创建事件
-
项目的头文件stdafx.h中添加以下代码:
#include <winsock2.h>
#pragma comment(lib,“ws2_32.lib”) -
App类的InitInstance()函数中,初始化socket库
if ((hExitEvent=WSACreateEvent())==WSA_INVALID_EVENT) { return -1; } WSAStartup(...);
-
App类的ExitInstance()函数中,卸载socket库
WSACleanup();
WSACloseEvent(hExitEvent);
3.在项目中,添加command.h文件
用于定义在客户端和服务器之间传递的数据的结构体。
4.socket通信处理
在工程中添加PeerSocket.h和PeerSocket.cpp文件完成客户端通信函数的实现
5.全局变量的定义
- 在Client.cpp中添加全局变量
WSAEVENT hExitEvent;
char ADDRESS[16];
BOOL g_ExitThread = FALSE;
CWinThread *hThreadScreen = NULL; - 在stdafx.h中添加全局变量的外部声明
extern WSAEVENT hExitEvent;
extern char ADDRESS[16];
extern BOOL g_ExitThread;
extern CWinThread *hThreadScreen;
6.在视图类中添加鼠标/键盘事件处理
7.在CMainFrame类中添加工具栏按钮的消息处理函数
8.注意的问题:ini 文件中的配置信息
参考代码
Server端:
// Server.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "Server.h"
#include "globalhead.h"
#include "CaptureScreen.h"
#include "PeerSocket.h"
#include "PeerThread.h"
#include "command.h"
#define MAX_LOADSTRING 100
// 全局变量:
HINSTANCE hInst; // 当前实例
TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
HWND hMainWnd;//主框架窗口句柄
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
// 1 创建互斥体,保证只有一个实例
if ((hOnlyInstance=CreateMutex(NULL,false,"JFY's PeerYou"))==NULL)
return -1;
else if (GetLastError()==ERROR_ALREADY_EXISTS)
return -1;
// 2 创建用于退出程序的事件句柄
if ((hExitEvent=WSACreateEvent())==WSA_INVALID_EVENT){
CloseHandle(hOnlyInstance);
return -1;
}
// 3 初始化socket库
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,2),&wsaData)!=0) {
// 无可用WinSock DLL.
CloseHandle(hOnlyInstance);
CloseHandle(hExitEvent);
return -1;
}
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此放置代码。
MSG msg;
HACCEL hAccelTable;
// 初始化全局字符串
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_SERVER, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_SERVER));
InitializeCriticalSection(&csCaptureScreen);
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
CloseHandle(hOnlyInstance);
WSACloseEvent(hExitEvent);
DeleteCriticalSection(&csCaptureScreen);
WSACleanup();
return (int) msg.wParam;
}
//
// 函数: MyRegisterClass()
//
// 目的: 注册窗口类。
//
// 注释:
//
// 仅当希望
// 此代码与添加到 Windows 95 中的“RegisterClassEx”
// 函数之前的 Win32 系统兼容时,才需要此函数及其用法。调用此函数十分重要,
// 这样应用程序就可以获得关联的
// “格式正确的”小图标。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SERVER));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_SERVER);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目的: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // 将实例句柄存储在全局变量中
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
CloseHandle(hOnlyInstance);
WSACloseEvent(hExitEvent);
WSACleanup();
return FALSE;
}
hMainWnd=hWnd;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目的: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_CREATE:
//得到主机名称
int nComputerNameLen;
nComputerNameLen=MAX_COMPUTERNAME_LENGTH + 1;
if(SOCKET_ERROR==gethostname(szHostName,nComputerNameLen))
return -1;
//得到主机IP地址
HOSTENT *hentThisHost;
hentThisHost=NULL;
if(!(hentThisHost=gethostbyname(