强大的 WebSocket 通信

1 篇文章 0 订阅
1 篇文章 0 订阅

WebSocket

第一、二章是一些理论性的知识和设计思路,如果要直接看具体是如何实现的,请点击

一、WebSocket简介

websocket是一种基于HTTP协议的网络通信协议,通过使用双向通信的长连接,可以实现服务端和客户端之间双向通信,具有很好的实时性和可靠性。相比于传统的HTTP等协议,更适用于一些需要实时通信的场景。

要搭建一个websocket服务器,一般需要以下步骤:
1.选择语言和框架如python的Flask、Node.js的Socket.IO、C++和QT的Websockets等,并安装相应的依赖和库文件。
2.编写服务端代码,实现 WebSocket 协议和业务逻辑,处理连接、消息发送和接收、错误处理等。
3.部署服务器,将代码部署在云服务器或者本地。
4.编写客户端代码,设置向服务器发送数据,实现想要的功能。
5.测试服务器,使用浏览器或者 WebSocket 客户端向服务器发送连接请求和消息,检查服务器是否能够正确处理和响应请求。
6.调试优化。

总的来说,要搭建一个满足自身需求的服务器,需要具备一定的编程技能和相关经验,需要认真思考设计和实现细节,才能保证服务器的稳定性和性能。

二、设计理念

由于WebSocket是一种中心化的通信方式,通过设置服务器实现用户之间的通信,而且同时兼容各种不同的编程语言,因此可以编写服务器的规则,实现数据一对一发送、一对多发送和广播;而且还可以通过在服务器设置ID,实现同个应用的多开和并行,应用场景非常广泛。下面我将介绍本次搭建的服务器的设计理念。(以下所有的设计都基于QT的WebSockets,javascript也有测试,只要是面向对象的编程语言应该都通用)
一个websocket服务器需要具备以下基本功能:
1.基本功能:开启和关闭;
2.新用户连接处理和用户退出处理。
3.统计用户ID功能;
4.接收、转发、以及自身发送的功能;
5.服务器关闭或断开时处理。

通过搭建websocket服务器,实现星型拓扑网络结构的通信,且每个节点可以并行多个客户端。结构如下图所示。
page_1

上图中,不同的组可以通过设置类型分辨;同组内的不同用户可以通过设置id分辨。
要完成一次发送,要告诉服务器的信息有以下:
1.发送者是谁?
2.要通过什么方式发送?一对一?一对多?还是广播?
3.如果不是广播的方式,那么要给谁发送?
4.发送的信息是什么?
5.接收方有没有接收到信息?

初步确定了以上需求,那么就可以愉快的搭建我们的服务器了!

三、具体实现

本次我搭建了一个本地两组客户端的服务器,其中一组客户端是用QT编写的,另一组客户端时Web端,主要实现的功能是,QT端通过发送命令,Web端接收命令执行并返回给QT端简单的日志。其实也是项目需求,大多数处理都在QT向Web发送的数据,Web端可以做简单回复,如果你需要更多的Web端向QT端的数据发送功能,可以通过略微增改服务器代码实现。

另外,为什么我只实现两组用户的通信呢?因为我懒。哦不,其实多组用户是差不多的,只需要略微修改服务器的转发规矩即可,可以根据自己的需求为服务器增添任意的规则,我这边两组用户的规则已经调试好了,可以满足我的需求,你有什么需求也得自己修改和调试。接下来介绍的我的服务器搭建框架如下:
page_2

1. 服务端实现

这部分较为复杂,也是这篇博文的精华所在,建议配合完整代码食用。

1.1 服务端UI如下:

在这里插入图片描述

非常简单,本地服务器的搭建只需要设置端口就行了,还有开启、关闭、清空当前日志显示等功能。服务器开启对应的槽函数中,需要具备以下关键语句:

server = new QWebSocketServer("My WebSocket Server", QWebSocketServer::NonSecureMode, this);

if (!server->listen(QHostAddress::Any, Port)){
    ui->plainTextEdit->appendPlainText(QString(current_time.toString()) + "     连接端口失败");
}
else{
    ui->plainTextEdit->appendPlainText(QString(current_time.toString()) + "     连接端口成功!");
}

QObject::connect(server, &QWebSocketServer::newConnection, this, &MainWindow::reply_message);

上述三个步骤分别是在:创建QWebSocketServer服务器对象、连接端口并开始监听、 为新用户连接信号绑定对应的槽函数reply_message()。

新用户连接时,要为用户分配ID,每个ID都是特有的,所以在类的private属性中定义一个int型变量user_id,以及用户数目user_num,另外每次新建用户连接都会创建新的socket对象,需要一个保存这些socket对象的集,可以使用QList创建一个列表m_clients:

private:
    int user_num = 0, user_id = 0;
    QList<QWebSocket *> m_clients;
1.2 槽函数reply_message()中有以下关键步骤:
QWebSocket* my_socket = new QWebSocket();
my_socket = server->nextPendingConnection();
my_socket->setObjectName("用户_" + QString::number(user_id).rightJustified(3, '0'));

connect(my_socket, SIGNAL(textMessageReceived(QString)), this, SLOT(processTextMessage(QString)));
connect(my_socket, SIGNAL(disconnected()), this, SLOT(slot_disconnected()));

上述代码中两个步骤分别是:连接时创建新的对象my_socket,并且通过修改QT的ObjectName属性为每个对象分配特有的ID、绑定接收到消息和连接断开这两个信号的槽函数。

1.2.1 接收到消息时的处理结构如下:
QTime current_time = QTime::currentTime();
QObject *senderObject = sender();
if (message == "send_position"){...}
else if (message == "cesium_position"){...}
else if (message == "get_target_id"){...}
else{...}

相信这一步你应该会很不解,这个if语句为什么要这样分流。前面说的,因为我这个工程一共有两组用户,一个Web端,一个QT端,因为我要在服务器中为这两组不同的设置不同的类型,但是客户端刚建立连接时还没法发送消息,因此,需要在连接之后发送一个确认字,让服务器知道他是哪种类型的用户,所以在客户端刚连接上服务端后要先发送一次确认字再传递信息。对应的就是"send_position"和"cesium_position"两种情形了。判断好之后在他们socket的ObjectName属性最后添加对应的类型字符,如下

senderObject->setObjectName(senderObject->objectName()+="S");	//QT端
senderObject->setObjectName(senderObject->objectName()+="C");	//Web端

设置"get_target_id"情形是因为发送端在进行非广播的发送时,需要知道可以发送的目标都有谁,因此,设置了这一标志,所以我们在正常发送信息时就要注意,不能发送这几个特殊的标记。else里边就是对发送信息的处理了,在其中,进行信息的解析判断和分流,我发送端发送的数据时json形式的,实现框架如下:

QString id = senderObject->objectName().right(4);
if(senderObject->objectName().right(1) == "S"){
    QString msg = message;
    QJsonObject doc = QJsonDocument::fromJson(msg.toUtf8()).object();
    QString tar_id = doc.value("target_id").toString();
    if (tar_id == "0"){}	//广播
    else{}					//发送给对应的目标
}
else if(senderObject->objectName().right(1) == "C"){}	//处理类似于"S"情形

另外,可以通过这句代码得到信号触发的对象,也就是对应发信息的socket对象,QT的这个功能非常好,大大方便了我们的编程。

QObject *senderObject = sender();

有用户断开连接时,需要告知服务器,并从创建的连接列表m_clients中清除这个断开的对象:

m_clients.removeOne(clientSocket);

具体的实现细节可以看本文文末的实现代码或者自行下载代码。跳转到代码

1.3 服务器关闭时,也需要执行一些命令,关键步骤有以下:
for (QWebSocket *socket : m_clients){
        socket->sendTextMessage("sever_close");
        socket->close();
    }
    
server->close();
    delete server;
    server = nullptr;
    
user_id = 0;
user_num = 0;
m_clients.erase(m_clients.begin(), m_clients.end());

以上三个步骤分别是:向所有连接的用户发送"sever_close"信息并且断开连接,关闭服务器、对象地址重置、重置统计信息和列表。

2. QT发送端实现

2.1 UI界面如下:

在这里插入图片描述

看上去很多按钮,其实也没有很复杂,毕竟是客户端,不比服务端那么自动化。使用时,首先绑定,发送确认字,就可以在发送区发送消息了,发送的数据我设置成了json形式。函数名和参数对应Web端要执行的函数和它的参数,可以按按钮“得到所有目标ID”来获取目前连接的Web端ID,发送目标为0时是广播的方式发送,发送ID的规则我设置成了强制三位,就是说,如果你要给ID为4的用户发送,那么你就得写成004,多用户发送时发送的每个ID中间以一个空格间隔。如果不好理解可以看代码,配合理解起来很方便。这里就不细讲了。直接上代码。

2.2 “绑定”按键的槽函数
void MainWindow::on_pushButton_1_released()
{
    QTime current_time = QTime::currentTime();
    client = new QWebSocket();
    QString url = ui->lineEdit_3->text();
    client->open(QUrl(url));
    connect(client, &QWebSocket::textMessageReceived, this, &MainWindow::Receive_Server_Message);
    ui->plainTextEdit_recv->appendPlainText(QString(current_time.toString()) + "  " + "点击 发送确认字 确认身份。");
    connect(client, SIGNAL(disconnected()), this, SLOT(slot_disconnected()));
    ui->pushButton_2->setDisabled(true);
    ui->pushButton_3->setEnabled(true);
    ui->pushButton_1->setDisabled(true);
}
2.3 “发送确认字”按键的槽函数
void MainWindow::on_pushButton_3_released()
{
    client->sendTextMessage("send_position");
    ui->pushButton_2->setEnabled(true);
    ui->pushButton_7->setEnabled(true);
    ui->pushButton_8->setEnabled(true);
    ui->pushButton_3->setDisabled(true);
}
2.4 “发送”按键的槽函数
void MainWindow::on_pushButton_2_released()
{

    QJsonObject my_json, params_json;

    QString func_name = ui->lineEdit_func->text();
    QString args = ui->plainTextEdit_send->toPlainText();
    QString target_id = ui->lineEdit_id->text();
    my_json.insert("target_id", target_id);
    my_json.insert("func_name", func_name);
    my_json.insert("func_params", args);

    QJsonDocument jsonDocument(my_json);
    QString jsonString = jsonDocument.toJson();
    client->sendTextMessage(jsonString);
}
2.5 “解除绑定”按键的槽函数
void MainWindow::on_pushButton_7_released()
{
    QTime current_time = QTime::currentTime();
    ui->pushButton_1->setEnabled(true);
    ui->pushButton_2->setDisabled(true);
    ui->pushButton_3->setDisabled(true);
    ui->pushButton_7->setDisabled(true);
    ui->pushButton_8->setDisabled(true);
    ui->plainTextEdit_recv->appendPlainText(QString(current_time.toString()) + "  " + "解除绑定");

    // 清除之前的连接,避免多次收到数据
    client->close();
    client->deleteLater();
    client = nullptr;
}
2.6 “得到所有目标ID”按键的槽函数
void MainWindow::on_pushButton_8_released()
{
    client->sendTextMessage("get_target_id");
}
2.7 “服务器断开时的处理”
void MainWindow::slot_disconnected()
{
    QTime current_time = QTime::currentTime();
    ui->pushButton_1->setEnabled(true);
    ui->pushButton_2->setDisabled(true);
    ui->pushButton_3->setDisabled(true);
    ui->pushButton_7->setDisabled(true);
    ui->pushButton_8->setDisabled(true);
    ui->plainTextEdit_recv->appendPlainText(QString(current_time.toString()) + "  " + "服务器失去连接,请重新建立连接!");
}

具体工程代码在文末,需要可以自取。跳转到代码

3. Web端实现

web端实现很简单,用javascript语言写,处理连接、断开和有消息传来这三种信号即可,之后将收到的消息解析,执行不同的函数并将参数打印在console控制台。实现代码如下:

let socket = new WebSocket('ws://localhost:7770/');
      socket.addEventListener('open', function (){
          console.log("Cesium连接成功")
          socket.send("cesium_position") 
      })
      socket.addEventListener("close", function(){
          console.log("服务器失去连接")
      })

//操作执行函数
socket.addEventListener('message', function(a){
      try {
        var receive = JSON.parse(a.data);
        console.log("收到发送端的命令: ")
        socket.send("Cesium收到了修改的命令!") 
        FUNCREGISTRY[receive["func_name"]](receive["func_params"]);
      } catch(error){
        console.log("收到其他数据: ")
        console.log(a);
      }
})   

function func_1(json){
  console.log("func_1", json);
}

function func_2(json){
  console.log("func_2", json);
}

function func_3(json){
  console.log("func_3", json);
}

function func_4(json){
  console.log("func_4", json);
}

let FUNCREGISTRY = {
  "func_1": func_1,
  "func_2": func_2,
  "func_3": func_3,
  "func_4": func_4,
}

至此,恭喜你,你的websocket通信服务以及搭建完成,可以愉快的玩耍了。

代码地址

github:

https://github.com/FLBa9762/Websocket-server.git

gitee:

https://gitee.com/flba666/Websocket-server.git
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值