Qt下简单WebSocket网络聊天服务器

参考:使用 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();
}

 

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
您好!对于Qt WebSocket服务器端实现,您可以按照以下步骤进行操作: 1. 导入Qt WebSocket模块:在您的Qt项目中,在.pro文件中添加`QT += websockets`。 2. 创建一个WebSocket服务器:使用`QWebSocketServer`类创建一个服务器对象,并指定要监听的端口号。 ```cpp QWebSocketServer server(QStringLiteral("WebSocket Server"), QWebSocketServer::NonSecureMode, this); if (server.listen(QHostAddress::Any, 1234)) { qDebug() << "Server started on port" << server.serverPort(); } ``` 3. 处理新连接:当有新的连接请求时,您需要为每个客户端创建一个`QWebSocket`对象,并将其添加到管理列表中。 ```cpp QList<QWebSocket*> clientConnections; connect(&server, &QWebSocketServer::newConnection, [&]() { QWebSocket* socket = server.nextPendingConnection(); clientConnections.append(socket); // 处理连接事件,例如接收消息、断开连接等 connect(socket, &QWebSocket::textMessageReceived, this, [&](const QString& message) { qDebug() << "Received message:" << message; // 在这里处理接收到的消息 }); connect(socket, &QWebSocket::disconnected, this, [&]() { QWebSocket* socket = qobject_cast<QWebSocket*>(sender()); if (socket) { clientConnections.removeOne(socket); socket->deleteLater(); } }); }); ``` 4. 发送消息给客户端:可以使用`QWebSocket`的`sendTextMessage`方法向客户端发送消息。 ```cpp void sendMessageToClients(const QString& message) { for (QWebSocket* socket : clientConnections) { socket->sendTextMessage(message); } } ``` 5. 关闭服务器:如果需要关闭服务器,您可以调用`QWebSocketServer`的`close`方法,并断开与所有客户端的连接。 ```cpp void closeServer() { server.close(); for (QWebSocket* socket : clientConnections) { socket->close(); } } ``` 这是一个基本的Qt WebSocket服务器端的示例,您可以根据您的实际需求进行修改和扩展。希望对您有所帮助!如果您有更多问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值