QT搭建C/S架构服务器端

两年前初学时做的C/S架构服务器,现在整理下发出来供大家参考。

哈哈哈,发现居然有人看,资源放这了,有需要可以瞅瞅,没积分可留言。

首先是入口处,创建tcpServer类进行监听QT使用TCP模块需要在Pro文件加上QT += sql network,sql模块是因为使用了数据库。

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MyTcpServer* tcpServer = new MyTcpServer();
    tcpServer->listen(QHostAddress::Any, quint16(10626));
    qDebug() << "开始监听端口: 10626";
    return a.exec();
}

MyTcpServer是继承自QTcpServer的类。

该类需要至少重载实现void incomingConnection(qintptr handle)这个函数。

下面是我的.h头文件

class MyTcpServer  : public QTcpServer
{
	Q_OBJECT

public:
	explicit MyTcpServer(QObject* parent = nullptr);
	void incomingConnection(qintptr handle);    // 重载实现该函数进行数据接收读取
	~MyTcpServer();

signals:
    void clientDisconnected(qintptr handle);

protected:
    QHash<QString, bool> userOnlineHash;	//在线
	QHash<qintptr, QThread*> socketThreadHash;	//套接字对应线程
    QHash<QThread*, QString> userThreadHash;    //线程用户
    QHash<QThread*, SqlBusiness*> threadSqlHash;	//每个线程对应sql实例

private:
	USERTYPE m_userType;
    QReadWriteLock dbLock;
};

在我的incomingConnection函数中,即在接收到有连接时,我会创建一个工作类SqlBusiness,该类继承自QObject来实现对QT元对象的支持。为了实现多线程运行多个用户连接,我使用了QT的moveToThread()函数,通过new一个QThread对象和一个SqlBusiness工作对象

将工作对象移入刚创建的新线程中。 

pSqlBusiness->moveToThread(thread);

下面两句话保存句柄

socketThreadHash[handle] = thread;
threadSqlHash[thread] = pSqlBusiness;

再连接槽函数

connect(pSqlBusiness, &SqlBusiness::start, pSqlBusiness, &SqlBusiness::mainBusiness); 

因为涉及跨线程,我们需要使用下面这句话进行自定义类型进行注册。

qRegisterMetaType<qintptr>("qintptr"); 

最后进行启动

thread->start();
emit pSqlBusiness->start(handle); 

该处完整代码为

void MyTcpServer::incomingConnection(qintptr handle)
{
    qDebug() << "已接收到连接";
    QThread* thread = new QThread(this);
    SqlBusiness* pSqlBusiness = new SqlBusiness();
    pSqlBusiness->moveToThread(thread);
    socketThreadHash[handle] = thread;
    threadSqlHash[thread] = pSqlBusiness;
    connect(pSqlBusiness, &SqlBusiness::start, pSqlBusiness, &SqlBusiness::mainBusiness);
    connect(pSqlBusiness, &SqlBusiness::onlineSiganl, this, &MyTcpServer::onlineHandle);
    qRegisterMetaType<qintptr>("qintptr");
    thread->start();
    emit pSqlBusiness->start(handle);
}

 跟随启动后的脚本,会来到SqlBusiness::mainBusiness()函数,在这里我们需要做的就是创建继承自QTcpSocket的自定义MyTcpSocket对象,这里直接贴代码:

void SqlBusiness::mainBusiness(qintptr handle) {
    MyTcpSocket* tcpSocket = new MyTcpSocket(this, handle);
    connect(tcpSocket, &MyTcpSocket::loginSiganl, this, &SqlBusiness::socketLoginHandle);

    connect(this, &SqlBusiness::sendSiganl, tcpSocket, &MyTcpSocket::sendHandle);	//发送函数
	qDebug() << "进入专属线程";
	tcpSocket->setSocketDescriptor(handle);
}

而在MyTcpSocket中基本没啥说的

class MyTcpSocket : public QTcpSocket
{
	Q_OBJECT

public:
    explicit MyTcpSocket(QObject* parent = nullptr, qintptr handle = NULL);
    ~MyTcpSocket();
protected:
	qintptr handle;
;
protected slots:
	void readyReadHandle();
	void disconnectedHandle();

public slots:
    void sendHandle(QByteArray toSend);	//发送
MyTcpSocket::MyTcpSocket(QObject *parent, qintptr handle)
	: QTcpSocket(parent)
{
	this->handle = handle;    // 绑定句柄
    qDebug() << "传入socket" ;
    connect(this, &MyTcpSocket::readyRead, this, &MyTcpSocket::readyReadHandle);    // 读取
    connect(this, &MyTcpSocket::disconnected, this, &MyTcpSocket::disconnectedHandle);// 断开连接
}

void MyTcpSocket::sendHandle(QByteArray toSend)
{
    qDebug() <<"发送数据"  << toSend;
    this->write(toSend);
    this->waitForBytesWritten(-1);  //阻塞发送
}

上面创建、接收部分算差不多了,剩下就是业务扩展,发送部分需要我们根据保存的句柄进行发送:

void MyTcpServer::loginHandle(QString usertype, QString username, QString password, qintptr handle)
{
    qDebug() << "进入登陆槽函数";
    DataBase::getConnection();
    //查询数据
    QSqlQuery query;
    QString qs;
    qDebug() << "usertype:" << usertype << "username:" << username << "password:" << password;
    qs = QString("select * from %1 where username = :username and password = :password").arg(usertype);
    query.prepare(qs);
    query.bindValue(":username", username);
    query.bindValue(":password", password);

    QReadLocker locker(&dbLock);
    if (!query.exec()) {
        qDebug() << query.lastError().text();   //输出错误信息
        bool state = false;
        QString userdata = "服务器异常";
        QByteArray dataToSend = MyJSON::loginOrRegisterBackToByteArray(state, userdata);
        emit threadSqlHash[socketThreadHash[handle]]->sendSiganl(dataToSend);
        return;
    }
    //获取查询结果
    if (query.next()) {
        if (!userOnlineHash[username]) {
            userOnlineHash[username] = true;
            QThread* thread = socketThreadHash[handle];
            userThreadHash[thread] = username;
            bool state = true;
            QString userdata = "登录成功";
            if (usertype == "teacher")
                m_userType = TEACHER;
            else if (usertype == "student")
                m_userType = STUDENT;
            qDebug() << "state:" << state << "userdata:" << userdata;
            QByteArray dataToSend = MyJSON::loginOrRegisterBackToByteArray(state, userdata);
            emit threadSqlHash[socketThreadHash[handle]]->sendSiganl(dataToSend);
        }
        else
        {
            bool state = false;
            QString userdata = "登录失败:该用户已在线";
            QByteArray dataToSend = MyJSON::loginOrRegisterBackToByteArray(state, userdata);
            DataBase::quitConnection();
            emit threadSqlHash[socketThreadHash[handle]]->sendSiganl(dataToSend);
        }
    }
    else {

        QString userdata = "登录失败:无效的用户名或密码";
        bool state = false;
        QByteArray dataToSend = MyJSON::loginOrRegisterBackToByteArray(state, userdata);
        DataBase::quitConnection();
        emit threadSqlHash[socketThreadHash[handle]]->sendSiganl(dataToSend);
    }
}

上面这个函数包括对MYSQL的语句查询和对TCP客服端信息的发送。关键就是下面这句

emit threadSqlHash[socketThreadHash[handle]]->sendSiganl(dataToSend);

 在信号发出后,依次经过SqlBusiness到MyTcpSocket,中间可以自己扩展一些。

通过SqlBusiness的中间性连接MyTcpServer和MyTcpSocket

connect(this, &SqlBusiness::sendSiganl, tcpSocket, &MyTcpSocket::sendHandle);   

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬算不酸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值