本系列所有文章可以在这里查看[http://blog.csdn.net/cloud_castle/article/category/2123873](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 <QApplication> #include <QtCore> #include <stdlib.h> // 使用随机函数包含的头文件 #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 <QStringList> #include <QTcpServer> //! [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就好了。 socketDescription是套接字的描述符,作为每个Client端的识别码。 fortuneserver.cpp: ~~~ #include "fortuneserver.h" #include "fortunethread.h" #include <stdlib.h> //! [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 <QThread> #include <QTcpSocket> //! [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 ~~~ fortunethread.cpp: ~~~ #include "fortunethread.h" #include <QtNetwork> //! [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] ~~~ dialog.h: ~~~ #ifndef DIALOG_H #define DIALOG_H #include <QWidget> #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 ~~~ dialog.cpp: ~~~ #include <QtWidgets> #include <QtNetwork> #include <stdlib.h> #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<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(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财富例程相关大概就是这些,回头做个总结。今天太累啦~