QT TCP网络聊天室
R1CHIE_L 人气:01.客户端
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(); }
加载全部内容