参考:使用 HTML5 WebSocket 构建实时 Web 应用
参考中提供了WebSocket客户端及服务器端代码(C#),这里只是通过QT(4.8.5)/C++进行了重写并使用比较新的Websocket协议,客户端使用参考页面上的前端代码。代码只是为了演示WebSocket的简单交互,所以未进行完善的处理。
WebSocketServer.h
#ifndef WEBSOCKETSERVER_H
#define WEBSOCKETSERVER_H
#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QList>
class SocketConnection;
class WebSocketServer : public QObject
{
Q_OBJECT
public:
WebSocketServer(QObject *parent);
~WebSocketServer();
bool StartServer();
void Close();
public slots:
void OnNewConnection();
void OnSocketStateChanged(QAbstractSocket::SocketState socketState);
void OnRecvMessage(const QString&);
private:
QTcpServer listener_;
QList<SocketConnection *> connect_socket_list_;
void Send(const QString& msg);
};
#endif // WEBSOCKETSERVER_H
WebSocketServer.cpp
#include <QDebug>
#include "WebSocketServer.h"
#include "SocketConnection.h"
#include "DataFrame.h"
WebSocketServer::WebSocketServer(QObject *parent)
: QObject(parent)
{
}
WebSocketServer::~WebSocketServer()
{
Close();
}
bool WebSocketServer::StartServer() {
listener_.listen(QHostAddress::Any, 4141);
connect(&listener_, SIGNAL(newConnection()), this, SLOT(OnNewConnection()));
qDebug() << QString("聊天服务器启动。监听地址:%1, 端口:%2").arg("0.0.0.0").arg(4141);
qDebug() << QString("WebSocket服务器地址: ws://%1:%2/chat").arg("0.0.0.0").arg(4141);
return true;
}
void WebSocketServer::Close() {
QList<SocketConnection *>::iterator iter = connect_socket_list_.begin();
for (; iter != connect_socket_list_.end(); ++iter) {
(*iter)->GetSocket()->close();
}
connect_socket_list_.clear();
}
void WebSocketServer::OnNewConnection() {
QTcpSocket* socket = listener_.nextPendingConnection();
SocketConnection* sc = new SocketConnection(NULL);
sc->SetSocket(socket);
connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), sc, SIGNAL(ConnectStateChanged(QAbstractSocket::SocketState)));
connect(sc, SIGNAL(ConnectStateChanged(QAbstractSocket::SocketState)), this, SLOT(OnSocketStateChanged(QAbstractSocket::SocketState)));
connect(socket, SIGNAL(readyRead()), sc, SLOT(OnReadyRead()));
connect(sc, SIGNAL(RecvMessage(const QString&)), this, SLOT(OnRecvMessage(const QString&)));
connect_socket_list_.push_back(sc);
}
void WebSocketServer::OnSocketStateChanged(QAbstractSocket::SocketState socketState) {
if (socketState == QAbstractSocket::UnconnectedState) {
SocketConnection* sc = static_cast<SocketConnection *>(QObject::sender());
Send(QString("【%1】离开了聊天室!").arg(sc->GetName()));
connect_socket_list_.removeOne(sc);
delete sc;
}
}
void WebSocketServer::OnRecvMessage(const QString& msg) {
int loginIdx = msg.indexOf("login:");
if (loginIdx != -1) {
SocketConnection* sc = static_cast<SocketConnection *>(QObject::sender());
QString name = msg.right(msg.size() - (loginIdx + QString("login:").size()));
sc->SetName(name);
QString loginMsg = QString("欢迎【%1】来到聊天室!").arg(name);
Send(loginMsg);
return;
}
Send(msg);
}
void WebSocketServer::Send(const QString& msg) {
QList<SocketConnection *>::iterator iter = connect_socket_list_.begin();
for (; iter != connect_socket_list_.end(); ++iter) {
SocketConnection* sc = *iter;
if (sc->GetSocket()->state() != QAbstractSocket::ConnectedState) continue;
if (sc->GetIsDataMasked()) {
DataFrame dr(msg);
sc->GetSocket()->write(dr.GetByteArray());
} else {
}
}
}
SocketConnection.h
#ifndef SOCKETCONNECTION_H
#define SOCKETCONNECTION_H
#include <QObject>
#include <QTcpSocket>
#include <QAbstractSocket>
class SocketConnection : public QObject
{
Q_OBJECT
public:
SocketConnection(QObject *parent);
~SocketConnection();
void SetSocket(QTcpSocket* socket) { socket_ = socket; }
QTcpSocket* GetSocket() { return socket_; }
void SetName(const QString& name) { name_ = name; }
QString GetName() { return name_; }
bool GetIsDataMasked() { return is_data_masked_; }
public slots:
void OnReadyRead();
signals:
void ConnectStateChanged(QAbstractSocket::SocketState);
void RecvMessage(const QString&);
private:
const static QString NewLine;
QTcpSocket* socket_;
QString name_;
bool is_data_masked_;
bool is_handshaked_;
QString handshake_msg_;
QString ComputeWebSocketHandshakeSecurityHash09(const QString& secWebSocketKey);
};
#endif // SOCKETCONNECTION_H
SocketConnection.cpp
#include <QTextCodec>
#include <QStringList>
#include <QCryptographicHash>
#include "SocketConnection.h"
#include "DataFrame.h"
const QString SocketConnection::NewLine = "\r\n";
SocketConnection::SocketConnection(QObject *parent)
: QObject(parent)
{
socket_ = NULL;
is_data_masked_ = false;
is_handshaked_ = false;
handshake_msg_ = "HTTP/1.1 101 Switching Protocols" + NewLine;
handshake_msg_ += "Upgrade: WebSocket" + NewLine;
handshake_msg_ += "Connection: Upgrade" + NewLine;
handshake_msg_ += "Sec-WebSocket-Accept: %1" + NewLine;
handshake_msg_ += NewLine;
}
SocketConnection::~SocketConnection()
{
}
void SocketConnection::OnReadyRead() {
QTcpSocket* socket = static_cast<QTcpSocket *>(QObject::sender());
QByteArray msg_ba = socket->readAll();
QString msg = QTextCodec::codecForName("UTF-8")->toUnicode(msg_ba);
if (!is_handshaked_) {
is_data_masked_ = true;
QStringList handshakeLines = msg.split(NewLine);
QString acceptKey;
for (int i=0; i<handshakeLines.size(); ++i) {
qDebug() << handshakeLines[i];
int commaIdx = -1;
if (handshakeLines[i].indexOf("Sec-WebSocket-Key:") != -1) {
QString secWebSocketKey = handshakeLines[i].right(handshakeLines[i].size() - (handshakeLines[i].indexOf(":")+2));
acceptKey = ComputeWebSocketHandshakeSecurityHash09(secWebSocketKey);
}
}
handshake_msg_ = handshake_msg_.arg(acceptKey);
qint64 w = socket_->write(handshake_msg_.toUtf8());
qDebug() << "Handshake write bytes: " << w;
is_handshaked_ = true;
} else {
QString msgRecved;
DataFrame dataFrame(msg_ba);
if (!is_data_masked_) {
socket_->close();
} else if (dataFrame.Header().OpCode() == 0x08) {
qDebug() << "接受到的信息 [\"logout:" << name_ << "\"]";
socket_->close();
}
else {
msgRecved = dataFrame.Text();
}
if (!msgRecved.isEmpty()) {
qDebug() << "接受到的信息 [\"" << msgRecved << "\"]";
emit RecvMessage(msgRecved);
}
}
}
QString SocketConnection::ComputeWebSocketHandshakeSecurityHash09(const QString& secWebSocketKey) {
const QString MagicKEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
QString secWebSocketAccept;
// 1. Combine the request Sec-WebSocket-Key with magic key.
QString ret = secWebSocketKey + MagicKEY;
// 2. Compute the SHA1 hash
QByteArray sha1 = QCryptographicHash::hash(ret.toLatin1(), QCryptographicHash::Sha1);
// 3. Base64 encode the hash
secWebSocketAccept = QString(sha1.toBase64());
return secWebSocketAccept;
}
DataFrame.h
#ifndef DATAFRAME_H
#define DATAFRAME_H
#include <QObject>
class DataFrameHeader {
private:
bool fin_;
bool rsv1_;
bool rsv2_;
bool rsv3_;
quint8 op_code_;
bool mask_code_;
quint8 payload_length_;
public:
DataFrameHeader() {}
bool Fin() { return fin_; }
bool Rsv1() { return rsv1_; }
bool Rsv2() { return rsv2_; }
bool Rsv3() { return rsv3_; }
quint8 OpCode() { return op_code_; }
bool HasMask() { return mask_code_; }
quint8 PayloadLength() { return payload_length_; }
bool DecodeHeader(const QByteArray& buffer);
void EncodeHeader(bool fin,bool rsv1,bool rsv2,bool rsv3,quint8 opcode,bool hasmask,quint8 length);
QByteArray GetByteArray();
};
class DataFrame : public QObject
{
public:
DataFrame(const QByteArray& buffer);
DataFrame(const QString& content);
~DataFrame();
QByteArray GetByteArray();
QString Text();
DataFrameHeader Header() { return header_; }
private:
DataFrameHeader header_;
QByteArray extend_;
QByteArray mask_;
QByteArray content_;
void Mask(QByteArray& data, const QByteArray& mask);
};
#endif // DATAFRAME_H
DataFrame.cpp
#include <QByteArray>
#include <QTextCodec>
#include "DataFrame.h"
bool DataFrameHeader::DecodeHeader(const QByteArray& buffer) {
if (buffer.size() < 2) return false;
//第一个字节
fin_ = (buffer[0] & 0x80) == 0x80;
rsv1_ = (buffer[0] & 0x40) == 0x40;
rsv2_ = (buffer[0] & 0x20) == 0x20;
rsv3_ = (buffer[0] & 0x10) == 0x10;
op_code_ = buffer[0] & 0x0f;
//第二个字节
mask_code_ = (buffer[1] & 0x80) == 0x80;
payload_length_ = buffer[1] & 0x7f;
return true;
}
void DataFrameHeader::EncodeHeader(bool fin,bool rsv1,bool rsv2,bool rsv3,quint8 opcode,bool hasmask,quint8 length) {
fin_ = fin;
rsv1_ = rsv1;
rsv2_ = rsv2;
rsv3_ = rsv3;
op_code_ = opcode;
mask_code_ = hasmask;
payload_length_ = length;
}
QByteArray DataFrameHeader::GetByteArray() {
QByteArray header_ba(2, '\0');
if (fin_) header_ba[0] = header_ba[0] ^ 0x80;
if (rsv1_) header_ba[0] = header_ba[0] ^ 0x40;
if (rsv2_) header_ba[0] = header_ba[0] ^ 0x20;
if (rsv3_) header_ba[0] = header_ba[0] ^ 0x10;
header_ba[0] = header_ba[0] ^ op_code_;
if (mask_code_) header_ba[1] = header_ba[1] ^ 0x80;
header_ba[1] = header_ba[1] ^ payload_length_;
return header_ba;
}
DataFrame::DataFrame(const QByteArray& buffer) {
header_.DecodeHeader(buffer);
if (header_.PayloadLength() == 126) {
extend_.resize(2);
extend_ = buffer.mid(2, 2);
} else if (header_.PayloadLength() == 127) {
extend_.resize(8);
extend_ = buffer.mid(2, 8);
}
if (header_.HasMask()) {
mask_.resize(4);
mask_ = buffer.mid(2 + extend_.size(), 4);
}
if (extend_.size() == 0) {
content_.resize(header_.PayloadLength());
content_ = buffer.mid(2 + extend_.size() + mask_.size(), content_.size());
} else if (extend_.size() == 2) {
quint16 contentLength = (quint16)extend_[0]*256 + extend_[1];
content_.resize(contentLength);
content_ = buffer.mid(2 + extend_.size() + mask_.size(), contentLength);
} else {
quint64 contentLength = 0;
int n = 1;
for (int i=7; i>=0; --i) {
contentLength += (quint64)extend_[i] * n;
n *= 256;
}
content_.resize(contentLength);
content_ = buffer.mid(2 + extend_.size() + mask_.size(), contentLength);
}
if (header_.HasMask())
Mask(content_, mask_);
}
DataFrame::DataFrame(const QString& content)
{
content_ = content.toUtf8();
int length = content_.size();
if (length < 126) {
header_.EncodeHeader(true, false, false, false, 1, false, length);
} else if (length < 65536) {
extend_.resize(2);
header_.EncodeHeader(true, false, false, false, 1, false, 126);
extend_[0] = length / 256;
extend_[1] = length % 256;
} else {
extend_.resize(8);
header_.EncodeHeader(true, false, false, false, 1, false, 127);
int left = length;
int unit = 256;
for (int i = 7; i > 1; i--)
{
extend_[i] = left % unit;
left = left / unit;
if (left == 0)
break;
}
}
}
DataFrame::~DataFrame()
{
}
QByteArray DataFrame::GetByteArray() {
QByteArray ba;
ba += header_.GetByteArray();
ba += extend_;
ba += mask_;
ba += content_;
return ba;
}
QString DataFrame::Text() {
if (header_.OpCode() != 1)
return QString();
return QTextCodec::codecForName("UTF-8")->toUnicode(content_);
}
void DataFrame::Mask(QByteArray& data, const QByteArray& mask) {
for (int i=0; i<data.size(); ++i) {
data[i] = data[i] ^ mask[i % 4];
}
}
main.cpp
#include <QtCore/QCoreApplication>
#include <QTextCodec>
#include "WebSocketServer.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("GBK"));
QTextCodec::setCodecForLocale(QTextCodec::codecForName("GBK"));
QTextCodec::setCodecForTr(QTextCodec::codecForName("GBK"));
WebSocketServer ws(NULL);
ws.StartServer();
return a.exec();
}