首頁 > 軟體

QT基於TCP網路聊天室

2022-08-19 22:02:52

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com