参考:使用 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_OBJECTpublic: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_OBJECTpublic: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 hashQByteArray sha1 = QCryptographicHash::hash(ret.toLatin1(), QCryptographicHash::Sha1);       // 3. Base64 encode the hashsecWebSocketAccept = 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();
}

Qt下简单WebSocket网络聊天服务器相关推荐

  1. 【网络聊天室】——基于socket编程的TCP/UDP网络聊天服务器

    早期网络刚刚普及的时候,给人们印象最深的就是上网聊天,虽然作为一名上世纪的尾巴刚刚出生的我没有经历过,但仍从有所耳闻,那个时期是网络聊天室风靡的年代,全国知名聊天室大家都争破头的想要进去,基于如上和一 ...

  2. JAVA网络编程 -UDP篇(简单实现网络聊天)

    通过UDP实现网络聊天 多线程实现UDP网络聊天 1. 编写UDP发送线程UdpSendThread import java.io.BufferedReader; import java.io.IOE ...

  3. 高并发MINA框架,网络编程(SOCKET)实现,简单的网络聊天DEMO

    MINA线程模型 一个IO Processor线程接管所有Sockets,进行I/O侦听.当某个Socket有I/O事件时,把该Socket委派给 I/O Processor 线程池中一个空闲线程处理 ...

  4. python写一个聊天程序_python实现一个简单的网络聊天程序

    一.Linux Socket 1.Linux Socke基本上就是BSD Socket(伯克利套接字) 伯克利套接字的应用编程接口(API)是采用C语言的进程间通信的库,经常用在计算机网络间的通信.B ...

  5. java简单ai聊天_一个hello/hi的简单的网络聊天程序——JAVA

    1.方案介绍 应用程序通过使用用于描述IP地址和端口的"套接字"-Socket向网络发出请求或应答网络请求.Socket和ServerSocket类库位于Java.net包中.前者 ...

  6. 用java编写一个聊天程序_基于JAVA实现的一个简单的网络聊天程序

    一.Java Socket的概述 1.Socket套接字方便了开发网络应用程序.TCP面向连接的可靠传输协议.具有数据确认和数据重传机制.保证了发送数据一定能到达通信的对方.UPD协议无连接,不可靠的 ...

  7. Qt下简单的文件读取

    此示例只是一个简单的文件的打开,读取,文件内容的加载,望共勉. 界面效果展示 代码实现 dialog.h #ifndef DIALOG_H #define DIALOG_H#include <Q ...

  8. Qt编写网络中转服务器(开源)

    一.前言 用Qt做开发9年了,其中做过好多项目,基于现在web和移动互联网发展如此迅猛,大量的应用场景需要一个网络中转服务器,可以实现手机app或者其他客户端远程回控设备,现在物联网发展非常迅猛,这个 ...

  9. 仿微信的网络聊天室项目开发【完整源码讲解】

    目录 总体开发思路 服务器端 服务器界面设计 建立TCP服务器端通信 建立服务器消息发送输出流 建立服务器消息接收输入流 建立服务器实时消息通信线程 设置服务器通信自由断开 客户端 客户端界面设计 建 ...

最新文章

  1. 飞书携手生态伙伴法大大 齐心守护健康
  2. UVA1601万圣节的早上
  3. mongodb atlas_如何使用MongoDB Atlas将MERN应用程序部署到Heroku
  4. 消息测试服务器,测试统一消息服务器功能
  5. Java虚拟机-垃圾回收器
  6. PostgreSQL学习总结(6)—— PostgreSQL 模式(SCHEMA)详解
  7. python打包安卓的方法_打包发布Python模块的方法详解
  8. Prototype使用$()函数
  9. 如何在HTML中使用JavaScript代码
  10. html删除子元素无效,如何使用JavaScript删除DOM节点的所有子元素?
  11. ppt太大无法上传怎么压缩变小?
  12. 【手机刷机】360 n4s降级039 root刷机(卡刷、线刷等)
  13. ImportError: cannot import name ‘ModelOutput‘ from ‘smplx.body_models‘
  14. laravel sail的坑
  15. 在运行局域网中另一台电脑上的程序时,总是弹出“无法验证发行者,确定要运行此软件吗”
  16. 右手螺旋判断磁感应强度方向_高中物理电流磁场的判断方法是什么?叉乘方向右手螺旋定则如何运用...
  17. 企业网络安全|监控解决方案
  18. 新闻爬虫及爬取结果查询网站的搭建(一)
  19. java实现极简单的 TXT小说阅读器(第二版)
  20. 数学知识—不同数据范围求组合数,例题、思路、代码实现

热门文章

  1. 使用IDEA完成一个SpringBoot的demo
  2. 什么是Alpha和Beta测试?
  3. 我爸是李刚 java_深入理解java继承从“我爸是李刚”讲起
  4. TOEFL 托福综合写作模板【高级版+低级版】
  5. MongoDB之查询文档
  6. 人工智能--语义网络表示法
  7. 计算机组成原理 机器数的浮点表示法
  8. 火车头采集翻页内容_火车头采集器教程:使用分页采集有分页的数据
  9. R语言怎么写积分_R语言入门的基本操作(1)
  10. 区块链学习(8) EOS环境安装和智能合约部署实战(绝对干货!)