如果你从没看过这系列教程请点击:从零开始做远控 简介篇
在看教程之前我希望大家能下载个: Packet Sender软件,这软件可以然你向服务端发送Tcp数据,方便你搭建完服务器后用来做测试
TcpServer类:
1.首先我们新建一个TcpServer的类,继承于QObject,然后把QTcpServer include进来,记得要在你的.pro文件里Qt += network,不然你是无法使用网络库的。
2.我们的TcpServer是给多个类调用的,所以要以接口的方式编写,意思就是要其他类也能方便的调用它。
3.一旦有新连接,他就会将新连接的socket用信号发射给调用他的类
TcpServer.h
/*
* Author: sumkee911@gmail.com
* Date: 2016-12-19
* Brief: Zero远控tcp服务端接口
*
*/
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <QObject>
#include <QTcpServer>
class TcpServer : public QObject
{
Q_OBJECT
public:
explicit TcpServer(QObject *parent = 0);
// 启动服务端
// @port: 监听的端口
void start(int port);
void stop();
// 反回服务器
QTcpServer *server() {
return mServer;
}
private:
QTcpServer *mServer; // 在构造函数里初始化
signals:
// 当新的连接进来时发送的信号
// @sock: 新的连接
void newConnection(QTcpSocket *sock);
public slots:
// 当有从mServer中接收到新连接后,获取新连接的socket,然后再
// 发射newConnection信号
void newConnection();
};
#endif // TCPSERVER_H
#include "tcpserver.h"
TcpServer::TcpServer(QObject *parent) : QObject(parent)
{
mServer = new QTcpServer(this);
connect(mServer, SIGNAL(newConnection()), this, SLOT(newConnection()));
}
void TcpServer::start(int port)
{
if (!mServer->isListening()) {
if (mServer->listen(QHostAddress::AnyIPv4, port)) {
qDebug() << "服务端监听成功";
} else {
qDebug() << "服务端监听失败:" << mServer->errorString();
}
}
}
void TcpServer::stop()
{
if (mServer->isListening()) {
mServer->close();
}
}
void TcpServer::newConnection()
{
while (mServer->hasPendingConnections()) {
// 获取新连接
QTcpSocket *sock = mServer->nextPendingConnection();
// 发射新连接信号让调用服务器的类知道
emit newConnection(sock);
}
}
TcpSocket类:
1.我们再建立一个TcpSocket的类,继承于QObject,然后把QTcpSocket include进来。
2.它和TcpServer一样也重复调用的,也是以接口的方式编写。
3.socket是用来和客户通信的桥梁,所以它主要实现读写数据的功能
4.一旦从客户里接收到数据,他就会将接收到数据的信号发射给调用他的类
TcpSocket.h
/*
* Author: sumkee911@gmail.com
* Date: 2016-12-19
* Brief: Zero远控tcp socket接口
*
*/
#ifndef TCPSOCKET_H
#define TCPSOCKET_H
#include <QObject>
#include <QTcpSocket>
#include <QHostAddress>
class TcpSocket : public QObject
{
Q_OBJECT
public:
// 初始化socket
// @sock: 把sock加到这个类mSock的私有变量中
explicit TcpSocket(QTcpSocket *sock, QObject *parent = 0);
// 获取socket
QTcpSocket *socket() {
return mSock;
}
// 获取缓存区
QByteArray *buffer() {
return &mBuf;
}
// 断开和客户之间的连接
void close();
// 发送数据
void write(QByteArray data);
private:
QTcpSocket *mSock; // 客户
QByteArray mBuf; // 数据缓冲区,从客户里接收到的数据都会先放在这里
signals:
// 当有新数据加入到mBuf后就发射这个信号,让调用这个类的类知道,
// 然后在对新的数据作出相应的处理
void newData();
// 当客户断开是发射的信号
void disconnected();
public slots:
void readReady();
};
#endif // TCPSOCKET_H
TcpSocket.cpp
#include "tcpsocket.h"
TcpSocket::TcpSocket(QTcpSocket *sock, QObject *parent):
QObject(parent), mSock(sock)
{
mSock->setParent(this);
connect(mSock, SIGNAL(readyRead()), this, SLOT(readReady()));
connect(mSock, SIGNAL(disconnected()), this, SIGNAL(disconnected()));
// 输出信息
qDebug() << mSock->peerAddress().toString() << ":" << mSock->peerPort() << " 已连接上服务端";
}
void TcpSocket::close()
{
mSock->close();
}
void TcpSocket::write(QByteArray data)
{
mSock->write(data);
if (!mSock->waitForBytesWritten(3000)) {
// 发送数据超时
close();
emit disconnected();
// 输出信息
qDebug() << mSock->peerAddress().toString() << ":" << mSock->peerPort()
<< " 写入失败:" << mSock->errorString();
}
}
void TcpSocket::readReady()
{
mBuf.append(mSock->readAll());
emit newData();
}
1.这个类组合了刚才的TcpSocket类,用来处理客户的信息:“登入,登出”等等,向客户发送指:“监控屏幕,监控键盘”等等。
2.制定两组指令,一组是服务端向客户发送的指令,一组是客户端向服务端发送的指令。
3.处理从客户端发送过来的数据;向客户端发送指令。
4.如果新的客户登入后,就把它加入到ZeroServer里显示在widget类里的客户列表mClientTable里;登出则相反。
5.本类也设置一个计时器,如果长时间未能收到登录的消息就会自动跟客户断开
ZeroClient.h
/*
* Author: sumkee911@gmail.com
* Date: 2016-12-19
* Brief: Zero远控ZeroServer主要与客户对话的类
*
*/
#ifndef ZEROCLIENT_H
#define ZEROCLIENT_H
#include <QObject>
#include "tcpsocket.h"
#include <QTimer>
#include <QTcpSocket>
#include <QHostAddress>
class ZeroClient : public QObject
{
Q_OBJECT
public:
explicit ZeroClient(QTcpSocket *sock, QObject *parent = 0);
// 服务端向客户端发送的指令(你觉得有需要你也可以增加自己的指令)
const QByteArray CmdScreenSpy = "SCREEN_SPY";
const QByteArray CmdKeyboardSpy = "KEYBOARD_SPY";
const QByteArray CmdFileSpy = "FILE_SPY";
const QByteArray CmdCmdSpy = "CMD_SPY";
const QByteArray CmdSendMessage = "SEND_MESSAGE";
const QByteArray CmdReboot = "REBOOT";
const QByteArray CmdQuit = "QUIT";
// 客户端向服务端发送的指令(你觉得有需要你也可以增加自己的指令)
const QByteArray CmdLogin = "LOGIN";
// 分割符号和结束符号,比如登入命令:LOGIN<分割符>SYSTEM<分割符>Windows 7<分割符>USER_NAME<分割符>sumkee911<结束符号>
const QByteArray CmdSplit = ";";
const QByteArray CmdEnd = "\r\n";
// 断开客户
void closeAndDelete();
// 设置ID
void setId(int id) {
mId = id;
}
private:
TcpSocket *mSock; // 与客户通讯的socket
QTimer *mLoginTimeout; // 用来判断客户是否超时登入
int mId; // 初始值是-1, 登入后会由ZeroServer分配大于或等于0的ID号码
// 处理指令
// @cmd: 指令
// @args: 参数
void processCommand(QByteArray &cmd, QByteArray &args);
// 分解指令的参数,反回哈希表
QHash<QByteArray, QByteArray> parseArgs(QByteArray &args);
// 各个指令相应的函数
void doLogin(QHash<QByteArray, QByteArray> &args);
signals:
// 登入和登出信号
// @client: 自己
void login(ZeroClient *client, QString userName, QString ip, int port, QString system);
void logout(int id);
public slots:
// 如果客户在制定时间内还没有登入就踢了他
void clientLoginTimeout();
// 客户断开
void disconnected();
// 接收新数据
void newData();
};
#endif // ZEROCLIENT_H
ZeroClient.cpp
#include "zeroclient.h"
ZeroClient::ZeroClient(QTcpSocket *sock, QObject *parent) :
QObject(parent), mId(-1)
{
// 设置socket
mSock = new TcpSocket(sock, this);
connect(mSock, SIGNAL(newData()), this, SLOT(newData()));
connect(mSock, SIGNAL(disconnected()), this, SLOT(disconnected()));
// 设置计时器来判断客户是否登入,如果没就断开连接
// 我在这里设置10秒钟,很随意的,你想怎么设置都可以
mLoginTimeout = new QTimer(this);
connect(mLoginTimeout, SIGNAL(timeout()), this, SLOT(clientLoginTimeout()));
mLoginTimeout->start(10*1000);
}
void ZeroClient::closeAndDelete()
{
// 输出信息
qDebug() << mSock->socket()->peerAddress().toString() << ":"
<< mSock->socket()->peerPort() << " 已经断开服务端";
mSock->close();
deleteLater();
}
void ZeroClient::processCommand(QByteArray &cmd, QByteArray &args)
{
cmd = cmd.toUpper().trimmed();
QHash<QByteArray, QByteArray> hashArgs = parseArgs(args);
// 登入指令
if (cmd == CmdLogin && mId == -1) {
doLogin(hashArgs);
return;
}
}
QHash<QByteArray, QByteArray> ZeroClient::parseArgs(QByteArray &args)
{
QList<QByteArray> listArgs = args.split(CmdSplit[0]);
// 分解参数,然后把它加入哈希表
QHash<QByteArray, QByteArray> hashArgs;
for(int i=0; i<listArgs.length()-1 ; i+=2) {
hashArgs.insert(listArgs[i].toUpper().trimmed(),
listArgs[i+1].trimmed());
}
return hashArgs;
}
void ZeroClient::doLogin(QHash<QByteArray, QByteArray> &args)
{
// 发射登录信号
QString userName = args["USER_NAME"];
QString system = args["SYSTEM"];
QString ip = mSock->socket()->peerAddress().toString();
int port = mSock->socket()->peerPort();
emit login(this, userName, ip, port, system);
// 输出信息
qDebug() << ip << ":" << port << " 已经登入服务端";
}
void ZeroClient::clientLoginTimeout()
{
if (mId == -1) {
closeAndDelete();
}
}
void ZeroClient::disconnected()
{
if (mId >= 0) {
emit logout(mId);
}
closeAndDelete();
}
void ZeroClient::newData()
{
// 从socket里获取缓存区
QByteArray *buf = mSock->buffer();
int endIndex;
while ((endIndex = buf->indexOf(CmdEnd)) > -1) {
// 提取一行指令
QByteArray data = buf->mid(0, endIndex);
buf->remove(0, endIndex + CmdEnd.length());
// 提取指令和参数
QByteArray cmd, args;
int argIndex = data.indexOf(CmdSplit);
if (argIndex == -1) {
cmd = data;
} else {
cmd = data.mid(0, argIndex);
args = data.mid(argIndex+CmdSplit.length(), data.length());
}
// 处理指令
processCommand(cmd, args);
}
}
ZeroServer类:
1.这个类组合了刚才的TcpServer和ZeroClient类,是ZeroServer项目的主要服务端,用来管理客户。
2.客户登入后给他们分配ID,并且把他们显示在窗口的mClientTable控件里;登出则反之。
ZeroServer.h
/*
* Author: sumkee911@gmail.com
* Date: 2016-12-19
* Brief: Zero远控主要服务端
*
*/
#ifndef ZEROSERVER_H
#define ZEROSERVER_H
#include <QObject>
#include "TcpServer.h"
#include "ZeroClient.h"
#include <QHash>
class ZeroServer : public QObject
{
Q_OBJECT
public:
explicit ZeroServer(QObject *parent = 0);
// 启动或停止服务端
void start(int port);
void stop();
// 用id来获取ZeroClient
ZeroClient *client(int id) {
return mClients[id];
}
private:
TcpServer *mServer; // Tcp服务端
QHash<int, ZeroClient*> mClients; // 用ID来索引相应的客户
// 生成新的id
int generateId();
signals:
// 客户登入或登出,主要是告诉窗口控件
void clientLogin(int id, QString userName,
QString ip,int port, QString system);
void clientLogout(int id);
public slots:
// 新客户连接
void newConnection(QTcpSocket *sock);
// 客户登入
void login(ZeroClient*, QString userName,
QString ip, int port, QString system);
// 客户登出
void logout(int id);
};
#endif // ZEROSERVER_H
ZeroServer.cpp
#include "zeroserver.h"
ZeroServer::ZeroServer(QObject *parent) : QObject(parent)
{
// 初始化服务器
mServer = new TcpServer(this);
connect(mServer, SIGNAL(newConnection(QTcpSocket*)), this, SLOT(newConnection(QTcpSocket*)));
}
void ZeroServer::start(int port)
{
mServer->start(port);
}
void ZeroServer::stop()
{
mServer->stop();
}
int ZeroServer::generateId()
{
const int max = 1 << 30;
// 避免重复
QList<int> existsKeys = mClients.keys();
for (int i=mClients.size()+1; i<max; ++i) {
if (existsKeys.indexOf(i) == -1) {
return i;
}
}
return -1;
}
void ZeroServer::newConnection(QTcpSocket *sock)
{
// 创建ZeroClient,把sock添加进去
ZeroClient *client = new ZeroClient(sock);
connect(client, SIGNAL(login(ZeroClient*,QString,QString,int,QString)),
this, SLOT(login(ZeroClient*,QString,QString,int,QString)));
connect(client, SIGNAL(logout(int)), this, SLOT(logout(int)));
}
void ZeroServer::login(ZeroClient *client, QString userName, QString ip, int port, QString system)
{
// 增加客户到哈希表
int id = generateId();
mClients.insert(id, client);
client->setId(id);
// 发射登入信号给窗口控件
emit clientLogin(id, userName, ip, port, system);
}
void ZeroServer::logout(int id)
{
// 从哈希表中删除客户
mClients.remove(id);
// 发射登出信号给窗口控件
emit clientLogout(id);
}
搭建服务端:
1.在wiget.cpp的构造函数里初始化ZeroServer,我把它绑定在18000端口,随意的,看你自己喜欢
mZeroServer = new ZeroServer(this);
connect(mZeroServer, SIGNAL(clientLogin(int,QString,QString,int,QString)),
this, SLOT(addClientToTable(int,QString,QString,int,QString)));
connect(mZeroServer, SIGNAL(clientLogout(int)), this, SLOT(removeClientFromTable(int)));
mZeroServer->start(18000);
2.现在可以用Packet Sender软件做测试了,向你的服务端发送登入指令做测试了:"LOGIN;SYSTEM;windows xp;USER_NAME;sumkee911\r\n"
本节完整项目: