两年前初学时做的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);