Qt WebSocket服務端的簡單Demo

WebSocket服務端:QWebSocketServer

背景

最近遇到一個項目要開發一個服務,該服務通過websocket 傳出數據。於是先從簡單的實現開始吧。

QWebSocketServer 簡單使用介紹:

1、Qt對websocket的封裝分爲服務端和客戶端,分別使用QWebSocketServer和QWebSocket。

2、QWebSocketServer 和 QTcpServer 都是基於QAbstractSocket模型的。所以可以類似QTcpServer的開發,它們行爲相同。使用參照QTcpServer。

3、偵聽:調用listen()讓服務器監聽傳入的連接。

4、接入新的客戶端連接:每次客戶端連接到服務器時都會發出newConnection()信號。調用nextPendingConnection()將等待接入的連接接受爲已連接的QWebSocket。函數返回指向QabstractSocket::ConnectedState中QWebSocket的指針,可以使用該指針與客戶端通信。

5、異常處理: 如果發生錯誤,ServerError()返回錯誤類型,並且可以調用ErrorString()以獲取對所發生情況的人類可讀描述。

6、停止接收新的接入:調用close()將使QWebSocketServer停止偵聽傳入的連接。

7、注意:

  • QWebSocket服務器當前不支持WebSocket擴展和WebSocket子工具。

  • 使用自簽名證書時,Firefox bug
    594502會阻止firefox連接到安全的Websocket服務器。要解決此問題,請首先使用https瀏覽到安全WebSocket服務器。Firefox將指示證書無效。從這裏開始,可以將證書添加到異常中。在這之後,安全WebSockets連接應該可以工作。

  • QWebSocketServer僅支持WebSocket協議的版本13,如RFC6455所述。

8、服務器安全設置:
枚舉
enum QWebSocketServer::SslMode
指示服務器是通過wss(SecureMode)還是ws(NonSecureMode)運行。

具體開發過程:

1、在工程文件夾中添加: `QT += websockets`
2、包含類  `#include <QWebSocketServer>`
3、創建對象:使用時先new一個QWebSocketServer,傳入服務器名稱和是否使用安全模式(安全模式wss,非安全模式ws),然後關聯其newConnected(),closed(),serverError()。  
4、當收到新的連接後,則是轉換爲QWebSocket,然後關聯其connected(),disconnected(),error(),textFrameReceived()(或者textMessageReceived()信號,兩個收到消息的信號都會觸發),發送調用sendTextMessage()函數即。
5、爲了更好的管理代碼,建一個類來管理webSocketServer
class WebSocketServerManager : public QObject
 {
 
    Q_OBJECT
public:
    explicit WebSocketServerManager(QString serverName,
                                    QWebSocketServer::SslMode secureMode = QWebSocketServer::NonSecureMode,
                                    QObject *parent = 0);
    ~WebSocketServerManager();

public:
    bool running() const;

 }
6、在該類中添加槽函數成員以及私有變量:如服務開啓,服務停止,服務關閉等


public slots:
    void slot_start(QHostAddress hostAddress = QHostAddress(QHostAddress::Any), qint32 port = 10080);
    void slot_stop();
    void slot_sendData(QString ip, qint32 port, QString message);

protected slots:
    void slot_newConnection();
    void slot_serverError(QWebSocketProtocol::CloseCode closeCode);
    void slot_closed();

protected slots:
    void slot_disconnected();
    void slot_error(QAbstractSocket::SocketError error);
    void slot_textFrameReceived(const QString &frame, bool isLastFrame);
    void slot_textMessageReceived(const QString &message);

private:
    QString _serverName;

    QWebSocketServer::SslMode _sslMode;
    bool _running;
    QWebSocketServer *_pWebSocketServer;
    QHash<QString, QWebSocket*> _hashIpPort2PWebSocket;
    QHostAddress _listenHostAddress;
    qint32 _listenPort;

主要關鍵代碼分析:

1、服務開啓

void WebSocketServerManager::slot_start(QHostAddress hostAddress, qint32 port)
{
    if(_running)   //如果服務已經開啓
    {
        qDebug() << __FILE__ << __LINE__
                 << "Failed to" << __FUNCTION__ << "it's already running...";
        return;
    }
    
    if(!_pWebSocketServer)   //如果沒有服務,則創建服務
    {
        _pWebSocketServer = new QWebSocketServer(_serverName, _sslMode, 0);
        connect(_pWebSocketServer, SIGNAL(newConnection()), this, SLOT(slot_newConnection()));
        connect(_pWebSocketServer, SIGNAL(closed()), this, SLOT(slot_closed()));
        connect(_pWebSocketServer, SIGNAL(serverError(QWebSocketProtocol::CloseCode)),
                this             , SLOT(slot_serverError(QWebSocketProtocol::CloseCode)));
    }
    _listenHostAddress = hostAddress;
    _listenPort = port;
    //開啓偵聽
    _pWebSocketServer->listen(_listenHostAddress, _listenPort);
    //更新標誌狀態
    _running = true;
}

2、服務停止:

void WebSocketServerManager::slot_stop()
{
    if(!_running)
    {
        qDebug() << __FILE__ << __LINE__
                 << "Failed to" << __FUNCTION__
                 << ", it's not running...";
        return;
    }
    else
    {
        qDebug() << __FILE__ << __LINE__
                 << "connect to" << __FUNCTION__
                 << ", it's running...";
    }
    _running = false;
    _pWebSocketServer->close();
}

注意:Qt 有一項很重要的調試工具qDebug(), 而且可以指定文件到行和函數體。

3、向指定客戶端發送數據

void WebSocketServerManager::slot_sendData(QString ip, qint32 port, QString message)
{
    QString key = QString("%1-%2").arg(ip).arg(port);
    if(_hashIpPort2PWebSocket.contains(key))
    {
        _hashIpPort2PWebSocket.value(key)->sendTextMessage(message);
    }
}

注意到這裏使用了容器,這使得我們更方便管理客戶端。

4、響應新接入的客戶端

void WebSocketServerManager::slot_newConnection()
{
    QWebSocket *pWebSocket = _pWebSocketServer->nextPendingConnection();
    connect(pWebSocket, SIGNAL(disconnected()), this, SLOT(slot_disconnected()));
    connect(pWebSocket, SIGNAL(error(QAbstractSocket::SocketError)),
            this      , SLOT(slot_error(QAbstractSocket::SocketError)));
    // 既會觸發frame接收也會觸發message接收
//    connect(pWebSocket, SIGNAL(textFrameReceived(QString,bool)),
//            this      , SLOT(slot_textFrameReceived(QString,bool)));
    connect(pWebSocket, SIGNAL(textMessageReceived(QString)),
            this      , SLOT(slot_textMessageReceived(QString)));
    _hashIpPort2PWebSocket.insert(QString("%1-%2").arg(pWebSocket->peerAddress().toString())
                                  .arg(pWebSocket->peerPort()),
                                  pWebSocket);
    qDebug() << __FILE__ << __LINE__ << pWebSocket->peerAddress().toString() << pWebSocket->peerPort();
    emit signal_conncted(pWebSocket->peerAddress().toString(), pWebSocket->peerPort());
}

5、關閉所有的客戶端

遍歷當前客戶端,逐一關閉。

void WebSocketServerManager::slot_closed()
{
    QList<QWebSocket *> _listWebSocket = _hashIpPort2PWebSocket.values();
    for(int index = 0; index < _listWebSocket.size(); index++)
    {
        _listWebSocket.at(index)->close();
    }
    _hashIpPort2PWebSocket.clear();
    emit signal_close();
}

6、斷開其中與某個客戶端的連接

void WebSocketServerManager::slot_disconnected()
{
    qDebug() << __FILE__ << __LINE__ << __FUNCTION__;
    QWebSocket *pWebSocket = dynamic_cast<QWebSocket *>(sender());
    if(!pWebSocket)
    {
        return;
    }
    qDebug() << __FILE__ << __LINE__ << __FUNCTION__;
    emit signal_disconncted(pWebSocket->peerAddress().toString(), pWebSocket->peerPort());
    _hashIpPort2PWebSocket.remove(QString("%1-%2").arg(pWebSocket->peerAddress().toString())
                                                  .arg(pWebSocket->peerPort()));
}

7、接收客戶端的數據

void WebSocketServerManager::slot_textFrameReceived(const QString &frame, bool isLastFrame)
{
    QWebSocket *pWebSocket = dynamic_cast<QWebSocket *>(sender());
    if(!pWebSocket)
    {
        return;
    }
    qDebug() << __FILE__ << __LINE__ << frame << isLastFrame;
    emit signal_textFrameReceived(pWebSocket->peerAddress().toString(), pWebSocket->peerPort(), frame, isLastFrame);
}

void WebSocketServerManager::slot_textMessageReceived(const QString &message)
{
    QWebSocket *pWebSocket = dynamic_cast<QWebSocket *>(sender());
    if(!pWebSocket)
    {
        return;
    }
    emit signal_textMessageReceived(pWebSocket->peerAddress().toString(), pWebSocket->peerPort(), message);
}


8、異常的處理

void WebSocketServerManager::slot_serverError(QWebSocketProtocol::CloseCode closeCode)
{
    QWebSocket *pWebSocket = dynamic_cast<QWebSocket *>(sender());
    if(!pWebSocket)
    {
        return;
    }
    emit signal_error(pWebSocket->peerAddress().toString(), pWebSocket->peerPort(), _pWebSocketServer->errorString());
}

void WebSocketServerManager::slot_error(QAbstractSocket::SocketError error)
{
    QWebSocket *pWebSocket = dynamic_cast<QWebSocket *>(sender());
    if(!pWebSocket)
    {
        return;
    }
    emit signal_error(pWebSocket->peerAddress().toString(), pWebSocket->peerPort(), pWebSocket->errorString());
}

9、最後在類頭文件添加相關信號,供其他對象關聯調用。

    void signal_conncted(QString ip, qint32 port);
    void signal_disconncted(QString ip, qint32 port);
    void signal_sendTextMessageResult(QString ip, quint32 port, bool result);
    void signal_sendBinaryMessageResult(QString ip, quint32 port, bool result);
    void signal_error(QString ip, quint32 port, QString errorString);
    void signal_textFrameReceived(QString ip, quint32 port, QString frame, bool isLastFrame);
    void signal_textMessageReceived(QString ip, quint32 port,QString message);
    void signal_close();

10、窗體的實現:

佈局一個窗體,將窗體中的控件與類對象WebSocketServerManager關聯。


webSocketServerWidget::webSocketServerWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::webSocketServerWidget)
{
    ui->setupUi(this);
    setWindowTitle("WebSocket服務端Demo v1.0.0 ");

    ui->pushButton_listen->setEnabled(true);
    ui->pushButton_stop->setEnabled(false);
    ui->pushButton_send->setEnabled(false);

    _pWebSocketServerManager = new WebSocketServerManager("Test");
    connect(_pWebSocketServerManager, SIGNAL(signal_conncted(QString,qint32)),
            this                    , SLOT(slot_conncted(QString,qint32)));
    connect(_pWebSocketServerManager, SIGNAL(signal_disconncted(QString,qint32)),
            this                    , SLOT(slot_disconncted(QString,qint32)));
    connect(_pWebSocketServerManager, SIGNAL(signal_error(QString,quint32,QString)),
            this                    , SLOT(slot_error(QString,quint32,QString)));
    connect(_pWebSocketServerManager, SIGNAL(signal_textFrameReceived(QString,quint32,QString,bool)),
            this                    , SLOT(slot_textFrameReceived(QString,quint32,QString,bool)));
    connect(_pWebSocketServerManager, SIGNAL(signal_textMessageReceived(QString,quint32,QString)),
            this                    , SLOT(slot_textMessageReceived(QString,quint32,QString)));

    ui->lineEdit_ip->setText(getLocalIp());
}

webSocketServerWidget::~webSocketServerWidget()
{
    delete ui;
}

void webSocketServerWidget::slot_conncted(QString ip, qint32 port)
{
    _strList << QString("%1-%2").arg(ip).arg(port);
    _hashIpPort2Message.insert(QString("%1-%2").arg(ip).arg(port), QString());
    _model.setStringList(_strList);
    ui->listView_ipPort->setModel(&_model);
    if(_strList.size() == 1)
    {
        ui->listView_ipPort->setCurrentIndex(_model.index(0));
    }
}

void webSocketServerWidget::slot_disconncted(QString ip, qint32 port)
{
    QString str = QString("%1-%2").arg(ip).arg(port);
    if(_strList.contains(str))
    {
        _strList.removeOne(str);
        _model.setStringList(_strList);
        ui->listView_ipPort->setModel(&_model);
        updateTextEdit();
    }
}

void webSocketServerWidget::slot_sendTextMessageResult(QString ip, quint32 port, bool result)
{
}

void webSocketServerWidget::slot_sendBinaryMessageResult(QString ip, quint32 port, bool result)
{

}

void webSocketServerWidget::slot_error(QString ip, quint32 port, QString errorString)
{

}

void webSocketServerWidget::slot_textFrameReceived(QString ip, quint32 port, QString frame, bool isLastFrame)
{
    if(_hashIpPort2Message.contains(QString("%1-%2").arg(ip).arg(port)))
    {
        _hashIpPort2Message[QString("%1-%2").arg(ip).arg(port)]
                = _hashIpPort2Message[QString("%1-%2").arg(ip).arg(port)] +
                  (_hashIpPort2Message[QString("%1-%2").arg(ip).arg(port)].size() == 0 ? "" :"\n") +
                  QDateTime::currentDateTime().toString("yyyy-HH-mm hh:MM:ss:zzz") + "\n" + frame;
    }
    updateTextEdit();
}

void webSocketServerWidget::slot_textMessageReceived(QString ip, quint32 port, QString message)
{
    qDebug() << __FILE__ << __LINE__ << message;
    if(_hashIpPort2Message.contains(QString("%1-%2").arg(ip).arg(port)))
    {
        _hashIpPort2Message[QString("%1-%2").arg(ip).arg(port)]
                = _hashIpPort2Message[QString("%1-%2").arg(ip).arg(port)] +
                  (_hashIpPort2Message[QString("%1-%2").arg(ip).arg(port)].size() == 0 ? "" :"\n") +
                  QDateTime::currentDateTime().toString("yyyy-HH-mm hh:MM:ss:zzz") + "\n" + message;
    }
    updateTextEdit();
}

void webSocketServerWidget::slot_close()
{
    _pWebSocketServerManager->slot_stop();
    _strList.clear();
    _model.setStringList(_strList);
    ui->listView_ipPort->setModel(&_model);
    ui->textEdit_recv->clear();
}

void webSocketServerWidget::updateTextEdit()
{
    int row = ui->listView_ipPort->currentIndex().row();
    if(row < 0)
    {
        ui->textEdit_recv->setText("");
        return;
    }
    if(_hashIpPort2Message.contains(_strList.at(row)))
    {
        ui->textEdit_recv->setText(_hashIpPort2Message.value(_strList.at(row)));
    }
}

QString webSocketServerWidget::getLocalIp()
{
    QString localIp;
    QList<QHostAddress> list = QNetworkInterface::allAddresses();
    for(int index = 0; index < list.size(); index++)
    {
        if(list.at(index).protocol() == QAbstractSocket::IPv4Protocol)
        {
            //IPv4地址
            if (list.at(index).toString().contains("127.0."))
            {
                continue;
            }
            localIp = list.at(index).toString();
            break;
        }
    }
    return localIp;
}

void webSocketServerWidget::on_pushButton_listen_clicked()
{
    _pWebSocketServerManager->slot_start(QHostAddress::Any, ui->spinBox->value());
    ui->pushButton_listen->setEnabled(false);
    ui->pushButton_stop->setEnabled(true);
    ui->pushButton_send->setEnabled(true);
}

void webSocketServerWidget::on_pushButton_stop_clicked()
{
    _pWebSocketServerManager->slot_stop();
    ui->pushButton_listen->setEnabled(true);
    ui->pushButton_stop->setEnabled(false);
    ui->pushButton_send->setEnabled(false);
}

void webSocketServerWidget::on_listView_ipPort_clicked(const QModelIndex &index)
{
    updateTextEdit();
}

void webSocketServerWidget::on_pushButton_send_clicked()
{
    int row = ui->listView_ipPort->currentIndex().row();
    if(_hashIpPort2Message.contains(_strList.at(row)))
    {
        QString str = _strList.at(row);
        QStringList strlist = str.split("-");
        if(strlist.size() == 2)
        {
            _pWebSocketServerManager->slot_sendData(strlist.at(0),
                                                    strlist.at(1).toInt(),
                                                    ui->textEdit_send->toPlainText());
        }
    }
}

最後的效果:

總結:

1、該示例代碼簡單實現了webSocketServer的創建。但是並沒有用到多線程的技術,所以對併發處理不不適合。

2、本示例對數據處理,和錯誤事件並沒有很好的解析,這需要後續實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章