教训
之前这篇博文早已写好,不知道是忘记发布还是其他原因,写新博客时默认打开了该博客,但是没有留意直接删除内容开写,写完发布着例子学Qt2后发现跟着例子学Qt少了1~ 额,悲剧…
概述
这个例子的重点告诉我们如何利用QThread来调用系统的同步接口,Qt帮助中也提出千万不要再主线程中干同样的事,因为这将导致主线程卡死。
服务器端fortuneserver
上核心代码
tcpServer = new QTcpServer(this);
if (!tcpServer->listen()) { // 监听随机端口(可通过tcpServer->serverPort()获取),任意ip
QMessageBox::critical(this, tr("Fortune Server"),
tr("Unable to start the server: %1.")
.arg(tcpServer->errorString()));
close();
return;
}
//! [0]
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(); // 通过以上方式查找到本机ip
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()));
剩下的就是响应客户端连接的代码,连接信号和槽connect(tcpServer, &QTcpServer::newConnection, this, &Server::sendFortune);
,处理函数如下:
void Server::sendFortune()
{
//! [5]
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0); // 为了各种版本的兼容最好设置版本号
out << fortunes.at(qrand() % fortunes.size());
//! [4] //! [7]
QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
connect(clientConnection, &QAbstractSocket::disconnected,
clientConnection, &QObject::deleteLater);
//! [7] //! [8]
clientConnection->write(block); // 发送一段文字后随即关闭连接。
clientConnection->disconnectFromHost();
//! [5]
}
客户端blockingfortuneclient
重点在于FortuneThread中,完整分析cpp文件:
FortuneThread::FortuneThread(QObject *parent)
: QThread(parent), quit(false)
{
}
//! [0]
FortuneThread::~FortuneThread()
{
mutex.lock();
quit = true;
cond.wakeOne(); // 主要需要唤醒下当前线程
mutex.unlock();
wait(); // 如果不wait可能导致FortuneThread已经析构,而run还为结束,最后可能在run中由于访问FortuneThread已经析构的成员而导致崩溃等现象。
}
//! [0]
//! [1] //! [2]
void FortuneThread::requestNewFortune(const QString &hostName, quint16 port) // 连接服务器
{
//! [1]
QMutexLocker locker(&mutex); // 注意加锁
this->hostName = hostName;
this->port = port;
//! [3]
if (!isRunning())
start();
else
cond.wakeOne();
}
//! [2] //! [3]
//! [4]
void FortuneThread::run()
{
mutex.lock(); // 由于下面获取hostName和port,这两个参数也可能在主线程改变(requestNewFortune)故需要加锁
//! [4] //! [5]
QString serverName = hostName;
quint16 serverPort = port;
mutex.unlock();
//! [5]
//! [6]
while (!quit) {
//! [7]
const int Timeout = 5 * 1000;
QTcpSocket socket;
socket.connectToHost(serverName, serverPort);
//! [6] //! [8]
if (!socket.waitForConnected(Timeout)) { // 阻塞等待连接成功
emit error(socket.error(), socket.errorString());
return;
}
//! [8] //! [11]
QDataStream in(&socket);
in.setVersion(QDataStream::Qt_4_0); // 同样设置了版本号
QString fortune;
//! [11] //! [12]
do {
if (!socket.waitForReadyRead(Timeout)) {
emit error(socket.error(), socket.errorString());
return;
}
in.startTransaction();
in >> fortune;
} while (!in.commitTransaction()); // 这里通过commitTransaction可以判断发送是否发生异常,这个值得借鉴
//! [12] //! [15]
mutex.lock();
emit newFortune(fortune);
//! [7]
cond.wait(&mutex); // 接收一次消息后阻塞等待条件
serverName = hostName; // 下次仍然重设hostName及port
serverPort = port;
mutex.unlock();
}
//! [15]
}
这里通过QThread代表了连接QTcpSocket等的连接,均采用同步方式,可以比较清晰地知道当前QTcpSocket的状态,比较容易管理。后面分析异步方式使用时将进行对比~