05 | Swoole 源码分析之 WebSocket 模块

首发原文链接:Swoole 源码分析之 WebSocket 模块
大家好,我是码农先森。

引言

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它允许客户端和服务器之间进行实时数据传输。

与传统的 HTTP 请求-响应模型不同,WebSocket 可以保持双向通信通道,从而使得服务器能够主动向客户端推送数据。

Swoole 中的 WebSocket 服务

下面这段代码是从Swoole 官方网站上的引用,从代码中可以看出创建了一个 WebScoket 对象且设置对应的 IP 地址及监听端口,同时还设置了四个回调方法处理对应的事件。

最后,调用 $server->start() 真正的启动 WebScoket 服务。

$server = new Swoole\Websocket\Server('127.0.0.1', 9502);$server->on('start', function ($server) {echo "Websocket Server is started at ws://127.0.0.1:9502\n";
});$server->on('open', function($server, $req) {echo "connection open: {$req->fd}\n";
});$server->on('message', function($server, $frame) {echo "received message: {$frame->data}\n";$server->push($frame->fd, json_encode(['hello', 'world']));
});$server->on('close', function($server, $fd) {echo "connection close: {$fd}\n";
});$server->start();

那么接下来,我们就从源码角度来分析 Swoole 对 WebSocket 的实现。

源码拆解

这个函数的主要作用是启动 Server 服务。

static void php_swoole_server_onStart(Server *serv) {// 锁定 Server 对象操作serv->lock();// 从 Server 对象中获取到 onStart 回调函数zval *zserv = (zval *) serv->private_data_2;ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv));auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onStart];...// 通过 zend::function::call 调用 PHP 层注册的 onStart 处理函数,并传递参数if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 1, zserv, nullptr, serv->is_enable_coroutine()))) {php_swoole_error(E_WARNING, "%s->onStart handler error", SW_Z_OBJCE_NAME_VAL_P(zserv));}// 解锁 Server 对象操作serv->unlock();
}

这个函数主要作用是 WebSocket 服务针对客户端建立连接时事件的处理。

void swoole_websocket_onOpen(Server *serv, HttpContext *ctx) {// 通过 session_id 获取与特定客户端连接相关的 Connection 对象Connection *conn = serv->get_connection_by_session_id(ctx->fd);if (!conn) {swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "session[%ld] is closed", ctx->fd);return;}// Server 对象中获取在 PHP 层设置的回调函数 onOpen。zend_fcall_info_cache *fci_cache = php_swoole_server_get_fci_cache(serv, conn->server_fd, SW_SERVER_CB_onOpen);if (fci_cache) {zval args[2];args[0] = *((zval *) serv->private_data_2);args[1] = *ctx->request.zobject;// 通过 zend::function::call 调用 PHP 层注册的 onOpen 处理函数,并传递参数if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, serv->is_enable_coroutine()))) {php_swoole_error(E_WARNING, "%s->onOpen handler error", ZSTR_VAL(swoole_websocket_server_ce->name));serv->close(ctx->fd, false);}}
}

这个函数主要作用是 WebSocket 服务器针对客户端发送消息事件的处理。

int swoole_websocket_onMessage(Server *serv, RecvData *req) {SessionId fd = req->info.fd;uchar flags = 0;zend_long opcode = 0;// 从接收到的数据中获取客户端的 session_id,并根据 session_id 获取对应的端口信息auto port = serv->get_port_by_session_id(fd);if (!port) {return SW_ERR;}zval zdata;char frame_header[2];// 从接收到的数据中解析出 WebSocket 消息的帧头信息和消息内容memcpy(frame_header, &req->info.ext_flags, sizeof(frame_header));php_swoole_get_recv_data(serv, &zdata, req);// 解析出 WebSocket 消息的标志位和操作码flags = frame_header[0];opcode = frame_header[1];// 根据操作码和服务的设置,判断是否需要特殊处理 Close、Ping 或 Pong 类型的消息if ((opcode == WebSocket::OPCODE_CLOSE && !port->open_websocket_close_frame) ||(opcode == WebSocket::OPCODE_PING && !port->open_websocket_ping_frame) ||(opcode == WebSocket::OPCODE_PONG && !port->open_websocket_pong_frame)) {if (opcode == WebSocket::OPCODE_PING) {...}zval_ptr_dtor(&zdata);return SW_OK;}...// Server 对象中获取在 PHP 层设置的回调函数 onMessagezend_fcall_info_cache *fci_cache =php_swoole_server_get_fci_cache(serv, req->info.server_fd, SW_SERVER_CB_onMessage);zval args[2];args[0] = *(zval *) serv->private_data_2;// 构造一个 WebSocket 消息帧的数据结构,并将结果存储在 args[1]php_swoole_websocket_construct_frame(&args[1], opcode, &zdata, flags);zend_update_property_long(swoole_websocket_frame_ce, SW_Z8_OBJ_P(&args[1]), ZEND_STRL("fd"), fd);// 通过 zend::function::call 调用 PHP 层注册的 onMessage 处理函数,并传递相应参数if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, serv->is_enable_coroutine()))) {php_swoole_error(E_WARNING, "%s->onMessage handler error", ZSTR_VAL(swoole_websocket_server_ce->name));serv->close(fd, false);}// 释放 zdata 和 args[1] 占用的内存zval_ptr_dtor(&zdata);zval_ptr_dtor(&args[1]);return SW_OK;
}

这个函数的主要作用是关闭 Server 服务。

void php_swoole_server_onClose(Server *serv, DataHead *info) {...// Server 对象中获取在 PHP 层设置的回调函数 onCloseauto *fci_cache = php_swoole_server_get_fci_cache(serv, info->server_fd, SW_SERVER_CB_onClose);Connection *conn = serv->get_connection_by_session_id(session_id);if (!conn) {return;}// 检查当前的 WebSocket 连接状态是否为非活动状态if (conn->websocket_status != swoole::websocket::STATUS_ACTIVE) {// 获取与当前连接相关的监听端口信息ListenPort *port = serv->get_port_by_server_fd(info->server_fd);// 如果该端口开启了 WebSocket 协议,且设置了 onDisconnect 回调函数if (port && port->open_websocket_protocol &&php_swoole_server_isset_callback(serv, port, SW_SERVER_CB_onDisconnect)) {// 获取 onDisconnect 回调函数fci_cache = php_swoole_server_get_fci_cache(serv, info->server_fd, SW_SERVER_CB_onDisconnect);}}if (fci_cache) {...// 通过 zend::function::call 调用 PHP 层注册的 onDisconnect 处理函数,并传递相应参数if (UNEXPECTED(!zend::function::call(fci_cache, argc, args, nullptr, serv->enable_coroutine))) {php_swoole_error(E_WARNING, "%s->onClose handler error", SW_Z_OBJCE_NAME_VAL_P(zserv));}...}...}

这个函数的作用是断开 WebSocket 客户端的连接,并发送关闭帧。

static PHP_METHOD(swoole_websocket_server, disconnect) {// 从 ZEND_THIS 中获取 Server 对象Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS);...// 清空全局的 WebSocket 缓冲区swoole_websocket_buffer->clear();// 将关闭帧数据打包到 WebSocket 缓冲区中if (WebSocket::pack_close_frame(swoole_websocket_buffer, code, data, length, 0) < 0) {RETURN_FALSE;}// 调用 swoole_websocket_server_close 函数来关闭客户端连接,并返回结果RETURN_BOOL(swoole_websocket_server_close(serv, fd, swoole_websocket_buffer, 1));
}

这个函数的作用是在 WebSocket 服务中关闭客户端连接的操作。

static sw_inline bool swoole_websocket_server_close(Server *serv, SessionId fd, String *buffer, bool real_close) {// 尝试将数据推送给客户端,用于判断是否已经关闭连接bool ret = swoole_websocket_server_push(serv, fd, buffer);if (!ret || !real_close) {return ret;}// 获取到客户端连接相关的 Connection 对象Connection *conn = serv->get_connection_by_session_id(fd);if (conn) {// 将该连接的 websocket_status 改变为 WebSocket::STATUS_CLOSINGconn->websocket_status = WebSocket::STATUS_CLOSING;// 立即关闭连接return serv->close(fd, false);} else {return false;}
}

总结

  • 在 Swoole 中 WebSocket 服务是继承于 Http 服务。
  • 在实际的使用过程中是通过 Http 服务来握手升级成 WebSocket 服务。
  • WebSocket 协议的出现解决了通过传统轮询方式来通信的效率问题。
  • 同时也为 PHP 在双向通信解决方式上提供了新的解决方案。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/789095.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Bash相关

Bash shell是Linux内核与用户之间的解释器程序 变量 自定义变量&#xff0c;名称可以用数字、字母、下划线、不能以数字开头&#xff0c;不能使用特殊符号&#xff0c;等号两边不能有空格 格式&#xff1a;变量名称值 环境变量&#xff0c;由系统提前定义好&#xff0c;使用…

node res.end返回json格式数据

使用 Node.js 内置 http 模块的createServer()方法创建一个新的HTTP服务器并返回json数据&#xff0c;代码如下&#xff1a; const http require(http);const hostname 127.0.0.1; const port 3000;const data [{ name: 测试1号, index: 0 },{ name: 测试2号, index: 1 },…

PhpWord导入试卷

规定word导入格式 1、[单选题][2024][一般]题目1 A.选项1 B.选项2 C.选项3 D.选项4 答案&#xff1a;D 试题图片&#xff08;上传多媒体图片&#xff09;&#xff1a; 分数&#xff1a;2 答案解析&#xff1a; 2、[多选题][2024][困难]题目2 A.选项1 B.选项2 C.选项3 D.选项4 E…

[计算机效率] 格式转换工具:格式工厂

3.14 格式转换工具&#xff1a;格式工厂 格式工厂是一款功能强大的多媒体格式转换软件&#xff0c;可以实现音频、视频、图片等多种格式的转换。它支持几乎所有类型的多媒体格式&#xff0c;包括视频、音频、图片、字幕等&#xff0c;可以轻松实现格式之间的转换&#xff0c;并…

分发饼干(C++ 贪心)

目录 题目需求 贪心算法思想 什么是贪心 贪心算法的使用 贪心算法的优缺点 代码实现 后言 题目需求 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&…

基本环境搭建指南

前端相关 Nodejs 官网下载&#xff1a;https://nodejs.cn/ 网盘下载&#xff1a;https://yun.mllt.cc/s/Rvtm 数据库相关 MySQL https://dev.mysql.com/downloads/mysql/5.7.html navcat https://navicat.com.cn/products redis 官网下载&#xff1a;https://redis.io/docs/ins…

python-文件操作常用功能-2

目录 列出子目录 获取文件属性 列出子目录 如果要列出子目录而不是文件&#xff0c;请使用下面的方法。现在展示如何使用 os.listdir() 和 os.path() : import osbasepath my_directoryfor entry in os.listdir(basepath):if os.path.isdir(os.path.join(basepath,entry)):…

手写红黑树【数据结构】

手写红黑树【数据结构】 前言版权推荐手写红黑树一、理论知识红黑树的特征增加删除 二、手写代码初始-树结点初始-红黑树初始-遍历初始-判断红黑树是否有效查找增加-1.父为黑&#xff0c;直接插入增加-2. 父叔为红&#xff0c;颜色调换增加-3. 父红叔黑&#xff0c;颜色调换&am…

C++ Primer 总结索引 | 第十二章:动态内存

1、到目前为止&#xff0c;我们编写的程序中 所使用的对象 都有着严格定义的生存期。全局对象 在程序启动时分配&#xff0c;在程序结束时 销毁。对于 局部自动对象&#xff0c;当我们进入 其定义所在的程序块时被创建&#xff0c;在 离开块时销毁。局部static对象 在第一次使用…

Qt_Note20_QML_自定义Grid控件与OpacityMask的使用

import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 import QtGraphicalEffects 1.14Window {visible: truewidth: 640height: 480title: qsTr("Hello World")// 自定义Grid控件与OpacityMask的使用Grid {id: gridwidth: 15height: 200co…

在线教育平台项目总结

一、业务流程 1.企业&#xff08;B&#xff09; 内容管理、媒资、缓存、消息、任务调度、搜索 2.个人&#xff08;C&#xff09; 搜索、缓存、订单、支付、学习 二、关键技术 1.缓存三兄弟&#xff1a; 缓存穿透&#xff0c;高并发请求过来之后&#xff0c;查询数据库中存…

升降梯人数识别摄像机

升降梯人数识别摄像机是一种智能监测设备&#xff0c;主要用于实时识别和计算升降梯内乘客的数量。通过搭载先进的图像识别技术和人工智能算法&#xff0c;该设备可以准确监测乘客进出数量&#xff0c;提供重要数据支持和信息反馈&#xff0c;帮助管理人员有效管理升降梯运行&a…

STM32学习和实践笔记(4): 分析和理解GPIO_InitTypeDef GPIO_InitStructure (a)

深入分析及学习一下上面这一段代码的构成与含义。 首先&#xff0c;这个GPIO_InitTypeDef GPIO_InitStructure;其实与int a 是完全类似的语法格式以及含义。 GPIO_InitStructure就相当于a这样一个变量。不过从这个变量的名字可以知道&#xff0c;这是一个用于GPIO初始化的结构…

界面控件DevExtreme JS ASP.NET Core 2024年度产品规划预览(一)

在本文中我们将介绍今年即将发布的v24.1附带的主要特性&#xff0c;这些特性既适用于DevExtreme JavaScript (Angular、React、Vue、jQuery)&#xff0c;也适用于基于DevExtreme的ASP.NET MVC/Core控件。 注意&#xff1a;本文中列出的功能和特性说明官方当前/预计的发展计划&a…

hcip实验4:gre mgre ppp综合实验

实验拓扑: 实验目的&#xff1a; 1.R5为ISP&#xff0c;只能进行IP地址配置&#xff0c;其所有地址均配为公有IP地址 2.R1和R5间使用PPP的PAP认证&#xff0c;R5为主认证方&#xff1b;R2与R5之间使用ppp的CHAP认证&#xff0c;R5为主认证方;R3与R5之间使用HDLC封装; 3.R1、R…

MQ消息队列详解以及MQ重复消费问题

MQ消息队列详解以及MQ重复消费问题 1、解耦2、异步调用3、流量削峰4、MQ重复消费问题&#xff0c;以及怎么解决&#xff1f;4.1、重复消费产生4.2、解决方法&#xff1a; https://blog.csdn.net/qq_44240587/article/details/104630567 核心的就是&#xff1a;解耦、异步、削锋…

当面试官问你插入排序算法,你敢说自己会吗?

算法学习的重要性 在程序员的世界里&#xff0c;算法就如同一座桥梁&#xff0c;连接着问题与解决方案&#xff0c;是实现优秀程序的关键。 掌握算法&#xff0c;就能够在面对各种问题时&#xff0c;找到最合适的解决方法&#xff0c;以最少的时间和空间&#xff0c;实现最优的…

解析基础设施即代码:重新定义云管理

由于现代架构、应用程序接口和相互关联的服务之间的互联性越来越强&#xff0c;云基础设施的复杂性也与日俱增。随着需要管理的云资源数量不断增加&#xff0c;企业开始采用基础设施即代码&#xff08;IaC&#xff09;来解决云应用的复杂性和相互依赖性问题。 IaC 提供各种工具…

《深入探索 Netty 框架:构建高效稳定的网络应用》

大家好&#xff0c;今天我将为大家深入介绍 Netty 框架&#xff0c;并分享一些基于 Java 实现的代码示例。 Netty 是一个非常强大的网络框架&#xff0c;它提供了一种高效、可靠的方式来构建网络应用程序。它具有以下优点&#xff1a; 高性能&#xff1a;通过优化的 IO 处理和线…