作者:小成Charles
原創作品
轉載請標注原創文章地址:https://blog.csdn.net/weixin_42999453/article/details/112363393
一、引言
最近一直在聯系網路通信的專案,一開始準備使用純C/C++去寫服務器的,雖說這樣效率很高,但是寫起來超級麻煩啊,第一個C/S專案,我就利用Qt的Qnetwork模塊去撰寫了,話說我中學的夢想就是能夠自己做一個社交軟體,最近開啟了名為“輕聊”的專案,今年要考研,也不知道能寫到多少…話不多說,上一下輕聊v1.0.1客戶端的的截圖;
(ps:界面很丑,只是實作了功能,下一步將繼續完善功能和界面)

二、功能實作
目前實作了最基本的功能:
- 登錄服務器
- 退出服務器
- 實時更新當前服務器在線的所有用戶
- 雙擊用戶串列可實作發送訊息給指定用戶
三、設計思路
我對服務器的設計思路毫無頭緒,都是想到什么寫什么,雖然功能都能實作,但是性能低,后期會繼續完善優化,
專案實作分為客戶端和服務端,服務端在linux環境下運行,眾所周知服務器是不帶界面的,客戶端在登錄,退出,發送訊息等等需要和服務器互動的資料統一是通過Json資料包傳送,這樣方便管理和提供介面等等,
服務端收到json資料包訊息后會將json資料傳送給sendServer.h,這個類是專門用來決議json資料包并將決議后的結果傳送給相應的客戶端,這里我就是創建了兩個類,一個送來接收訊息一個用來發送訊息,這樣做的目的是方便代碼管理以及高性能做準備,
客戶端就簡單點,只要對接收到的訊息進行處理分析就可,后面代碼分析會講解,
由于本專案是基于UDP協議的,也就是面向無連接的,這樣收發訊息很方便,但是這樣的話對用戶登錄和下線的處理比較麻煩,在這里雖然也解決了,但是后期還是將會利用TCP協議進行登錄連接,UDP進行收發訊息,據說騰訊QQ就是這種模式,
這里我對我專案的大致邏輯畫了一個圖,方便大家理解!

四、客戶端代碼講解
(1)登錄和退出的代碼塊,
登錄和退出的實作就是點擊系結埠和解除系結,這時候如果你點擊系結埠,你就會給服務器發送一個json資料包,其中鍵“way”對應的值就是“log”,相反對應的值就是“quit”,同時這里點擊接觸系結的同時會把當前在線串列清空
void MainWindow::on_actBind_triggered()
{//開始系結 就是上線
quint16 port=ui->editPort->text().toUInt();
QString serverAddrEdit=ui->serverAddr->text(); //目標IP
QHostAddress serverAddr(serverAddrEdit);
quint16 ServerPort=ui->ServerPort->text().toUInt();//目標port
if (udpSocket->bind(port))//系結埠成功
{
ui->chatPlainTextEdit->appendPlainText("**已成功系結");
ui->chatPlainTextEdit->appendPlainText("**系結埠:"
+QString::number(udpSocket->localPort()));
QJsonDocument document=logOrQuit("log");
QByteArray jsonArry=document.toJson(QJsonDocument::Compact);
udpSocket->writeDatagram(jsonArry,serverAddr,ServerPort);
ui->actBind->setEnabled(false);
ui->actBort->setEnabled(true);
}
else
ui->chatPlainTextEdit->appendPlainText("**系結失敗");
}
void MainWindow::on_actBort_triggered()
{//解除系結 就是下線
QString serverAddrEdit=ui->serverAddr->text(); //目標IP
QHostAddress serverAddr(serverAddrEdit);
quint16 ServerPort=ui->ServerPort->text().toUInt();//目標port
QJsonDocument document=logOrQuit("quit");
QByteArray jsonArry=document.toJson(QJsonDocument::Compact);
udpSocket->writeDatagram(jsonArry,serverAddr,ServerPort);
ui->listWidget->clear();
udpSocket->abort(); //不能解除系結
ui->actBind->setEnabled(true);
ui->actBort->setEnabled(false);
ui->chatPlainTextEdit->appendPlainText("**已解除系結");
}
(2)發送訊息模塊
這里就是將輸入的訊息傳送給sendMsg(QString msg)這個函式,這里將資料封裝成json資料包,并且指定需要發送的客戶端的地址,封裝好后,發送給服務端,
void MainWindow::on_btnSend_clicked()
{//發送訊息
QString msg=ui->msgEdit->text();//發送的訊息內容
sendMsg(msg);
ui->chatPlainTextEdit->appendPlainText("[out] "+msg);
ui->msgEdit->clear();
ui->msgEdit->setFocus();
}
void MainWindow::sendMsg(QString msg)
{
QString serverAddrEdit=ui->serverAddr->text(); //目標IP
QHostAddress serverAddr(serverAddrEdit);
quint16 serverPort=ui->ServerPort->text().toUInt();//目標port
QJsonObject toObject;
toObject.insert("addr",ui->targetAddrEdit->text());
toObject.insert("port",ui->targetPortEdit->text());
toObject.insert("msg",msg);
QJsonObject msgObject;
msgObject.insert("way","sendMsg");
msgObject.insert("to",QJsonValue(toObject));
QJsonDocument msgDocument;
msgDocument.setObject(msgObject);
QByteArray msgJsonArry=msgDocument.toJson(QJsonDocument::Compact);
udpSocket->writeDatagram(msgJsonArry,serverAddr,serverPort); //發出資料報
}
(3)接收訊息的模塊
這里接收到訊息之后就先把資料包發送給getWay()函式,這個函式將會判斷”way“對應的值什么,然后執行相應的函式,
void MainWindow::onSocketReadyRead()
{//接受訊息
while(udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
udpSocket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
//分析json包
QJsonParseError jsonError;
QJsonDocument parseDocument=QJsonDocument::fromJson(datagram.data(),&jsonError);
QJsonObject object=parseDocument.object();
getWay(object);
}
}
void MainWindow::getWay(QJsonObject object)
{
QString way = object.value("way").toString();
if(way=="receiveMsg")
{
getMsg( object);
}
if(way=="updateUsers")
{
updateUsers(object);
}
}
(4)更新用戶在線串列
這里用到的空間是QlistWidge來存放用戶資料串列
這里就是如果收到的資料的way是updateUsers,將會執行如下函式功能,json資料中心傳輸過來的addr和port兩個陣列一一對應,遍歷并獲得資料,創建QlistWidgetItem物件,設定用戶地址和埠;我這里每次都會先清空所用串列再一一重新創建來更新串列,這樣效率很差,不建議使用,后期會優化這一部分,因為客戶端界面沒有開始做就不講究效率問題了,
void MainWindow::updateUsers(QJsonObject object)
{//接收json包分析資料 并實作更新在線用戶串列 if(object.contains("way"))
{
QJsonValue value=object.value("way");
if(value.isString())
{
QString way=value.toString();
qDebug()<<way;
if(way=="updateUsers")
{
ui->listWidget->clear(); if(object.contains("addr")&&object.contains("port"))
{
QJsonValue addrValue=object.value("addr");
QJsonValue portValue=object.value("port");
QJsonArray addrArry=addrValue.toArray();
QJsonArray portArry=portValue.toArray();
for (int i = 0; i < addrArry.size(); ++i) {
QString addrStr=addrArry.at(i).toString();
QString portStr=portArry.at(i).toString();
onlineAddr.append(addrStr);
onlinePort.append(portStr);
QListWidgetItem *item=new QListWidgetItem();
item->setData(i,i);
item->setText("用戶IP:"+addrStr+"用戶埠:"+portStr);
ui->listWidget->addItem(item);
}
}
}
}
}
}
(5)最后就是將訊息添加到聊天框
void MainWindow::getMsg(QJsonObject object)
{
QJsonObject fromObject=object.value("from").toObject();
QString fromAddr=fromObject.value("addr").toString();
QString fromPort=fromObject.value("port").toString();
QString fromMsg=fromObject.value("msg").toString();
QString peer="[From "+fromAddr+":"+fromPort+"] ";
ui->chatPlainTextEdit->appendPlainText(peer+fromMsg);
}
五、服務端代碼講解
(1)接收資料模塊
這里就是接收到訊息之后創建一個SendServer類的物件,這個類主要做資料處理和轉發資料的,創建了兩個connect函式,分別監聽如果有用戶登錄和用戶退出的事件,然后將資料傳出來進行更新在線用戶串列,記住這里最后一定要將這個物件delete掉,不然最后可能會導致記憶體泄漏等問題!
void udpserver::onReadyRead()
{//server to revecive
QByteArray datagram;
datagram.resize(udpServer->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
udpServer->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
qDebug()<<datagram.data();
SendServer *send=new SendServer ();
connect(send,SIGNAL(newUsers(QStringList)),this,SLOT(inserNewUsers(QStringList)));//connect the signals before the emit
connect(send,SIGNAL(quitUsers(QStringList)),this,SLOT(pushQuitUsers(QStringList)));
send->getReceiveData(datagram.data(),datagram.size(),peerAddr,peerPort);
delete send;//free object
}
(2)更新用戶串列模塊
這里有兩個函式,分別是inserNewUsers和pushQuitUsers,代碼邏輯大相徑庭,就是對從SendServer類中監聽到的資料進行判斷操作的槽函式,這里也創建了一個SendServer物件把onlineUserList(存放在線用戶的容器)傳送給了這個類中的函式sendAllCilentsTheUsers(onlineUserList);,它將實作把資料發送給所用客戶端
void udpserver::inserNewUsers(QStringList logUserInfo)
{//update the userList
for (int i=0;i<onlineUserList.length();++i) {
if(onlineUserList.at(i)==logUserInfo)
{
qDebug()<<"the same log users";
SendServer *updateSend=new SendServer ();
updateSend->sendAllCilentsTheUsers(onlineUserList);
delete updateSend;
return;
}
}
onlineUserList.append(logUserInfo);
SendServer *updateSend=new SendServer ();
updateSend->sendAllCilentsTheUsers(onlineUserList);
delete updateSend;
qDebug()<<onlineUserList;
}
void udpserver::pushQuitUsers(QStringList quitUserInfo)
{
for (int i=0;i<onlineUserList.length();++i) {
if(onlineUserList.at(i)==quitUserInfo)
{
qDebug()<<"it can be deleted";
onlineUserList.removeAt(i);
qDebug()<<"removved:"<<onlineUserList;
SendServer *updateSend=new SendServer ();
updateSend->sendAllCilentsTheUsers(onlineUserList);
delete updateSend;
return;
}else {
qDebug()<<"reQuited!";
}
}
}
(3)最后貼出SendServer.h所用的代碼塊
感興趣可以自己看一下,這里getWay函式實作分析way,然后執行對應的函式塊,sendLOgUser和sendQuitUser分別觸發了newUsers和quitUsers這兩個信號,并將資料傳送出去,對應上面講的那兩個connect函式,然后又去執行sendAllCilentsTheUsers實作實時更新用戶串列,如果是執行sendMsgToClients這個函式,就會發送者ip和埠以及訊息打包成json發送給指定的客戶端,
SendServer::SendServer(QObject *parent) : QObject(parent)
{
udpSendServer =new QUdpSocket ();
}
void SendServer::getWay( QJsonDocument parseDocument)
{
QJsonObject object=parseDocument.object();
if(object.contains("way"))
{
QJsonValue value=object.value("way");
if(value.isString())
{
QString way=value.toString();
qDebug()<<way;
if(way=="log")
{
sendLOgUser(object);
}
if(way=="sendMsg")
{
sendMsgToClients(object);
}
if(way=="quit")
{
sendQuitUser(object);
}
}
}
}
void SendServer::getReceiveData(QByteArray data,int size,QHostAddress &peerAddr,quint16 &peerPort)
{
QJsonParseError jsonError;
QJsonDocument parseDocument=QJsonDocument::fromJson(data,&jsonError);
if(!parseDocument.isNull())
{
temtLOgAddr =peerAddr;
temtLogPort = peerPort;
getWay(parseDocument);
}
}
void SendServer::sendLOgUser(QJsonObject object)
{//send the log user to clients
logUserInfo.append(temtLOgAddr.toString());
logUserInfo.append(QString::number(temtLogPort));
qDebug()<<logUserInfo;
emit newUsers(logUserInfo);
udpSendServer>writeDatagram(jsonArry,temtLOgAddr,temtLogPort);
}
void SendServer::sendQuitUser(QJsonObject object)
{//send Quit users
quitUserInfo.append(temtLOgAddr.toString());
quitUserInfo.append(QString::number(temtLogPort));
qDebug()<<quitUserInfo;
emit quitUsers(quitUserInfo);
}
void SendServer::sendAllCilentsTheUsers(QList <QStringList> onlineUserList)
{
bool ok;
QJsonArray addrArry;
QJsonArray portArry;
for (int i = 0;i<onlineUserList.length();i++) {
QString addr=onlineUserList.at(i).at(0);
QString port=onlineUserList.at(i).at(1);
// qDebug()<<"enter 1";
addrArry.append(addr);
portArry.append(port);
}
QJsonObject jsonObject;
jsonObject.insert("way","updateUsers");
jsonObject.insert("addr",QJsonValue(addrArry) );
jsonObject.insert("port",QJsonValue(portArry));
QJsonDocument document;
document.setObject(jsonObject);
QByteArray jsonArry=document.toJson(QJsonDocument::Compact);
//forward the json to all clients
for (int j = 0;j<onlineUserList.length();j++) {
// qDebug()<<"enter 2";
QHostAddress addr(onlineUserList.at(j).at(0));
quint16 port= onlineUserList.at(j).at(1).toUInt();
qDebug()<<addr<<port;
udpSendServer->writeDatagram(jsonArry,addr,port);
}
}
void SendServer::sendMsgToClients(QJsonObject object)
{
QJsonObject toObject=object.value("to").toObject();
QString toAddr=toObject.value("addr").toString();
QString toPort=toObject.value("port").toString();
QString msg=toObject.value("msg").toString();
QHostAddress targetAddr(toAddr);
quint16 targetPort=toPort.toUInt();
QJsonObject fromObject;
fromObject.insert("addr",temtLOgAddr.toString());
fromObject.insert("port",QString::number( temtLogPort));
fromObject.insert("msg",msg);
QJsonObject sendObject;
sendObject.insert("way","receiveMsg");
sendObject.insert("from",QJsonValue(fromObject));
QJsonDocument sendDoucment;
sendDoucment.setObject(sendObject);
QByteArray sendJsonArry=sendDoucment.toJson(QJsonDocument::Compact);
udpSendServer->writeDatagram(sendJsonArry,targetAddr,targetPort);
}
五、總結
沒有總結,接下來繼續更新完善更能,繼續更新博客,立個flag輕聊5.0將達到商用級別!
作者:小成Charles
原創作品
轉載請標注原創文章地址:https://blog.csdn.net/weixin_42999453/article/details/112363393
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/246591.html
標籤:其他
上一篇:Zstack私有云平臺運行實踐
