赞
踩
感谢这位博主的思路
源码直接点这里
qt的tcp服务器有两个socket一个是监听套接字(QTcpServer),一个是通信套接字(QTcpSocket),因为我只需要TCP服务器端线程的实现就将该部分移植整理了一下,因为我所用到就是在子线程里面进行socket的连接所只针对我移植后的代码进行梳理,当然博主的思路已经完美实现了TCP的多线程
由线程ID可见Socket客户端都是在子线程中进行连接
在客户端断开两个连接
服务器端断开后重新进行监听,客户端连接后子线程重新创建
主线程(服务器端)断开连接
界面没整很复杂就需要一个监听和停止监听,显示连接断开信息
就从按键开始说吧,清除按键就是对接收断开信息进行清除
void MainWindow::on_textClear_clicked()
{
ui->TcpMessage->clear();
}
主要看一下监听/停止按键干了什么
void MainWindow::on_TcpConnect_clicked() { if(this->m_tcpServer == nullptr) { m_tcpServer = new MyServer(this); //启动线程 m_tcpServer->MyThreadStart(); //监听 bool islisten = m_tcpServer->listen(QHostAddress(ip), port); if(!islisten) { QMessageBox::warning(this,"错误",m_tcpServer->errorString()); m_tcpServer->close(); m_tcpServer->deleteLater();//释放 m_tcpServer=nullptr; return; } ui->TcpMessage->append("开始监听"+ip+" "+QString::number(port));//消息框提示信息 ui->TcpConnect->setText("停止"); } else { m_tcpServer->close(); delete m_tcpServer; m_tcpServer=nullptr; ui->TcpMessage->append("停止监听"+ip+" "+QString::number(port));//消息框提示信息 ui->TcpConnect->setText("监听"); } }
先说一下MyServer是继承与QTcpServer的一个类,重写MyServer里incomingConnection这个函数在监听后,有socket进行连接时会自动进入该函数,而单线程有客户端连接的时候是通过newConnection信号,具体自行查阅
class MyServer : public QTcpServer { Q_OBJECT public: explicit MyServer(QObject *parent = nullptr); ~MyServer(); void MyThreadStart(); SocketHelper* sockethelper;//socket创建辅助对象 QList<MyThread*> list_thread;//线程列表 QList<SocketInformation> list_information;//socket信息列表 MainWindow *mainwindow; public slots: void AddInf(MySocket* mysocket,int index);//添加信息 void RemoveInf(MySocket* mysocket);//移除信息 private: void incomingConnection(qintptr socketDescriptor);//重写这个函数,有客户端连接会自动调用该函数 };
其中list_thread在我的程序中其实可以不用列表,因为我只有一个子线程,而博主有多个子线程,list_information里面保存了所有socket客户端的连接,包括主线程和子线程
回到监听按键代码
m_tcpServer = new MyServer(this);
在构造MyServer时候指明父对象也就是mainwindow
MyServer::MyServer(QObject *parent) :
mainwindow(static_cast<MainWindow*>(parent))
{
//在线程内创建对象,槽函数在这个线程中执行
this->sockethelper=new SocketHelper(this);
//注册信号类型
qRegisterMetaType<qintptr>("qintptr");
//主线程信号和槽
connect(sockethelper,&SocketHelper::Create,sockethelper,&SocketHelper::CreateSocket);
connect(sockethelper,&SocketHelper::AddList,this,&MyServer::AddInf);
connect(sockethelper,&SocketHelper::RemoveList,this,&MyServer::RemoveInf);
}
在MyServer类的定义中看见有一个mainwindow指针,它指向的是主界面的mainwindow,也就是main中的w,这里面还包含一个类SocketHelper,先看一下声明
//Socket创建辅助类
class SocketHelper:public QObject
{
Q_OBJECT
public:
explicit SocketHelper(QObject *parent);
MyServer* myserver;
public slots:
void CreateSocket(qintptr socketDescriptor,int index);//创建socket
signals:
void Create(qintptr socketDescriptor,int index);//创建
void AddList(MySocket* tcpsocket,int index);//添加信息
void RemoveList(MySocket* tcpsocket);//移除信息
};
博主称该类为socket辅助类,其功能就是对socket进行创建,添加,移除,在此类中有一个MyServer指针,再来看看它的构造函数
SocketHelper::SocketHelper(QObject *parent):
myserver(static_cast<MyServer*>(parent))
{
}
myserver(static_cast<MyServer*>(parent))这句话也就是说声明时myserver就是父对象,父对象是谁回到myserver.cpp中看MyServer构造函数this->sockethelper=new SocketHelper(this);,也就是说传入的是当前构造的MyServer,在声明时候构造的,再回到mianwindow.cpp,m_tcpServer = new MyServer(this);,也就是说SocketHelper的myserver指针指向的是监听按键中new出来的MyServer,而MyServer继承自QTcpServer,也就是监听套接字
继续回到MyServer中,只剩下以下代码
//在线程内创建对象,槽函数在这个线程中执行
this->sockethelper=new SocketHelper(this);
//注册信号类型
qRegisterMetaType<qintptr>("qintptr");
//主线程信号和槽
connect(sockethelper,&SocketHelper::Create,sockethelper,&SocketHelper::CreateSocket);
connect(sockethelper,&SocketHelper::AddList,this,&MyServer::AddInf);
connect(sockethelper,&SocketHelper::RemoveList,this,&MyServer::RemoveInf);
为什么需要注册qintptr,因为在多线程的socket信号槽传递中是通过qintptr类型(有点牵强,自行百度…),下面就是信号和槽的连接了,包含socket的创建、添加、移除,上面的this就表示以下三个信号都是主线程中进行,后面会有解释
回到监听按键的代码
m_tcpServer->MyThreadStart();//启动线程
void MyServer::MyThreadStart()//启动线程
{
list_thread.append(new MyThread(this));
list_thread[0]->start();
}
qt线程就是写一个类继承自QThread函数,重写run函数,通过start启动run函数,先看看MyThread声明和构造函数
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent);
~MyThread() override;
public:
MyServer* myserver;
SocketHelper* sockethelper;
void run() override;
};
MyThread::MyThread(QObject *parent):
myserver(static_cast<MyServer*>(parent)),
sockethelper(nullptr)
{
}
也就是说线程里的myserver指针也是指向监听按键中new出来的MyServer,也就是主线程的监听套接字,再来看看run函数
void MyThread::run()
{
//在线程内创建对象,槽函数在这个线程中执行
this->sockethelper=new SocketHelper(this->myserver);
connect(sockethelper,&SocketHelper::Create,sockethelper,&SocketHelper::CreateSocket);
connect(sockethelper,&SocketHelper::AddList,myserver,&MyServer::AddInf);
connect(sockethelper,&SocketHelper::RemoveList,myserver,&MyServer::RemoveInf);
exec();
}
这里面重新定义了一个SocketHelper,但是其父对象还是指向监听按键中new出来的MyServer,但是其槽函数是在子线程中注册的
回到监听按键代码,监听部分就剩以下
bool islisten = m_tcpServer->listen(QHostAddress(ip), port);
if(!islisten)
{
QMessageBox::warning(this,"错误",m_tcpServer->errorString());
m_tcpServer->close();
m_tcpServer->deleteLater();//释放
m_tcpServer=nullptr;
return;
}
ui->TcpMessage->append("开始监听"+ip+" "+QString::number(port));//消息框提示信息
ui->TcpConnect->setText("停止");
就是开始监听,当有客户端连接后,会自动进入到incomingConnection函数中,看看连接后incomingConnection函数做了什么
void MyServer::incomingConnection(qintptr socketDescriptor)
{
if(list_thread.count() != 0)//启动了子线程
{
emit list_thread[0]->sockethelper->Create(socketDescriptor,1);//在子线程中创建连接
}
else //只有主线程
{
emit sockethelper->Create(socketDescriptor,0);
}
}
这里面就是通过对线程列表进行判断,如果没有启动子线程,那么就发射emit sockethelper->Create(socketDescriptor,0);,这里对应的槽就是主线程中的创建,记得上面提到在MyServer构造时对应的信号和槽在主线程中而emit list_thread[0]->sockethelper->Create(socketDescriptor,1);对应的信号和槽是在run函数中,看看创建连接信号里面干嘛了
void SocketHelper::CreateSocket(qintptr socketDescriptor ,int index) { qDebug()<<"subThread:"<<QThread::currentThreadId(); MySocket* tcpsocket = new MySocket(this->myserver); tcpsocket->sockethelper = this; //初始化socket tcpsocket->setSocketDescriptor(socketDescriptor);//设置本 socket 唯一标识符 //发送到UI记录信息 emit AddList(tcpsocket,index); if(index == 1)//在子线程中 { //关联释放socket,非UI线程需要阻塞 connect(tcpsocket , &MySocket::DeleteSocket , tcpsocket, &MySocket::deal_disconnect,Qt::ConnectionType::BlockingQueuedConnection); } else { connect(tcpsocket , &MySocket::DeleteSocket , tcpsocket, &MySocket::deal_disconnect,Qt::ConnectionType::AutoConnection); } //关联显示消息 connect(tcpsocket,&MySocket::AddMessage,myserver->mainwindow,&MainWindow::on_addServerMessage); //发送消息 connect(tcpsocket,&MySocket::WriteMessage,tcpsocket,&MySocket::deal_write); //关联接收数据 connect(tcpsocket , &MySocket::readyRead , tcpsocket , &MySocket::deal_readyRead); //关联断开连接时的处理槽 connect(tcpsocket , &MySocket::disconnected , tcpsocket, &MySocket::deal_disconnect); QString ip = tcpsocket->peerAddress().toString(); quint16 port = tcpsocket->peerPort(); QString message = QString("[%1:%2] 已连接").arg(ip).arg(port); //发送到UI线程显示 emit tcpsocket->AddMessage(message); }
socketDescriptor 就是该客户端的唯一标识,MySocket是继承自QTcpSocket,其声明和构造函数如下
class MySocket : public QTcpSocket { Q_OBJECT public: explicit MySocket(QObject *parent = nullptr); ~MySocket(); MyServer* m_tcpServer; SocketHelper* sockethelper; signals: void AddMessage(QString data);//发送给UI显示 void WriteMessage(QByteArray ba);//UI发送过来数据 void DeleteSocket();//主动关闭socket public slots: void deal_readyRead();//读取数据槽函数 void deal_disconnect();//断开连接槽函数 void deal_write(QByteArray ba);//写入数据槽函数 };
MySocket::MySocket(QObject *parent):
m_tcpServer(static_cast<MyServer*>(parent))
{
}
回到创建连接的函数就可以知道new MySocket(this->myserver);这个父对象也是主线程中监听按键new出来的MyServer
, 继续emit AddList(tcpsocket,index);
//添加socket信息
void MyServer::AddInf(MySocket* mysocket,int index)
{
SocketInformation inf;
QString ip = mysocket->peerAddress().toString();
quint16 port = mysocket->peerPort();
QString str_inf = QString("[%1:%2]").arg(ip).arg(port);
inf.str_inf=str_inf;
inf.mysocket=mysocket;
inf.threadIndex=index;
this->list_information.append(inf);
}
这里将CreateSocket函数中开始new出来的MySocket进行了保存,保存进list_information,回到CreateSocket函数
if(index != 0)//在子线程中
{
//关联释放socket,非UI线程需要阻塞
connect(tcpsocket , &MySocket::DeleteSocket , tcpsocket, &MySocket::deal_disconnect,Qt::ConnectionType::BlockingQueuedConnection);
}
else
{
connect(tcpsocket , &MySocket::DeleteSocket , tcpsocket, &MySocket::deal_disconnect,Qt::ConnectionType::AutoConnection);
}
这里是在服务器这边停止监听时主动断开服务器对客户端socket连接发出的信号
void MySocket::deal_disconnect() { MySocket* tcpsocket=static_cast<MySocket*>(sender()); //断开socket tcpsocket->abort(); //消息提示断开 QString ip = tcpsocket->peerAddress().toString(); quint16 port = tcpsocket->peerPort(); QString message = QString("[%1:%2] 已断开").arg(ip).arg(port); //发送到UI线程显示 emit AddMessage(message); //断开所有信号连接 tcpsocket->disconnect(); //发送到UI线程移除信息 emit this->sockethelper->RemoveList(tcpsocket); //释放 tcpsocket->deleteLater(); }
这就是对断开做相应的处理,继续回到创建socket函数中
//关联显示消息
connect(tcpsocket,&MySocket::AddMessage,myserver->mainwindow,&MainWindow::on_addServerMessage);
//发送消息
connect(tcpsocket,&MySocket::WriteMessage,tcpsocket,&MySocket::deal_write);
//关联接收数据
connect(tcpsocket , &MySocket::readyRead , tcpsocket , &MySocket::deal_readyRead);
//关联断开连接时的处理槽
connect(tcpsocket , &MySocket::disconnected , tcpsocket, &MySocket::deal_disconnect);
QString ip = tcpsocket->peerAddress().toString();
quint16 port = tcpsocket->peerPort();
QString message = QString("[%1:%2] 已连接").arg(ip).arg(port);
//发送到UI线程显示
emit tcpsocket->AddMessage(message);
这些信号和槽函数看注释就能理解了,其中博主的一个槽函数名字定义有点问题报以下提示,于是我就把槽函数名字换为on_addServerMessage
至此监听流程已经完了,回到监听按键代码部分
m_tcpServer->close();
delete m_tcpServer;
m_tcpServer=nullptr;
ui->TcpMessage->append("停止监听"+ip+" "+QString::number(port));//消息框提示信息
ui->TcpConnect->setText("监听");
就是删除线程,然后对socket进行释放,为什么所有的MyServer指针都是指向这里的m_tcpServer,因为在该对象析构时候对所有socket进行删除处理
MyServer::~MyServer() { //释放所有socket while(list_information.count()>0) { emit list_information[0].mysocket->DeleteSocket(); list_information.removeAt(0); } //释放所有线程 while(list_thread.count()>0) { list_thread[0]->quit(); list_thread[0]->wait();//等待退出 list_thread[0]->deleteLater();//释放 list_thread.removeAt(0); } //UI线程里的sockethelper sockethelper->disconnect(); delete this->sockethelper;// }
不管是主线程socket还是子线程socket都会添加到list_information中,因此主动断开时会对所有连接的socket进行释放,下面是对线程等进行释放。该博主的实现很巧妙,毕竟自己功底还不够,哈哈,以上只是个人理解
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。