本系列所有文章可以在这里查看http://blog.csdn.net/cloud_castle/article/category/2123873
上篇博文谈到了QTcpSocket基于线程的同步网络客户端的实现,主要基于Waitfor...()函数。如果用多线程做服务器怎么做呢,不要紧,官网依然有Demo~
这个例子共有7个文件(不算pro,如果例子的pro文件很特别我会挑出来讨论下),部分实现与Fortune Server相同,就不重复说了。
主要是多实现了一个FortuneServer类继承自QTcpServer,为什么要写这样一个类呢?因为服务器要响应多个Client的请求,
你不能把所有Client请求的数据都写到Dialog里面吧,一是使主界面只关注GUI的实现,二也是为了复用性嘛~
例子介绍:
The Threaded Fortune Server example shows how to create a server for a simple network service that uses threads to handle requests from different clients.
main.cpp:
#include
#include
#include // 使用随机函数包含的头文件
#include "dialog.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Dialog dialog;
dialog.show();
qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); // 随机种子初始化
return app.exec();
}
fortuneserver.h:
#ifndef FORTUNESERVER_H
#define FORTUNESERVER_H
#include
#include
//! [0]
class FortuneServer : public QTcpServer
{
Q_OBJECT
public:
FortuneServer(QObject *parent = 0);
protected:
void incomingConnection(qintptr socketDescriptor); // 第(1)点
private:
QStringList fortunes;
};
//! [0]
#endif
第一点,FortuneServer类重新实现了QTcpSocket类的incomingConnection(qintptr socketDescriptor)函数,这是个虚函数,保证了基类指针调用子类方法。qintptr是平台相关的整型,可以理解为在64位系统下 typedef qintptr qint64,在32位系统下typedef qintptr qint32。但是FortuneThread类中socketDescription参数却是int型的,为什么?因为FortuneServer类是由外部调用的,这个参数也是从外部读进来的,因此需要屏蔽系统间的差别。FortuneThread类的socketDescription是由FortuneServer给进来的,同一个平台,直接int就好了。
fortuneserver.cpp:
#include "fortuneserver.h"
#include "fortunethread.h"
#include
//! [0]
FortuneServer::FortuneServer(QObject *parent)
: QTcpServer(parent)
{
fortunes << tr("You've been leading a dog's life. Stay off the furniture.") // 在构造函数中将fortunes初始化
<< 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.");
}
//! [0]
//! [1]
void FortuneServer::incomingConnection(qintptr socketDescriptor) // socketDescriptor作为参数从外部接收,因此用qintptr修饰
{
QString fortune = fortunes.at(qrand() % fortunes.size()); // qrand() % n表示在[0,n-1]取一个随机数
FortuneThread *thread = new FortuneThread(socketDescriptor, fortune, this); // 以此socketDescription初始化FortuneThread
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); // 线程完成后删除自身的信号槽绑定
thread->start(); // 启动线程
}
//! [1]
FortuneServer类就完成了,incomingconnection()是protected的虚函数,不能直接由QTcpSocket的对象调用的~
来看fortunethread线程类是怎么写的。fortunethread.h:
#ifndef FORTUNETHREAD_H
#define FORTUNETHREAD_H
#include
#include
//! [0]
class FortuneThread : public QThread
{
Q_OBJECT
public:
FortuneThread(int socketDescriptor, const QString &fortune, QObject *parent);
void run(); // 继承QThread重写run()几乎是必须的
signals:
void error(QTcpSocket::SocketError socketError); // 自定义信号方便其他线程的特定事件处理
private:
int socketDescriptor;
QString text;
};
//! [0]
#endif
#include "fortunethread.h"
#include
//! [0]
FortuneThread::FortuneThread(int socketDescriptor, const QString &fortune, QObject *parent)
: QThread(parent), socketDescriptor(socketDescriptor), text(fortune) // 利用初始化列表对成员变量赋值
{
}
//! [0]
//! [1]
void FortuneThread::run()
{
QTcpSocket tcpSocket; // run()函数中创建的栈对象保证了可靠的销毁。注意这个变量的依附性,当前线程变量仅在调用它的线程中有效。
//! [1] //! [2]
if (!tcpSocket.setSocketDescriptor(socketDescriptor)) { // 相当于tcpSocket的初始化,参数是为了保证不会为同一个客户端创建多个QTcpSocket对象
emit error(tcpSocket.error());
return;
}
//! [2] //! [3]
QByteArray block; // 参见Fortune Server
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
out << (quint16)0;
out << text;
out.device()->seek(0);
out << (quint16)(block.size() - sizeof(quint16));
//! [3] //! [4]
tcpSocket.write(block);
tcpSocket.disconnectFromHost(); // 这个函数是异步执行的
tcpSocket.waitForDisconnected(); // 注意到这又是个waitFor...()函数,它会阻塞当前线程直到连接断开
}
//! [4]
#ifndef DIALOG_H
#define DIALOG_H
#include
#include "fortuneserver.h"
QT_BEGIN_NAMESPACE
class QLabel;
class QPushButton;
QT_END_NAMESPACE
class Dialog : public QWidget
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
private:
QLabel *statusLabel;
QPushButton *quitButton;
FortuneServer server; // 比较一下,与Blocking Fortune Client里的FortuneThread成员的优势
};
#endif
#include
#include
#include
#include "dialog.h"
#include "fortuneserver.h"
Dialog::Dialog(QWidget *parent)
: QWidget(parent)
{
statusLabel = new QLabel;
statusLabel->setWordWrap(true);
quitButton = new QPushButton(tr("Quit"));
quitButton->setAutoDefault(false);
if (!server.listen()) { // 打开子线程,只用了这一句话
QMessageBox::critical(this, tr("Threaded Fortune Server"),
tr("Unable to start the server: %1.")
.arg(server.errorString()));
close();
return;
}
QString ipAddress;
QList 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(server.serverPort()));
connect(quitButton, SIGNAL(clicked()), this, SLOT(close()));
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addStretch(1);
buttonLayout->addWidget(quitButton);
buttonLayout->addStretch(1);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(statusLabel);
mainLayout->addLayout(buttonLayout);
setLayout(mainLayout);
setWindowTitle(tr("Threaded Fortune Server"));
}
Fortune财富例程相关大概就是这些,回头做个总结。今天太累啦~