亲宝软件园·资讯

展开

QT TCP网络聊天室

R1CHIE_L 人气:0

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();
}

加载全部内容

相关教程
猜你喜欢
用户评论