期末大作業(yè)是要做一個(gè)網(wǎng)絡(luò)聊天室,于是學(xué)習(xí)了一個(gè)開源的項(xiàng)目,里面能夠?qū)W習(xí)的東西還是蠻多的,在這里做一下總結(jié)。
項(xiàng)目地址
QT環(huán)境配置
嵌入式項(xiàng)目,要求程序可以在Linux上面可以跑,選擇使用Qt,C++畢竟是面向?qū)ο蟮木幊陶Z言,用來封裝方法還是很方便的。
系統(tǒng)使用的是deepin,深度商店還是比較方便的直接下載Qt Creator,環(huán)境配置比GTK+省略了不少東西。
項(xiàng)目.pro中添加support
# 對網(wǎng)絡(luò)請求必須加上network
QT += core gui network
QTcpServer用法總結(jié)
頭函數(shù)需要添加庫文件
#include <QTcpSocket>
#include <QTcpServer>
槽函數(shù)也是自己定義,連接和斷開時(shí)候的事件。
這里是傳入線程指針,server端接受到client連接請求時(shí)候的socket描述符,會為對應(yīng)的描述符創(chuàng)建對應(yīng)線程去處理這個(gè)請求。
void on_client_connected(ClientThread* clientThread);
void on_client_disconnected(ClientThread* clientThread);
頭文件中聲明
protected:
//重新實(shí)現(xiàn)該方法用來接受client,并創(chuàng)建線程處理該連接
void incomingConnection(qintptr socketDescriptor) override;
該方法在代碼中實(shí)現(xiàn),為
void Server::incomingConnection(qintptr socketDescriptor){
qDebug() << socketDescriptor << " 連接中...";
QTcpSocket *socket = new QTcpSocket();
ClientThread *cliThread = new ClientThread(socketDescriptor,socket,this);
//為連接進(jìn)來的線程綁定事件
connect(cliThread, SIGNAL(finished()),cliThread,SLOT(deleteLater()));
connect(cliThread, SIGNAL(connected(ClientThread*)),this,SIGNAL(connected(ClientThread*)));
connect(cliThread, SIGNAL(connected(ClientThread*)),this,SLOT(on_client_connected(ClientThread*)));
·
·
·
connect(cliThread, SIGNAL(clientDisconnected(ClientThread*)),this,SIGNAL(clientDisconnected(ClientThread*)));
//將socket轉(zhuǎn)移到線程處理
socket->moveToThread(cliThread);
cliThread->start(); //啟用線程
}
服務(wù)端開啟監(jiān)聽,等待連接
使用QTcpServer的異步模式,需要覆蓋其中的一個(gè)listen函數(shù),在調(diào)用listen()相當(dāng)于開啟一個(gè)循環(huán)(不會調(diào)用waitForNewConnection()一種阻塞方法或者叫做同步),關(guān)鍵代碼。
//默認(rèn)使用host即“127.0.0.1”
//調(diào)用listen函數(shù),在指定的端口號中進(jìn)行任意監(jiān)聽 ps:Any參數(shù)表示任意IPv4地址0.0.0.0
if(!this->listen(QHostAddress::Any,port)){
qDebug() << "無法啟動服務(wù)端!";//log
return false;
}
else{
qDebug() << "server 監(jiān)聽中...";//log
return true;
}
該方法需要設(shè)置ip以及端口號,使用函數(shù)獲取或者手動輸入也可以。
客戶端連接到服務(wù)端
關(guān)鍵代碼,試圖連接到主機(jī)host的指定端口 并立即return。
//獲取地址
QHostAddress servAddr(serverIP);
·
·
//設(shè)置ip和端口號
connectToHost(servAddr,this->port);
waitForConnected(30000); //設(shè)置超時(shí)時(shí)間為30秒.
QTcpSocket數(shù)據(jù)傳送
qt中的該類提供了一個(gè)tcp套接字,面向傳輸,面向連接的可靠的傳輸協(xié)議。
/** 將消息發(fā)送給所有線程*/
void Server::sendTextToAll(QString text,ClientThread* except){
//用于暫存要發(fā)送的數(shù)據(jù)
QByteArray block;
//使用數(shù)據(jù)流寫入數(shù)據(jù) 只寫(設(shè)置為ReadWrite則為讀寫)
QDataStream out(&block,QIODevice::WriteOnly);
//設(shè)置數(shù)據(jù)流的版本,客戶端server與服務(wù)端Client版本需要相同
out.setVersion(QDataStream::Qt_5_8);
//設(shè)置初始值為0,設(shè)置長度
out << (quint32)0 << text;
//回到字節(jié)流的起始位置
out.device()->seek(0);
//重置字節(jié)流長度
out << (quint32)(block.size() - sizeof(quint32));
qDebug() << "block.size() = " << block.size();//調(diào)試用(打印出block的長度)
qint64 x = 0;
//遍歷線程列表
foreach(ClientThread* eachClient,clientThreadList){
if(except != NULL && eachClient == except)
continue;
x = 0;
while(x < block.size()){
//向套接字緩存中寫入數(shù)據(jù)(主要)
qint64 y = eachClient->getTcpSocket()->write(block);
x+=y;
//緩沖池在首次連接的時(shí)候沒有數(shù)據(jù),在首次連接成功時(shí)候打印出發(fā)送者
qDebug() << eachClient->getUsername()<< "/sent" << x ;//調(diào)試用(輸出發(fā)送者)
}
qDebug() << "-----";
}
}
有關(guān)正則消息驗(yàn)證
client 與 server 兩端是分離的,可以開啟多個(gè)client端,每一端發(fā)送的數(shù)據(jù)都要經(jīng)過server的接收處理,分組轉(zhuǎn)發(fā)。所以這里的思路是,所有向server端發(fā)送數(shù)據(jù)時(shí)候,對應(yīng)的數(shù)據(jù)類型在Qstring字符串上進(jìn)行處理(比如前面加上特定的字符:/pm就代表一條私聊消息),之后server在經(jīng)過正則驗(yàn)證,不同的數(shù)據(jù)觸發(fā)不同的信號。
總結(jié)一下capture(QRegularExpression類使用正則表達(dá)式提供模式匹配)
//私法消息的格式
QString psersonal="/pm:"+currentTime+myUsername+text
//私人消息的驗(yàn)證
QRegularExpression regex_private("^/pm:(.*)/(.*) : (.*)\n$");
·
·
//
QString senderName = match.captured(2);
QString time = match.captured(1);
QString text = match.captured(3);
分別提取對應(yīng)括號中的文本信息。