您好,登錄后才能下訂單哦!
這篇“QT基于TCP怎么實現網絡聊天室”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“QT基于TCP怎么實現網絡聊天室”文章吧。
分兩個部分,第一部分是消息區里面包含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(); }
以上就是關于“QT基于TCP怎么實現網絡聊天室”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。