源码: 点击此处
一 多线程应用
- 实现一个多线程的网络时间服务器,利用多线程功能的技术,为每个客户端返回当前的时间,并且在返回后自动退出。同时,服务器也会记录当前受到的请求次数。
- 其实这相当于一个ntp时间服务器
二 服务器实现
2.1 创建服务器UI
- 首先我们值得注意的是,这些new出来的对象都没有进行释放,是因为对于qt来说他的布局管理器会接管该布局上的控件或布局的所有权,并且我们在vLayout布局上设置了父类指针this,此时当这个类被释放时,vLayout和其上面的控件都会被释放。
#include <QDialog>
class QLabel;
class QPushButton;
class MainWindow : public QDialog
{
Q_OBJECT
public:
MainWindow(QDialog *parent = nullptr);
~MainWindow();
private:
QLabel *m_label1; // 显示监听端口
QLabel *m_label2; // 显示请求次数
QPushButton *m_btn; // 退出按钮
};
MainWindow::MainWindow(QDialog *parent)
: QDialog(parent)
{
setWindowTitle("多线程时间服务器");
m_label1 = new QLabel(tr("服务器端口"));
m_label2 = new QLabel;
m_btn = new QPushButton(tr("退出"));
QHBoxLayout *hLayout = new QHBoxLayout;
hLayout->addStretch(1);
hLayout->addWidget(m_btn);
hLayout->addStretch(1);
QVBoxLayout *vLayout = new QVBoxLayout(this);
vLayout->addWidget(m_label1);
vLayout->addWidget(m_label2);
vLayout->addLayout(hLayout);
connect(m_btn,&QPushButton::clicked,this,&MainWindow::close);
m_count = 0;
m_server = new CTcperver(this);
if(!m_server->listen())
{
QMessageBox::critical(this,tr("多线程时间服务器"),tr("无法启动服务器: %1").arg(m_server->errorString()));
close();
return;
}
m_label1->setText(tr("服务器端口: %1").arg(m_server->serverPort()));
}
2.2 创建服务器处理socket
- 首先我们继承QThread并重写run方法,但是要切记的是,这里的构造函数要传入socket描述符。
- tcpSocket.setSocketDescriptor(m_sockerIntptr):用socket描述来构造出一个socket连接。
- 这里使用数据流的方式写入数据,并且设置了数据流版本,这很重要,能够保证写入和读取的版本兼容性,因为当您设置数据流版本时,
QDataStream
会对数据进行特定版本的序列化处理。这意味着在序列化过程中,数据会被按照特定版本的要求进行格式化。
#include <QThread>
#include <QTcpSocket>
class CTimeThread : public QThread
{
Q_OBJECT
public:
explicit CTimeThread(int socketDescriptor,QObject *parent = nullptr);
protected:
void run() override;
signals:
void error(QTcpSocket::SocketError errStr);
private:
int m_sockerIntptr;
};
CTimeThread::CTimeThread(int socketDescriptor,QObject *parent)
:socketDescriptor(m_sockerIntptr),QObject{parent}
{}
void CTimeThread::run()
{
QTcpSocket tcpSocket;
if(!tcpSocket.setSocketDescriptor(m_sockerIntptr))
{
emit error(tcpSocket.error());`
return;
}
QByteArray block;
QDataStream out(&block,QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_12);
uint time = QDateTime::currentDateTime().toSecsSinceEpoch();
out<<time;
tcpSocket.write(block); // 将获取的当前时间传回客户端
tcpSocket.disconnectFromHost(); // 断开连接
tcpSocket.waitForDisconnected(); // 等待返回
}
2.3 服务器类
- 创建了一个继承自QTcpServer的类,
- 在成员函数中创建了一个之前创建的MainWindow也就是服务器的ui类
- 并且在构造函数中直接用传入的parent给他转换,这里首先要理解,对于C++继承来说,必须构造父类的构造函数才能构造成功,而QTcpServer就是构造父类,而(parent)的作用只是传递当前类的父对象,告诉qt的内存管理机制。本类的父对象是哪个。
- 然后在incoming里面进行的socket的处理,使用socket描述符创建对应的线程来处理。
- 在connect函数中处理了socket的线程的资源释放。
#include <QTcpServer>
class MainWindow;
class CTcperver : public QTcpServer
{
Q_OBJECT
public:
explicit CTcperver(QObject *parent = nullptr);
protected:
void incomingConnection(int sockerDescriptor);
private:
MainWindow *dlg;
};
CTcperver::CTcperver(QObject *parent)
: QTcpServer(parent)
{
dlg = (MainWindow *)parent;
}
void CTcperver::incomingConnection(int sockerDescriptor)
{
CTimeThread *thread = new CTimeThread(sockerDescriptor,0);
connect(thread,&CTimeThread::finished,dlg,&MainWindow::slotShow);
connect(thread,&CTimeThread::finished,thread,&CTimeThread::deleteLater,Qt::DirectConnection);
thread->start();
}
2.4 调用
MainWindow w;
w.show();
三 继承问题
- 对于C++的子类继承父类必须要构造父类的构造函数
- 在qt中使用构造函数比如上面的QTcpServer(parent),其实包含两个作用,第一完成基类的构造函数,而将传入的parent给当前类设置了父对象。