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、本示例对数据处理,和错误事件并没有很好的解析,这需要后续实现。