QT基于TCP网络聊天室

Thirza ·
更新时间:2024-11-13
· 15 次阅读

本文实例为大家分享了QT实现网络聊天室的具体代码,供大家参考,具体内容如下

1.客户端 1.1UI设计

分两个部分,第一部分是消息区里面包含QPlainTextEdit和QListWidget,要显示接收的消息和在线的成员。第二部分QLineEdit发生字符。

1.2 子模块

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.服务端 2.1 子模块

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(); }



tcp 聊天室

需要 登录 后方可回复, 如果你还没有账号请 注册新账号