从零开始做远控 第二篇

如果你从没看过这系列教程请点击:从零开始做远控 简介篇

第二节是搭建ZeroServer的网络通信:
在看教程之前我希望大家能下载个: 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


TcpServer.cpp
#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();
}



ZeroClient类:
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"



本节完整项目:

下载

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值