Fortune Server Example
此文为Qt帮助文档的翻译
目的:为网络服务创建服务器
本例可以与Fortune Client
示例或Blocking Fortune Client
示例一起运行。
它使用QTcpServer
接收传入的TCP连接,并使用基于QDataStream
的简单数据传输协议在关闭连接之前将fortune 写入连接的客户端
class Server : public QDialog
{
Q_OBJECT
public:
explicit Server(QWidget *parent = nullptr);
private slots:
void sendFortune();
private:
void initServer();
QLabel *statusLabel = nullptr;
QTcpServer *tcpServer = nullptr;
QVector<QString> fortunes;
};
服务器是使用一个只有一个插槽的简单类来实现的,用于处理传入的连接。
tcpServer = new QTcpServer(this);
if (!tcpServer->listen()) {
QMessageBox::critical(this, tr("Fortune Server"),
tr("Unable to start the server: %1.")
.arg(tcpServer->errorString()));
close();
return;
}
QString ipAddress;
QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
// use the first non-localhost IPv4 address
for (int i = 0; i < ipAddressesList.size(); ++i) {
if (ipAddressesList.at(i) != QHostAddress::LocalHost &&
ipAddressesList.at(i).toIPv4Address()) {
ipAddress = ipAddressesList.at(i).toString();
break;
}
}
// if we did not find one, use IPv4 localhost
if (ipAddress.isEmpty())
ipAddress = QHostAddress(QHostAddress::LocalHost).toString();
statusLabel->setText(tr("The server is running on\n\nIP: %1\nport: %2\n\n"
"Run the Fortune Client example now.")
.arg(ipAddress).arg(tcpServer->serverPort()));
在其构造函数中,我们的服务器对象调用QTcpServer::listen()
来设置一个QTcpServer来监听任意端口上的所有地址。In然后显示标签中选择的端口QTcpServer,以便用户知道fortune客户机应该连接到哪个端口。
fortunes << tr("You've been leading a dog's life. Stay off the furniture.")
<< tr("You've got to think about tomorrow.")
<< tr("You will be surprised by a loud noise.")
<< tr("You will feel hungry again in another hour.")
<< tr("You might have mail.")
<< tr("You cannot kill time without injuring eternity.")
<< tr("Computers are not intelligent. They only think they are.");
我们的服务器生成一个随机财富列表,它可以发送到连接的客户端。
connect(tcpServer, &QTcpServer::newConnection, this, &Server::sendFortune);
当客户端连接到我们的服务器时,QTcpServer将发出QTcpServer::newConnection()。反过来,这将调用我们的sendFortune()槽:
void Server::sendFortune()
{
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_10);
out << fortunes[QRandomGenerator::global()->bounded(fortunes.size())];
这个槽的目的是从运气列表中随机选择一行,使用QDataStream将其编码为QByteArray,然后将其写入连接的套接字。这是使用QTcpSocket传输二进制数据的常用方法。首先,我们创建一个QByteArray和一个QDataStream对象,将bytearray传递给QDataStream的构造函数。然后我们显式地设置QDataStream的协议版本为QDataStream::Qt_4_0,以确保我们可以与未来版本的Qt的客户端通信(参见QDataStream::setVersion())。我们继续随机输入财富。
QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
connect(clientConnection, &QAbstractSocket::disconnected,
clientConnection, &QObject::deleteLater);
然后我们调用QTcpServer::nextPendingConnection(),它返回表示连接的服务器端的QTcpSocket。通过将QTcpSocket::disconnected()连接到QObject::deleteLater(),我们可以确保socket在断开连接后被删除。
clientConnection->write(block);
clientConnection->disconnectFromHost();
}
编码后的财富是使用QTcpSocket::write()编写的,最后我们调用QTcpSocket::disconnectFromHost(),它将在QTcpSocket完成将财富写入网络后关闭连接。由于QTcpSocket是异步工作的,数据将在函数返回后写入,控制返回到Qt的事件循环。然后套接字将关闭,这将导致QObject::deleteLater()删除它。
Threaded Fortune Server Example
此文为Qt帮助文档的翻译
线程Fortune服务器示例展示了如何为一个简单的网络服务创建一个服务器,该服务器使用线程来处理来自不同客户机的请求。它将与Fortune Client
范例一起运行。
此示例的实现与Fortune Server example的实现类似,但这里我们将实现QTcpServer的一个子类,它在不同的线程中启动每个连接。
为此,我们需要两个类:一个是QTcpServer子类FortuneServer,另一个是继承QThread的FortuneThread。
class FortuneServer : public QTcpServer
{
Q_OBJECT
public:
FortuneServer(QObject *parent = 0);
protected:
void incomingConnection(qintptr socketDescriptor) override;
private:
QStringList fortunes;
};
FortuneServer继承QTcpServer并重新实现QTcpServer::incomingConnection()。我们也用它来存储随机命运的列表。
FortuneServer::FortuneServer(QObject *parent)
: QTcpServer(parent)
{
fortunes << tr("You've been leading a dog's life. Stay off the furniture.")
<< tr("You've got to think about tomorrow.")
<< tr("You will be surprised by a loud noise.")
<< tr("You will feel hungry again in another hour.")
<< tr("You might have mail.")
<< tr("You cannot kill time without injuring eternity.")
<< tr("Computers are not intelligent. They only think they are.");
}
我们使用FortuneServer的构造函数来生成财富列表。
void FortuneServer::incomingConnection(qintptr socketDescriptor)
{
QString fortune = fortunes.at(QRandomGenerator::global()->bounded(fortunes.size()));
FortuneThread *thread = new FortuneThread(socketDescriptor, fortune, this);
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}
我们的QTcpServer::incomingConnection()实现创建了一个FortuneThread对象,将传入的套接字描述符和随机的fortune传递给FortuneThread的构造函数。通过将FortuneThread的finished()信号连接到QObject::deleteLater(),我们确保线程一旦完成就会被删除。然后我们可以调用QThread::start(),它启动线程。
class FortuneThread : public QThread
{
Q_OBJECT
public:
FortuneThread(int socketDescriptor, const QString &fortune, QObject *parent);
void run() override;
signals:
void error(QTcpSocket::SocketError socketError);
private:
int socketDescriptor;
QString text;
};
继续看FortuneThread类,这是一个QThread子类,它的工作是将fortune写到连接的套接字中。这个类重新实现了QThread::run(),并且它有一个报告错误的信号。
FortuneThread::FortuneThread(int socketDescriptor, const QString &fortune, QObject *parent)
: QThread(parent), socketDescriptor(socketDescriptor), text(fortune)
{
}
FortuneThread的构造函数只是存储套接字描述符和fortune文本,以便稍后可以使用run()。
void FortuneThread::run()
{
QTcpSocket tcpSocket;
run()函数所做的第一件事是在堆栈上创建一个QTcpSocket对象。值得注意的是,我们是在线程中创建这个对象的,它会自动将套接字与线程的事件循环关联起来。这确保了当我们从fortunthread::run()访问事件时,Qt不会试图从主线程将事件传递给套接字。
if (!tcpSocket.setSocketDescriptor(socketDescriptor)) {
emit error(tcpSocket.error());
return;
}
通过调用QTcpSocket::setSocketDescriptor(),将套接字描述符作为参数传递,从而初始化套接字。我们希望这能成功,但为了确保(虽然不太可能,系统可能会耗尽资源),我们捕获返回值并报告任何错误。
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
out << text;
与Fortune服务器示例一样,我们使用QDataStream将Fortune编码到QByteArray中。
tcpSocket.write(block);
tcpSocket.disconnectFromHost();
tcpSocket.waitForDisconnected();
}
但与前面的示例不同的是,我们通过调用QTcpSocket::waitForDisconnected()来结束调用,它阻塞调用线程,直到套接字断开连接。因为我们在一个单独的线程中运行,GUI将保持响应。