起因是MySQL在Android上沒有驅動。也就是說,移動端想要訪問遠程數據庫,必須通過一台(或多台)PC進行中轉。
中轉PC作為Server,接受來自移動端Socket訪問數據庫的要求,Server訪問數據庫,取得數據,通過Socket發送給移動端。
Qt寫個C/S其實很簡單,網上各種教程,硬傷:Server!是!單!線!程!
假設有10000個移動端訪問中轉Server,那么如果Server是單線程,那么這10000個移動端是排隊通信,排隊訪問數據庫,肯定完蛋!
所以Server必須使用多線程。
Qt的多線程是個經常讓新手搞錯的東西,很多文章中看起來是多線程,實際上根本就是單線程。
默認的C/S連接方式(acceptConnection)不支持多線程也是硬傷!
於是搞了好久,總算搞定了多線程Server。
①首先寫Server類,派生自QTcpServer, 只要重載 incomingConnection 這個虛函數就行了。
無須像單線程那樣 connect(&server,SIGNAL(newConnection()),this,SLOT(acceptConnection()));
void Server::incomingConnection (qintptr socketDescriptor) { SocketThread *thread = new SocketThread(socketDescriptor,this); Processor *cpu=new Processor(thread->socket); connect(thread->socket,SIGNAL(readyRead()),cpu,SLOT(work())); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); cpu->moveToThread (thread); thread->start(); }
在這個函數里有幾個陌生的玩意。
SocketThread類 派生自QThread,子線程不解釋。
Processor類 派生自 QObject,這個類是重點。
Qt多線程的最大問題在於,除了子線程的run函數是跑在子線程里,線程其它函數(包括信號/槽)都是跑在主線程里。
我們的Server肯定要處理Client的請求,也就是Socket的數據請求,在Qt里,這步被封裝在Socket的readyRead信號里。
就算你在run函數里綁了readyRead信號,最后信號還是會在主線程里觸發。
解決方案是單獨寫個處理類,這里就是Processor類,將子線程moveThread到這個對象中,這樣這個對象的所有函數都是在子線程里執行了,work函數用於Server接受請求以及返回數據庫數據。
這是Qt 4.7之后,官方的推薦寫法,因為N多人的多線程寫的根本就是錯的,官方實在忍不了了。
②再看 SocketThread類

class SocketThread : public QThread { Q_OBJECT public: SocketThread(int socketDescriptor,QObject* parent); int socketDescriptor; QTcpSocket *socket; void run(); }; SocketThread::SocketThread(int id,QObject *parent):QThread() { socketDescriptor=id; socket=new QTcpSocket; } void SocketThread::run () { socket->setSocketDescriptor(socketDescriptor); QThread::run (); }
構造函數不用多說,傳入系統為Server分配的Socket的識別id。
關鍵就是QThread的虛函數run。首先設置Server的Socket識別id。
記得調用 QThread::run (); 否則這個run函數並沒有完全執行。
③再看Processor類

void Processor::work () { //qDebug()<<"當前線程: "<<QThread::currentThreadId ()<<endl; buff=m_socket->readAll (); m_socket->write (buff); //qDebug()<<buff<<endl; }
成員就一個指針m_socket,保存子線程的socket地址。
以及一個QByteArray作為緩沖區buff。
readAll讀取Client的Socket,write寫回Client的Sokcet。
無聊的話可以把注釋拿掉,看看work函數的工作線程是否與主線程不同。
④Client端
void Client::send () { socket->connectToHost(QHostAddress(address->text ()),7399); QString x="2333,要完蛋了 "+QString::number (cnt++); socket->write(x.toStdString ().c_str ()); } void Client::get () { QString data=socket->readAll(); qDebug()<<"接收端:"<<data<<endl; socket->disconnectFromHost (); }
這里使用的策略如下:
每發一個請求,連一次Server,收到Server的回復后,斷開連接,防止占用Sever資源。
get函數作為Client的readyRead的槽函數就行了。