首发原文链接: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 层设置的回调函数 onMessage
zend_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 层设置的回调函数 onClose
auto *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_CLOSING
conn->websocket_status = WebSocket::STATUS_CLOSING;
// 立即关闭连接
return serv->close(fd, false);
} else {
return false;
}
}

总结

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

Swoole 源码分析之 WebSocket 模块的更多相关文章

  1. 【转】Spark源码分析之-deploy模块

    原文地址:http://jerryshao.me/architecture/2013/04/30/Spark%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B- ...

  2. ADB 源码分析(一) ——ADB模块简述【转】

    ADB源码分析(一)——ADB模块简述 1.Adb 源码路径(system/core/adb). 2.要想很快的了解一个模块的基本情况,最直接的就是查看该模块的Android.mk文件,下面就来看看a ...

  3. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  4. elasticsearch源码分析之search模块(server端)

    elasticsearch源码分析之search模块(server端) 继续接着上一篇的来说啊,当client端将search的请求发送到某一个node之后,剩下的事情就是server端来处理了,具体 ...

  5. elasticsearch源码分析之search模块(client端)

    elasticsearch源码分析之search模块(client端) 注意,我这里所说的都是通过rest api来做的搜索,所以对于接收到请求的节点,我姑且将之称之为client端,其主要的功能我们 ...

  6. (一) Mybatis源码分析-解析器模块

    Mybatis源码分析-解析器模块 原创-转载请说明出处 1. 解析器模块的作用 对XPath进行封装,为mybatis-config.xml配置文件以及映射文件提供支持 为处理动态 SQL 语句中的 ...

  7. 【转】Spark源码分析之-scheduler模块

    原文地址:http://jerryshao.me/architecture/2013/04/21/Spark%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B- ...

  8. Spark源码分析之-Storage模块

    原文链接:http://jerryshao.me/architecture/2013/10/08/spark-storage-module-analysis/ Background 前段时间琐事颇多, ...

  9. jQuery 源码分析(十一) 队列模块 Queue详解

    队列是常用的数据结构之一,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队).特点是先进先出,最先插入的元素最先被删除. 在jQuery内部,队列模块为动画模块提供基 ...

  10. jQuery 源码分析(二) 入口模块

    jQuery返回的对象本质上是一个JavaScript对象,而入口模块则可以保存对应的节点的引用,然后供其它模块操作 我们创建jQuery对象时可以给jQuery传递各种不同的选择器,如下: fals ...

随机推荐

  1. openGauss社区入门(openGauss-常用数据类型)

    常用数据类型 数据类型 说明 布尔类型 boolean true/'1'/'yes' false/'0'/'no' 数值类型 smallint 两字节 int 4 字节 bigint 8 字节 dou ...

  2. HDC2021技术分论坛:OpenHarmony驱动框架解读和开发实践

    转自:OpenAtom OpenHarmony    作者:yuanbo,华为高级工程师 在IoT时代下,终端设备差异较大.形态各异.尺寸各异.交互方式各异,解决设备适配问题无疑是实现万物互联的一个关 ...

  3. Spring Cloud Config:外部集中化配置管理

    Spring Cloud Config:外部集中化配置管理 SpringCloud学习教程 SpringCloud Spring Cloud Config 可以为微服务架构中的应用提供集中化的外部配置 ...

  4. 重新整理数据结构与算法(c#)—— 算法套路二分法[二十四]

    前言 前面写过二分法使用的是递归手法,然后该节才有不递归的方式执行. 二分法的时间复杂度为log(2)n.空间复杂度为:1. 正文 代码: static void Main(string[] args ...

  5. 6个高级Vue3知识技巧

    Vue 3是一个非常流行的前端框架,广泛应用于大型互联网企业和个人项目. 虽然我们已经熟悉了一些常见的 Vue 3 知识,但还有一些不太常见但实用性很强的点可以帮助我们进一步优化和提升 Vue 3 应 ...

  6. redis+lua脚本实现接口限流

    写在前面 在多线程的情况下对一个接口进行访问,如果访问次数过大,且没有缓存存在的情况下大量的请求打到数据库可能会存在数据库宕机,从而造成服务的不可用性.往往我们需要对其进行限流操作用来保证服务的高可用 ...

  7. JVM简明笔记4:垃圾回收

    1 垃圾回收相关算法 垃圾回收器首先要做的就是,判断一个对象是存活状态还是死亡状态,死亡的对象将会被标识为垃圾数据并等待收集器进行清除. 判断一个对象是否为死亡状态的常用算法有两个:引用计数器算法 . ...

  8. 面试题45(Java)-把数组排成最小的数(中等)

    题目: 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个. 示例 1: 输入: [10,2] 输出: "102" 示例 2: 输入: [ ...

  9. 合阔智云核心生产系统切换到服务网格 ASM 的落地实践

    简介: 合阔智云提供了从全渠道交易管理到订单履约再到门店供应链完整的餐饮零售连锁解决方案,整个方案采取微服务设计,并深度使用了 Kubernetes 作为生产调度平台. 作者:刘如鸿   背景 合阔智 ...

  10. CPU静默数据错误:存储系统数据不丢不错的设计思考

    简介: 对于数据存储系统来说,保障数据不丢不错是底线,也是数据存储系统最难的部分.据统计,丢失数据中心10天的企业,93%会在1年内破产.那么如果想要做到数据不丢不错,我们可以采取怎样的措施呢? 作者 ...