本文实例为大家分享了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();
}