zoukankan      html  css  js  c++  java
  • QT网络聊天室

    QT基于TCP网络聊天室

    1.客户端

    1.1UI设计

    ​ 分两个部分,第一部分是消息区里面包含QPlainTextEditQListWidget,要显示接收的消息和在线的成员。第二部分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() + '
    ';
            }
        }
    
        return ret;
    }
    
    void MainWin::sendBtnClicked()
    {
        QString input = inputEdit.text().trimmed();
    
        if(input != "")
        {
            QString self = inputGrpBx.title();
            QString text = self + ":
    " + "     " + input + "
    ";
            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 + '
    ');
                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() + '
    ' + 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 + '
    ' + 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("
    ",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("
    ",QString::SkipEmptyParts);
       
        //保存勾选状态
        QStringList checked = getCheckedUserId().split("
    ",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() == "
    ")
            {
                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 + '
    ';
            }
        }
    
        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('
    ');
        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 + '
    ' + status + '
    ' + level).serizlize());
    
        //登录成功
        if(result == "LIOK")
        {
            //发送给客户端用于更新在线列表
            TextMessage user("USER",getOnlineUserId());
            sendToAllOnlineUser(user);
    
            //发送系统消息
            TextMessage msga("MSGA", "[系统消息]: " + id + "进入聊天室");
            sendToAllOnlineUser(msga);
        }
    }
    
    //私聊操作
    void ServerHandler::MSGP_Handler(QTcpSocket&,TextMessage& message)
    {
        //分隔消息,在协议制订时,最后被
    分开的是具体信息
        QStringList tl = message.data().split("
    ",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("
    ",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();
    }
    
  • 相关阅读:
    Azure 虚拟机安全加固整理
    AzureARM 使用 powershell 扩容系统磁盘大小
    Azure Linux 云主机使用Root超级用户登录
    Open edX 配置 O365 SMTP
    powershell 根据错误GUID查寻错误详情
    azure 创建redhat镜像帮助
    Azure Powershell blob中指定的vhd创建虚拟机
    Azure Powershell 获取可用镜像 PublisherName,Offer,Skus,Version
    Power BI 连接到 Azure 账单,自动生成报表,可刷新
    Azure powershell 获取 vmSize 可用列表的命令
  • 原文地址:https://www.cnblogs.com/r1chie/p/14418161.html
Copyright © 2011-2022 走看看