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();
    }
    
  • 相关阅读:
    优化linux,安装node环境
    腾讯云主机的公网无法访问,putty和FileZilla连接不上
    angularjs1.x的directive中的link参数element见解
    springboot自动装配原理
    redis哨兵部署
    HashMap笔记
    多线程与高并发
    java面试必会知识点
    maven仓库
    java任务调度简单示例
  • 原文地址:https://www.cnblogs.com/r1chie/p/14418161.html
Copyright © 2011-2022 走看看