<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
本文範例為大家分享了QT實現網路聊天室的具體程式碼,供大家參考,具體內容如下
分兩個部分,第一部分是訊息區裡面包含QPlainTextEdit和QListWidget,要顯示接收的訊息和線上的成員。第二部分QLineEdit發生字元。
1.2.1 登入介面
登入介面主要就是要有驗證碼,防止惡意程式的攻擊。通過paintEvent畫出一個白色矩形,在白色矩形裡面顯示四個不同顏色的字母以及隨機出現的噪點。
程式碼:
QLoginDialog.h
#ifndef _QLOGINDIALOG_H_ #define _QLOGINDIALOG_H_ #include <QDialog> #include <QLabel> #include <QLineEdit> #include <QPushButton> #include <QTimer> //繼承自Dialog class QLoginDialog : public QDialog { Q_OBJECT public: typedef bool (*ValFunc)(QString); private: QLabel UserLabel; QLabel PwdLabel; QLabel CaptLabel; QLineEdit UserEdit; QLineEdit PwdEdit; QLineEdit CaptEdit; QPushButton LoginBtn; QPushButton CancelBtn; QString m_user; QString m_pwd; QString m_captcha; Qt::GlobalColor* m_colors; QTimer m_timer; ValFunc m_vf; private slots: void LoginBtn_Clicked(); void CancelBtn_Clicked(); void Timer_Timeout(); protected: void paintEvent(QPaintEvent *); QString getCaptcha(); Qt::GlobalColor* getColors(); void showEvent(QShowEvent *); public: QLoginDialog(QWidget *parent = 0); QString getUser(); QString getPwd(); void setValFunc(ValFunc); ~QLoginDialog(); }; #endif
QLoginDialog.cpp
#include "QLoginDialog.h" #include <QPainter> #include <QTime> #include <QMessageBox> QLoginDialog::QLoginDialog(QWidget* parent) : QDialog(parent, Qt::WindowCloseButtonHint), UserLabel(this), PwdLabel(this), CaptLabel(this), UserEdit(this), PwdEdit(this), CaptEdit(this), LoginBtn(this), CancelBtn(this), m_vf(NULL) { UserLabel.setText("使用者名稱:"); UserLabel.move(20, 30); UserLabel.resize(60, 25); UserEdit.move(85, 30); UserEdit.resize(180, 25); PwdLabel.setText("密 碼:"); PwdLabel.move(20, 65); PwdLabel.resize(60,25); PwdEdit.move(85, 65); PwdEdit.resize(180, 25); PwdEdit.setEchoMode(QLineEdit::Password); CaptLabel.setText("驗證碼:"); CaptLabel.move(20, 100); CaptLabel.resize(60, 25); CaptEdit.move(85, 100); CaptEdit.resize(85, 25); CancelBtn.setText("取消"); CancelBtn.move(85, 145); CancelBtn.resize(85, 30); LoginBtn.setText("登入"); LoginBtn.move(180, 145); LoginBtn.resize(85, 30); m_timer.setParent(this); setWindowTitle("登入..."); setFixedSize(285, 205); connect(&m_timer, SIGNAL(timeout()), this, SLOT(Timer_Timeout())); connect(&LoginBtn, SIGNAL(clicked()), this, SLOT(LoginBtn_Clicked())); connect(&CancelBtn, SIGNAL(clicked()), this, SLOT(CancelBtn_Clicked())); //以時間作為種子,獲取亂數 qsrand(QTime::currentTime().second() * 1000 + QTime::currentTime().msec()); m_timer.start(100); } void QLoginDialog::LoginBtn_Clicked() { //去除空格 QString captcha = CaptEdit.text().replace(" ", ""); //校驗驗證碼 if( m_captcha.toLower() == captcha.toLower() ) { m_user = UserEdit.text().trimmed(); m_pwd = PwdEdit.text(); if( m_user == "" ) { QMessageBox::information(this, "訊息", "使用者名稱不能為空!"); } else if( m_pwd == "" ) { QMessageBox::information(this, "訊息", "密碼不能為空!"); } else if( (m_vf != NULL) && !(m_vf(m_user))) //一些非法字元不可輸入 { QMessageBox::information(this, "訊息", "使用者名稱非法,請重新輸入!"); } else { done(Accepted); } } else { QMessageBox::critical(this, "錯誤", "驗證碼輸入錯誤!"); m_captcha = getCaptcha(); CaptEdit.selectAll(); } } void QLoginDialog::setValFunc(ValFunc vf) { m_vf = vf; } void QLoginDialog::CancelBtn_Clicked() { done(Rejected); } QString QLoginDialog::getUser() { return m_user; } QString QLoginDialog::getPwd() { return m_pwd; } //獲取四個隨機的顏色 Qt::GlobalColor* QLoginDialog::getColors() { static Qt::GlobalColor colors[4]; for(int i=0; i<4; i++) { colors[i] = static_cast<Qt::GlobalColor>(2 + qrand() % 16); } return colors; } void QLoginDialog::Timer_Timeout() { //每100毫秒獲取四種顏色 m_colors = getColors(); //更新畫板 update(); } void QLoginDialog::showEvent(QShowEvent* event) { //每次顯示之前,獲取驗證碼和顏色 m_captcha = getCaptcha(); m_colors = getColors(); QDialog::showEvent(event); } void QLoginDialog::paintEvent(QPaintEvent* event) { QPainter painter(this); //獲取一個矩形 painter.fillRect(180, 100, 84, 24, Qt::white); painter.setFont(QFont("Comic Sans MS", 12)); //填充噪點,150個點隨機顯示 for(int i=0; i<150; i++) { painter.setPen(m_colors[i%4]); painter.drawPoint(180 + qrand() % 84, 100 + qrand() % 24); } //驗證碼四個顏色 for(int i=0; i<4; i++) { painter.setPen(m_colors[i]); painter.drawText(180 + 20 * i, 100, 20, 24, Qt::AlignCenter, QString(m_captcha[i])); } QDialog::paintEvent(event); } QString QLoginDialog::getCaptcha() { QString ret = ""; for(int i=0; i<4; i++) { int c = (qrand() % 2) ? 'a' : 'A'; ret += static_cast<QChar>(c + qrand() % 26); } return ret; } QLoginDialog::~QLoginDialog() { }
1.2.2 協定
1.2.2.1 協定的制訂
使用者端與伺服器端之間的操作需要用到協定,能夠方便解析使用者端需要的操作。
操作型別+資料長度+資料
TextMessage.h
#ifndef TEXTMESSAGE_H #define TEXTMESSAGE_H #include <QObject> #include <QByteArray> class TextMessage : public QObject { Q_OBJECT QString m_type; QString m_data; public: TextMessage(QObject *parent = 0); TextMessage(QString type,QString data,QObject* parent = NULL); QString type(); int length(); QString data(); QByteArray serizlize(); bool unserialize(QByteArray ba); }; #endif // TEXTMESSAGE_H
TextMessage.cpp
#include "TextMessage.h" #include <QString> #include <QDebug> TextMessage::TextMessage(QObject *parent) : QObject(parent) { m_type = ""; m_data = ""; } TextMessage::TextMessage(QString type,QString data,QObject* parent) { m_type = type.trimmed(); if(m_type.length() < 4) { m_type += QString(4-m_type.length(),' '); } m_data = data.mid(0, 15000); } QString TextMessage::type() { return m_type.trimmed(); } int TextMessage::length() { return m_data.length(); } QString TextMessage::data() { return m_data; } //把需要傳送的資料轉換成協定 QByteArray TextMessage::serizlize() { QByteArray ret; QByteArray dba = m_data.toUtf8(); QString len = QString::asprintf("%X",dba.length()); if(len.length() < 4) { len += QString(4-len.length(),' '); } ret.append(m_type.toStdString().c_str(),4); ret.append(len.toStdString().c_str(),4); ret.append(dba); return ret; } //把接收的協定轉換為具體的資料 bool TextMessage::unserialize(QByteArray ba) { bool ret = (ba.length() >= 8); if(ret) { QString type = QString(ba.mid(0,4)); QString len = QString(ba.mid(4,4)).trimmed(); int l = len.toInt(&ret , 16); ret = ret && (l == (ba.length() - 8)); if(ret) { m_type = type; m_data = QString(ba.mid(8)); } } return ret; }
1.2.2.2 協定裝配器
為什麼需要裝配器,原因從伺服器端發來的資料可能是多個操作,可能出現粘包、資料不足一個包等情況,可以使用裝配器來進行資料的裝配。
TxtMsgAssmbler.h
#ifndef TXTMSGASSEMBLER_H #define TXTMSGASSEMBLER_H #include <QObject> #include <QQueue> #include <QSharedPointer> #include "TextMessage.h" class TxtMsgAssembler : public QObject { QQueue<char> m_queue; QString m_type; int m_length; QByteArray m_data; void clear(); QByteArray fetch(int n); bool makeTypeAndLength(); TextMessage* makeMessage(); public: TxtMsgAssembler(QObject *parent = 0); void prepare(const char* data,int len); QSharedPointer<TextMessage> assemble(); QSharedPointer<TextMessage> assemble(const char* data, int len); void reset(); }; #endif // TXTMSGASSEMBLER_H
TxtMsgAssembler.cpp
#include "TxtMsgAssembler.h" #include <QSharedPointer> TxtMsgAssembler::TxtMsgAssembler(QObject *parent) : QObject(parent) { } void TxtMsgAssembler::clear() { m_type = ""; m_data.clear(); m_length = 0; } //把資料從佇列中取出 QByteArray TxtMsgAssembler::fetch(int n) { QByteArray ret; for(int i = 0; i < n;i++) { ret.append(m_queue.dequeue()); } return ret; } //把資料放入佇列中 void TxtMsgAssembler::prepare(const char* data,int len) { if(data != NULL) { for(int i = 0; i < len; i++) { m_queue.enqueue(data[i]); } } } //把資料進行處理,識別出操作型別和獲取資料長度 bool TxtMsgAssembler::makeTypeAndLength() { bool ret = (m_queue.length() >= 8); if(ret) { QString len = ""; m_type = QString(fetch(4)); len = QString(fetch(4)); m_length = len.trimmed().toInt(&ret,16); if(!ret) { clear(); } } return ret; } //獲取資料 TextMessage* TxtMsgAssembler::makeMessage() { TextMessage* ret = NULL; if(m_type != "") { int needed = m_length - m_data.length(); int n = (needed <= m_queue.length()) ? needed : m_queue.length(); m_data.append(fetch(n)); if(m_length == m_data.length()) { ret = new TextMessage(m_type, QString(m_data)); } } return ret; } QSharedPointer<TextMessage> TxtMsgAssembler::assemble(const char* data, int len) { prepare(data, len); return assemble(); } //只要從網路中接收到資料就呼叫該函數 QSharedPointer<TextMessage> TxtMsgAssembler::assemble() { TextMessage* ret = NULL; bool tryMakeMsg = false; if(m_type == "") { tryMakeMsg = makeTypeAndLength(); } else { tryMakeMsg = true; } if(tryMakeMsg) { ret = makeMessage(); } if(ret != NULL) { clear(); } return QSharedPointer<TextMessage>(ret); } void TxtMsgAssembler::reset() { }
1.2.3 TCP使用者端
使用者端使用sokect通訊,要提供read、send、connect、close等介面,還要提供當連線、關閉上伺服器,要傳送給伺服器端一些資訊。
接收到資訊時,要處理伺服器端傳入的資料。
ClientDemo.h
#ifndef CLIENTDEMO_H #define CLIENTDEMO_H #include <QObject> #include <QTcpSocket> #include "TextMessage.h" #include "TxtMsgAssembler.h" #include "txtmsghandler.h" class ClientDemo : public QObject { Q_OBJECT QTcpSocket m_client; TxtMsgAssembler m_assmbler; TxtMsgHandler *m_handler; protected slots: void onConnected(); void onDisconnected(); void onDataReady(); void onBytesWritten(qint64 bytes); public: explicit ClientDemo(QObject *parent = 0); bool connectTo(QString ip, int port); qint64 send(TextMessage& message); qint64 available(); void setHandler(TxtMsgHandler* handler); void close(); bool isValid(); signals: public slots: }; #endif // CLIENTDEMO_H
ClientDemo.cpp
#include "ClientDemo.h" #include <QSharedPointer> #include <QHostAddress> #include <QDebug> #include <QByteArray> ClientDemo::ClientDemo(QObject *parent) : QObject(parent) { //當連線上的時,就會呼叫槽函數onConnected connect(&m_client,SIGNAL(connected()),this,SLOT(onConnected())); //當斷開連線時,就會呼叫onDisconnected connect(&m_client,SIGNAL(disconnected()),this,SLOT(onDisconnected())); //接收到伺服器端傳送來的資料,呼叫prepare把資料儲存到佇列中,呼叫assemble對資料進行解析 //呼叫m_handler->handle處理對應傳送來的操作 connect(&m_client,SIGNAL(readyRead()),this,SLOT(onDataReady())); //傳送成功後,並沒有做什麼 connect(&m_client,SIGNAL(bytesWritten(qint64)),this,SLOT(onBytesWritten(qint64))); } void ClientDemo::onConnected() { if(m_handler != NULL) { TextMessage conn("CONN",m_client.peerAddress().toString() + ":" + QString::number(m_client.peerPort())); m_handler->handle(m_client,conn); } } void ClientDemo::onDisconnected() { m_assmbler.reset(); if(m_handler != NULL) { TextMessage dscn("DSCN",""); m_handler->handle(m_client,dscn); } } void ClientDemo::onDataReady() { char buf[256] = {0}; int len = 0; while((len = m_client.read(buf,sizeof(buf))) > 0 ) { QSharedPointer<TextMessage> ptm; m_assmbler.prepare(buf,len); while( (ptm = m_assmbler.assemble()) != NULL ) { if((m_handler != NULL) ) { //根據具體的type,處理不同的事件。 m_handler->handle(m_client, *ptm); } } } } void ClientDemo::onBytesWritten(qint64 bytes) { (void)bytes; } bool ClientDemo::connectTo(QString ip, int port) { m_client.connectToHost(ip,port); return m_client.waitForConnected(); } qint64 ClientDemo::send(TextMessage& message) { QByteArray ba = message.serizlize(); return m_client.write(ba.data(),ba.length()); } qint64 ClientDemo::available() { return m_client.bytesAvailable(); } void ClientDemo::close() { m_client.close(); } bool ClientDemo::isValid() { return m_client.isValid(); } void ClientDemo::setHandler(TxtMsgHandler* handler) { m_handler = handler; }
1.2.4 使用者端介面
1.在沒有登入的時候,傳送框和傳送按鈕不能使用,只有登入按鈕可以用。
2.管理員可以通過選擇群友,點選右鍵對群友進行許可權操作(禁言、恢復、封禁)。
3.被禁言、恢復、封禁的群友要出現提示。
4.通過選擇群友來進行私聊
5.群友上線或下線時,訊息方塊內要有系統提示和及時重新整理Listwidget
6.對於非法符號,要拒絕註冊
7.當用戶端接收到訊息時,視窗要閃爍
8.按下回車可以傳送訊息
MainWinUI.h
#ifndef MAINWIN_H #define MAINWIN_H #include <QWidget> #include <QVBoxLayout> #include <QGroupBox> #include <QPlainTextEdit> #include <QLineEdit> #include <QPushButton> #include <QLabel> #include <QListWidget> #include "QLoginDialog.h" #include "ClientDemo.h" #include "txtmsghandler.h" #include <QMap> #include <QMenu> class MainWin : public QWidget ,public TxtMsgHandler { Q_OBJECT typedef void(MainWin::*MSGHandler)(QTcpSocket&,TextMessage&); QVBoxLayout vMainLayout; QGroupBox msgGrpBx; QListWidget listWidget; QGroupBox inputGrpBx; QPlainTextEdit msgEditor; QMenu listWidgetMenu; QLineEdit inputEdit; QPushButton logInOutBtn; QPushButton sendBtn; QLabel statusLbl; QLoginDialog loginDlg; QString m_level; ClientDemo m_client; //用鍵值儲存type型別與對應的操作函數 QMap<QString,MSGHandler> m_handlerMap; void initMember(); void initMsgGrpBx(); void initInputGrpBx(); void initListWidgetMenu(); void connectSlots(); void setCtrlEnabled(bool enabled); QString getCheckedUserId(); //對應型別操作的函數 void CONN_Handler(QTcpSocket&,TextMessage&); void DSCN_Handler(QTcpSocket&,TextMessage&); void LIOK_Handler(QTcpSocket&,TextMessage&); void LIER_Handler(QTcpSocket&,TextMessage&); void MSGA_Handler(QTcpSocket&,TextMessage&); void USER_Handler(QTcpSocket&,TextMessage&); void CTRL_Handler(QTcpSocket&,TextMessage&); private slots: void sendBtnClicked(); void logInOutBtnClicked(); void listWidgetMenuClicked(); void listWidgetContextMenu(const QPoint&); //重寫事件過濾器,為了處理確認鍵 bool eventFilter(QObject *, QEvent *); public: MainWin(QWidget *parent = 0); void handle(QTcpSocket& obj,TextMessage& message); ~MainWin(); }; #endif // MAINWIN_H
MainWinUI.cpp
#include "MainWinUI.h" #include <QHBoxLayout> #include <QGridLayout> #include <QAction> MainWin::MainWin(QWidget *parent) : QWidget(parent) , loginDlg(this) { initMember(); initMsgGrpBx(); initInputGrpBx(); initListWidgetMenu(); connectSlots(); vMainLayout.setSpacing(10); vMainLayout.addWidget(&msgGrpBx); vMainLayout.addWidget(&inputGrpBx); setWindowTitle("R1CHIE聊天室"); setLayout(&vMainLayout); setMinimumSize(550,400); resize(550,400); } void MainWin::connectSlots() { connect(&sendBtn,SIGNAL(clicked(bool)),this,SLOT(sendBtnClicked())); connect(&logInOutBtn,SIGNAL(clicked(bool)),this,SLOT(logInOutBtnClicked())); //對群友點選右鍵後出現的選單的槽函數連線 connect(&listWidget,SIGNAL(customContextMenuRequested(QPoint)),this,SLOT(listWidgetContextMenu(QPoint))); } void MainWin::initMsgGrpBx() { QHBoxLayout* hbl = new QHBoxLayout(); hbl->setContentsMargins(2,5,2,2); hbl->addWidget(&msgEditor,7); hbl->addWidget(&listWidget,3); msgEditor.setReadOnly(true); msgEditor.setFocusPolicy(Qt::NoFocus); listWidget.setFocusPolicy(Qt::NoFocus); listWidget.setContextMenuPolicy(Qt::CustomContextMenu); msgGrpBx.setLayout(hbl); msgGrpBx.setTitle("聊天訊息"); } void MainWin::initInputGrpBx() { QGridLayout* gl = new QGridLayout(); gl->setSpacing(10); gl->addWidget(&inputEdit,0,0,1,5); gl->addWidget(&statusLbl,1,0,1,3); gl->addWidget(&logInOutBtn,1,3); gl->addWidget(&sendBtn,1,4); inputEdit.setFixedHeight(23); inputEdit.setEnabled(false); inputEdit.installEventFilter(this); statusLbl.setText("狀態: 未登入"); logInOutBtn.setFixedHeight(30); logInOutBtn.setText("登入"); sendBtn.setFixedHeight(30); sendBtn.setText("傳送"); sendBtn.setEnabled(false); inputGrpBx.setFixedHeight(100); inputGrpBx.setLayout(gl); inputGrpBx.setTitle("使用者名稱"); } //對群友點選右鍵後出現的選單 void MainWin::initListWidgetMenu() { QAction* act = NULL; act = listWidgetMenu.addAction("禁言",this,SLOT(listWidgetMenuClicked())); act->setObjectName("silent"); act = listWidgetMenu.addAction("恢復",this,SLOT(listWidgetMenuClicked())); act->setObjectName("recover"); act = listWidgetMenu.addAction("封禁",this,SLOT(listWidgetMenuClicked())); act->setObjectName("kick"); } void MainWin::setCtrlEnabled(bool enabled) { inputEdit.setEnabled(enabled); statusLbl.setText(enabled ? "狀態: 連線成功" : "狀態: 未登入"); logInOutBtn.setText(enabled ? "退出":"登入"); sendBtn.setEnabled(enabled); if(enabled) { inputEdit.setFocus(); } else { msgEditor.clear(); listWidget.clear(); inputEdit.clear(); } } MainWin::~MainWin() { m_client.close(); }
MainWinSlot.cpp
#include "MainWinUI.h" #include <QMessageBox> #include <QDebug> //當出現以下符號時,認定為非法使用者名稱 static bool ValidateUserID(QString id) { bool ret = true; QString invalid = "~`!@#$%^&*()_+[]:?><,./;"; for(int i = 0; i < invalid.length(); i++) { if(id.contains(invalid[i])) { ret = false; break; } } return ret; } void MainWin::initMember() { #define MapToHandler(MSG) m_handlerMap.insert(#MSG,&MainWin::MSG##_Handler) //把對應type型別的處理常式,用鍵值QMap儲存 MapToHandler(CONN); MapToHandler(DSCN); MapToHandler(LIOK); MapToHandler(LIER); MapToHandler(MSGA); MapToHandler(USER); MapToHandler(CTRL); m_client.setHandler(this); } //獲取listwidget選中群友 QString MainWin::getCheckedUserId() { QString ret = ""; for(int i = 0; i < listWidget.count(); i++) { QListWidgetItem *item = listWidget.item(i); if(item->checkState() == Qt::Checked) { ret += item->text() + 'r'; } } return ret; } void MainWin::sendBtnClicked() { QString input = inputEdit.text().trimmed(); if(input != "") { QString self = inputGrpBx.title(); QString text = self + ":n" + " " + input + "n"; QString uid = getCheckedUserId(); bool ok = true; //如果沒有選中群友,則認為是公聊 if(uid == "") { TextMessage tm("MSGA",text); ok = m_client.send(tm); } else { //如果選中群友,則發給對應的群友 QString sid = (uid.indexOf(self) >= 0) ? uid : (uid + self + 'r'); TextMessage tm("MSGP",sid+text); ok = m_client.send(tm); } if(ok ) { inputEdit.clear(); } } } void MainWin::listWidgetMenuClicked() { QAction *act = dynamic_cast<QAction*>(sender()); if(act != NULL) { const QList<QListWidgetItem*>& sl = listWidget.selectedItems(); if(sl.length() > 0) { QString user = sl.at(0)->text(); QString tip = "確認對使用者[" + user + "]進行 "+ act->text() + "操作嗎?"; //管理員對群友進行許可權操作 if(QMessageBox::question(this,"提示",tip,QMessageBox::Yes,QMessageBox::No) == QMessageBox::Yes) { QString data =act->objectName() + 'r' + user; TextMessage tm("ADMN",data); m_client.send(tm); } } else { QMessageBox::information(this,"提示","請選擇使用者!"); } } } void MainWin::listWidgetContextMenu(const QPoint&) { //只有管理員可以操作群友 if(m_level == "admin") listWidgetMenu.exec(QCursor::pos()); } void MainWin::logInOutBtnClicked() { if(!m_client.isValid()) { loginDlg.setValFunc(ValidateUserID); if(loginDlg.exec() == QDialog::Accepted) { QString usr = loginDlg.getUser().trimmed(); QString pwd = loginDlg.getPwd(); if(m_client.connectTo("127.0.0.1",8890)) { //setCtrlEnabled(true); //連線伺服器成功後,向伺服器傳送登入的資料 TextMessage tm("LGIN" , usr + 'r' + pwd); m_client.send(tm); } else { QMessageBox::critical(this,"失敗","連線不到遠端伺服器"); } } } else { m_client.close(); } } void MainWin::handle(QTcpSocket& obj,TextMessage& message) { if(m_handlerMap.contains(message.type())) { MSGHandler handler = m_handlerMap.value(message.type()); (this->*handler)(obj,message); } } void MainWin::CONN_Handler(QTcpSocket& ,TextMessage& ) { } //自己或其它群友傳送的訊息 void MainWin::MSGA_Handler(QTcpSocket& ,TextMessage& message) { msgEditor.appendPlainText(message.data()); //接收到資訊後,視窗閃爍 activateWindow(); } //斷開連線 void MainWin::DSCN_Handler(QTcpSocket& ,TextMessage& ) { setCtrlEnabled(false); inputGrpBx.setTitle("使用者名稱"); m_level = ""; } //這是伺服器發來的登入成功資料 void MainWin::LIOK_Handler(QTcpSocket& ,TextMessage& message) { QStringList rl = message.data().split("r",QString::SkipEmptyParts); QString id = rl[0]; QString status = rl[1]; m_level = rl[2]; //當前為禁言狀態 if(status == "slient") { setCtrlEnabled(true); inputEdit.setEnabled(false); sendBtn.setEnabled(false); inputGrpBx.setTitle(id); } //當前為封禁狀態 else if(status == "kick") { m_client.close(); QMessageBox::information(this,"提示","賬號 [" + id + "]已被封禁"); } else { setCtrlEnabled(true); inputGrpBx.setTitle(id); } } //這是登入失敗的操作 void MainWin::LIER_Handler(QTcpSocket& ,TextMessage& ) { QMessageBox::critical(this,"錯誤","身份驗證失敗"); m_client.close(); } //每當有群友上線或下線時,重新整理listwidget列表,由使用者端傳送過來 void MainWin::USER_Handler(QTcpSocket&,TextMessage& message) { QStringList users = message.data().split("r",QString::SkipEmptyParts); //儲存勾選狀態 QStringList checked = getCheckedUserId().split("r",QString::SkipEmptyParts); listWidget.clear(); //新增傳送過來的使用者 for(int i = 0; i < users.length();i++) { QListWidgetItem *item = new QListWidgetItem(); if(item != NULL) { item->setText(users[i]); item->setCheckState(Qt::Unchecked); listWidget.addItem(item); } } //勾選的狀態恢復 for(int i = 0; i < listWidget.count(); i++) { QListWidgetItem* item = listWidget.item(i); for(int j = 0; j<checked.length(); j++) { if(checked.at(j) == item->text()) { item->setCheckState(Qt::Checked); } } } } //這是由伺服器傳送來的資料,管理員操作後的結果 void MainWin::CTRL_Handler(QTcpSocket&,TextMessage& message) { if(message.data() == "silent") { QMessageBox::information(this,"提示","你已被禁言!"); inputEdit.clear(); inputEdit.setEnabled(false); sendBtn.setEnabled(false); } else if(message.data() == "recover" ) { QMessageBox::information(this,"提示","你已被解除禁言!"); inputEdit.setEnabled(true); sendBtn.setEnabled(true); } else if(message.data() == "kick") { QMessageBox::information(this,"提示","你已被封禁!"); m_client.close(); } } //事件過濾器的重寫,處理確認鍵 bool MainWin::eventFilter(QObject *obj, QEvent *evt) { if( (obj == &inputEdit ) && (evt->type() == QEvent::KeyPress)) { QKeyEvent *key = dynamic_cast<QKeyEvent*>(evt); if(key->text() == "r") { sendBtnClicked(); return true; } } return QWidget::eventFilter(obj,evt); }
txtmsghandler.h
#ifndef TXTMSGHANDLER_H #define TXTMSGHANDLER_H #include <QTcpSocket> #include "TextMessage.h" class TxtMsgHandler { public: virtual void handle(QTcpSocket&,TextMessage&) = 0; }; #endif // TXTMSGHANDLER_H
1.2.5 main
main.cpp
#include "MainWinUI.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWin w; w.show(); return a.exec(); }
2.1.1 協定的訂製
與使用者端相同
2.1.2 協定裝配器
與使用者端相同
2.1.3 TCP使用者端
1.每當有使用者端連線進來時,要儲存
2.每當有使用者端連線或斷開時,要有系統訊息提示
ServerDemo.h
#ifndef SERVERDEMO_H #define SERVERDEMO_H #include <QObject> #include <QTcpServer> #include <QMap> #include "TextMessage.h" #include "TxtMsgAssembler.h" #include "txtmsghandler.h" class ServerDemo : public QObject { Q_OBJECT QTcpServer m_server; QMap<QTcpSocket*,TxtMsgAssembler*> m_map; TxtMsgHandler* m_handler; public: explicit ServerDemo(QObject *parent = 0); bool start(int port); void stop(); void setHandler(TxtMsgHandler* handler); ~ServerDemo(); protected slots: void onNewconnection(); void onConnected(); void onDisconnected(); void onDataReady(); void onBytesWritten(qint64 bytes); }; #endif // SERVERDEMO_H
ServerDemo.cpp
#include "ServerDemo.h" #include "TextMessage.h" #include "TxtMsgAssembler.h" #include <QSharedPointer> #include <QHostAddress> #include <QTcpSocket> #include <QObject> #include <QDebug> ServerDemo::ServerDemo(QObject *parent) : QObject(parent) { //有新的使用者端連線 connect(&m_server,SIGNAL(newConnection()),this,SLOT(onNewconnection())); } //開始監聽, bool ServerDemo::start(int port) { bool ret = true; if(!m_server.isListening()) { ret = m_server.listen(QHostAddress("127.0.0.1"),port); } return ret; } void ServerDemo::stop() { if(m_server.isListening()) m_server.close(); } void ServerDemo::onNewconnection() { QTcpSocket *tcp = m_server.nextPendingConnection(); //給每一個使用者端建立一個裝配器 TxtMsgAssembler *as = new TxtMsgAssembler(); //儲存每一個socket對應的裝配器 m_map.insert(tcp,as); //給該socket建立連線 connect(tcp,SIGNAL(connected()),this,SLOT(onConnected())); connect(tcp,SIGNAL(disconnected()),this,SLOT(onDisconnected())); connect(tcp,SIGNAL(readyRead()),this,SLOT(onDataReady())); connect(tcp,SIGNAL(bytesWritten(qint64)),this,SLOT(onBytesWritten(qint64))); if(m_handler != NULL) { TextMessage msg("CONN",tcp->peerAddress().toString() + ":" + QString::number(tcp->peerPort())); m_handler->handle(*tcp,msg); } } void ServerDemo::onConnected() { } void ServerDemo::onDisconnected() { //獲取斷開連線的使用者端 QTcpSocket *tcp = dynamic_cast<QTcpSocket*>(sender()); if(tcp != NULL) { //取出對應tcp與裝配器的對映,並且刪除該結點 m_map.take(tcp); if(m_handler != NULL) { //呼叫斷開的handler函數 TextMessage msg("DSCN",""); m_handler->handle(*tcp,msg); } } } void ServerDemo::onDataReady() { QTcpSocket *tcp = dynamic_cast<QTcpSocket*>(sender()); char buf[256] = {0}; int len = 0; if(tcp != NULL) { //取出tcp對應的裝配器 TxtMsgAssembler* assembler = m_map.value(tcp); while( (len = tcp->read(buf,sizeof(buf))) > 0) { if(assembler != NULL) { QSharedPointer<TextMessage> ptm; assembler->prepare(buf,len); while( (ptm = assembler->assemble()) != NULL) { if(m_handler != NULL) { //處理對應型別的操作 m_handler->handle(*tcp,*ptm); } } } } } } void ServerDemo::onBytesWritten(qint64 bytes) { (void)bytes; } ServerDemo::~ServerDemo() { const QObjectList& list = m_server.children(); //關閉所有連線的使用者端 for(int i = 0; i < list.length(); i++) { QTcpSocket *tcp = dynamic_cast<QTcpSocket*>(list[i]); if(tcp != NULL) { tcp->close(); } } //對應的裝配器也刪除 const QList<TxtMsgAssembler*>& al = m_map.values(); for(int i = 0; i < al.length(); i++) { delete al.at(i); } } void ServerDemo::setHandler(TxtMsgHandler* handler) { m_handler = handler; }
ServerHandler.cpp
#include "ServerHandler.h" #include <QDebug> #include <QMap> ServerHandler::ServerHandler() { #define MapToHandler(MSG) m_handlerMap.insert(#MSG,&ServerHandler::MSG##_Handler) //連線各種型別的操作函數 MapToHandler(CONN); MapToHandler(DSCN); MapToHandler(LGIN); MapToHandler(MSGA); MapToHandler(MSGP); MapToHandler(ADMN); //新增管理員賬號 static Node admin; admin.id = "admin"; admin.pwd = "123"; admin.level = "admin"; m_nodeList.append(&admin); // m_handlerMap.insert("CONN",&ServerHandler::CONN_Handler); // m_handlerMap.insert("DSCN",&ServerHandler::DSCN_Handler); // m_handlerMap.insert("LGIN",&ServerHandler::LGIN_Handler); // m_handlerMap.insert("MSGA",&ServerHandler::MSGA_Handler); } //抽象出來的獲取所有線上的群友 QString ServerHandler::getOnlineUserId() { QString ret = ""; for(int i = 0; i < m_nodeList.length(); i++) { Node* n = m_nodeList.at(i); if(n->socket != NULL) { ret += n->id + 'r'; } } return ret; } void ServerHandler::handle(QTcpSocket& obj,TextMessage& message) { if(m_handlerMap.contains(message.type())) { MSGHandler handler = m_handlerMap.value(message.type()); (this->*handler)(obj,message); } } //傳送訊息給所有線上的群友 void ServerHandler::MSGA_Handler(QTcpSocket&,TextMessage& message) { sendToAllOnlineUser(message); } void ServerHandler::CONN_Handler(QTcpSocket& ,TextMessage& ) { } //接收到使用者端發來的斷開連線操作 void ServerHandler::DSCN_Handler(QTcpSocket& obj,TextMessage& ) { Node* n = NULL; // for(int i = 0; i < m_nodeList.length();i++) { n = m_nodeList.at(i); if(n->socket == &obj) { n->socket = NULL; break; } } //傳送給使用者端,使用者端用於更新線上列表 TextMessage tm("USER",getOnlineUserId()); sendToAllOnlineUser(tm); //傳送給使用者端,用於顯示系統訊息 if(n != NULL) { TextMessage tm("MSGA", "[系統訊息]: " + n->id + "退出聊天室"); sendToAllOnlineUser(tm); } } //使用者端傳送的上線資料 void ServerHandler::LGIN_Handler(QTcpSocket& obj,TextMessage& message) { QString data = message.data(); int index = data.indexOf('r'); QString id = data.mid(0,index); QString pwd = data.mid(index+1); QString result = ""; QString status =""; QString level = ""; index = -1; //遍歷是否存在該使用者 for(int i = 0; i < m_nodeList.length(); i++) { if(id == m_nodeList.at(i)->id) { index = i; break; } } //如果不存在就註冊新使用者 if(index == -1) { Node* newNode = new Node(); if(newNode != NULL) { newNode->id = id; newNode->pwd = pwd; newNode->socket = &obj; m_nodeList.append(newNode); result = "LIOK"; status = newNode->status; level = newNode->level; } else { result = "LIER"; } } else //如果存在就校驗密碼 { Node* n = m_nodeList.at(index); if(pwd == n->pwd) { n->socket = &obj; result = "LIOK"; status = n->status; level = n->level; } else { result = "LIER"; } } //傳送給使用者端,當前是登入成功還是失敗 obj.write(TextMessage(result,id + 'r' + status + 'r' + level).serizlize()); //登入成功 if(result == "LIOK") { //傳送給使用者端用於更新線上列表 TextMessage user("USER",getOnlineUserId()); sendToAllOnlineUser(user); //傳送系統訊息 TextMessage msga("MSGA", "[系統訊息]: " + id + "進入聊天室"); sendToAllOnlineUser(msga); } } //私聊操作 void ServerHandler::MSGP_Handler(QTcpSocket&,TextMessage& message) { //分隔訊息,在協定製訂時,最後被r分開的是具體資訊 QStringList tl = message.data().split("r",QString::SkipEmptyParts); const QByteArray& ba = TextMessage("MSGA",tl.last()).serizlize(); tl.removeLast(); //遍歷使用者,檢視是否存在該使用者 for(int i = 0; i < tl.length(); i++) { for(int j = 0; j < m_nodeList.length(); j++) { Node *n = m_nodeList.at(j); //如果存在,就發給對應的使用者 if( (tl[i] == n->id) && (n->socket != NULL)) { n->socket->write(ba); break; } } } } //管理員許可權操作 void ServerHandler::ADMN_Handler(QTcpSocket&,TextMessage& message) { //協定製訂:第一個為操作,第二個為使用者 QStringList data = message.data().split("r",QString::SkipEmptyParts); QString op = data[0]; QString id = data[1]; //遍歷檢視使用者是否存在 for(int i = 0; i < m_nodeList.length();i++) { Node *n = m_nodeList.at(i); //如果存在,並且被操作的使用者不是管理員身份才能被操作 if( (id == n->id) && (n->socket != NULL) && (n->level != "admin")) { n->socket->write(TextMessage("CTRL",op).serizlize()); n->status = op; break; } } } //傳送訊息給所有線上的使用者 void ServerHandler::sendToAllOnlineUser(TextMessage& message) { const QByteArray& ba = message.serizlize(); for(int i = 0; i < m_nodeList.length();i++) { Node* n = m_nodeList.at(i); if(n->socket != NULL) { n->socket->write(ba); } } }
2.1.4 main
main.c
#include <QCoreApplication> #include "ServerHandler.h" #include "ServerDemo.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); ServerHandler handler; ServerDemo server; server.setHandler(&handler); //開始監聽 server.start(8890); return a.exec(); }
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45