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 层设置的回调函数 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 在双向通信解决方式上提供了新的解决方案。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C语言基础】:自定义类型(二) -->联合和枚举

文章目录 一、联合体1.1 联合体类型的声明1.2 联合体的特点1.3 相同成员的结构体和联合体对比1.4 联合体大小的计算1.5 联合体练习 二、枚举类型2.1 枚举类型的声明2.2 枚举的优点 书山有路勤为径&#xff0c;学海无涯苦作舟。 创作不易&#xff0c;宝子们&#xff01;如果这篇…

docker + miniconda + python 环境安装与迁移

本文主要列出从安装到安装python环境到迁移环境的整体步骤。 windows与linux之间进行测试。 一、docker 安装和测试 【linux端】 可以参考其他教程&#xff0c;不在此赘述&#xff0c;以windows端举例。 【windows端】 我的是windows10 家庭中文版&#xff0c;docker 安装和…

HWOD:字符串逆序

一、知识点 回车键的ASCII码是10 fgets()会读入回车键&#xff0c;也就是说字符串最后一个字符之后是回车键&#xff0c;再之后才是\0 字符串逆序不一定要真正的逆序&#xff0c;只需倒着从最后一个字符打印到第一个字符即可 二、题目 1、描述 将一个字符串str的内容颠倒…

问题2-前端json数组数据转换成csv文件

代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>将 JSON 数据导出为 CSV 文件</title> …

市场复盘总结 20240401

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 二进三&#xff1a; 进级率中 38% 最常用…

绝地求生:[更新周报] 4/3 不停机更新:无上新、众多物品和活动即将下架!

大家好&#xff0c;我闲游盒&#xff1b;本周三4月3号&#xff0c;绝地求生不会有停机时间&#xff0c;大家可以随便玩~ ▲本周可选地图池 亚服/东南亚服&#xff1a;艾伦格、荣都、萨诺、米拉玛、帕拉莫&#xff1b; 日服/韩服KAKAO服&#xff1a;艾伦格、泰戈、萨诺、荣都、…

Spring面试常见问题

目录 1、为什么要用Spring框架&#xff1f;2、有了spring为什么又出现了 springboot&#xff1f;3、springboot出现后为什么又出现了spring cloud&#xff1f;4、SpringBoot自动配置&#xff08;重点&#xff09;5、SpringBoot启动流程&#xff08;重点&#xff09;6、简单谈一…

香港科技大学广州|数据科学与分析学域硕博招生宣讲会—天津大学专场

时间&#xff1a;2024年4月12日&#xff08;星期五&#xff09;14:00 地点&#xff1a;天津大学北洋园校区55楼B204 报名链接&#xff1a;https://www.wjx.top/vm/Q0cKTUI.aspx# 跨学科研究领域 *数据驱动的人工智能和机器学习 *统计学习和建模 工业和商业分析 *特定行业的数…

80C51实现四种流水灯流水灯 | 自用学习笔记

单个流水灯 #include <reg51.h> #include <intrins.h> // 包含移位库函数的头文件void delay(void) // 定义延时函数 {unsigned char i, j;for(i 0; i < 200; i) // 循环延时&#xff0c;延时约50msfor(j 0; j < 125; j); }void main(void){ unsigned ch…

C++ 注册Nacos

下载源码&#xff1a; git clone GitHub - nacos-group/nacos-sdk-cpp: C client for Nacos 编译源码 cd nacos-sdk-cpp cmake . make 生成库文件 在nacos-sdk-cpp 下 注册nacos 将include 和libnacos-cli.so libnacos-cli-static.a 放入你的工程 如果Nacos服务地址:…

C++ list

文章目录 list的介绍及使用list的介绍list的构造list iterator的使用list capacitylist element accesslist modifiers list模拟实现list节点类list迭代器类list类 list深度剖析list迭代器失效list反向迭代器 list与vector对比 list的介绍及使用 list的介绍 1.list的底层是双向…

yolov5目标检测可视化界面pyside6源码(无登录版)

一、软件简介&#xff1a; 这是基于yolov5-7.0目标检测实现的的可视化目标检测源码 本套项目没有用户登录的功能&#xff0c;如需用户登录版&#xff0c;看另一篇文章&#xff1a;yolov5pyside6登录用户管理目标检测可视化源码_yolov5用户登入功能-CSDN博客 ①程序中图片和图标…

2024年04月IDE流行度最新排名

点击查看最新IDE流行度最新排名&#xff08;每月更新&#xff09; 2024年04月IDE流行度最新排名 顶级IDE排名是通过分析在谷歌上搜索IDE下载页面的频率而创建的 一个IDE被搜索的次数越多&#xff0c;这个IDE就被认为越受欢迎。原始数据来自谷歌Trends 如果您相信集体智慧&am…

02-JDK新特性-泛型

泛型 什么是泛型 泛型是JDK5中引入的特性&#xff0c;它提供了编译时类型安全检测机制&#xff0c;该机制允许在编译是检测到非法的类型。 它的本质是参数化类型&#xff0c;也就是说操作的数据类型被指定为一个参数。 也就是将类型有原来的具体类型参数化&#xff0c;然后在…

MySQL 优化及故障排查

目录 一、mysql 前置知识点 二、MySQL 单实例常见故障 故障一 故障二 故障三 故障四 故障五 故障六 故障七 故障八 三、MySQL 主从故障排查 故障一 故障二 故障三 四、MySQL 优化 1.硬件方面 &#xff08;1&#xff09;关于 CPU &#xff08;2&#xff09;关…

使用虚拟引擎为AR体验提供动力

Powering AR Experiences with Unreal Engine ​​​​​​​ 目录 1. 虚拟引擎概述 2. 虚拟引擎如何为AR体验提供动力 3. 虚拟引擎中AR体验的组成部分是什么&#xff1f; 4. 使用虚拟引擎创建AR体验 5. 虚拟引擎中AR的优化提示 6. 将互动性融入AR与虚拟引擎 7. 在AR中…

Java项目:83 springboot知识管理系统

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本知识管理系统有管理员和用户两个角色。 管理员功能有个人中心&#xff0c;用户管理&#xff0c;文章分类管理&#xff0c;文章信息管理&…

Excel 数据-分列的三个经常用法

Case 1 &#xff1a;有时候数据导出时如果没有电子表格的话&#xff0c;只能导出本地文件&#xff0c;如下图情况&#xff1a; 可以使用数据-分列处理数据&#xff1a; 原来是因为SAP导出数据没有完成的原因&#xff0c;或者关闭Excel重新打开试一下。 重新打开后可以输入了 C…

NoSQL之 Redis配置

目录 关系数据库与非关系型数据库 关系型数据库&#xff1a; ●非关系型数据库 关系型数据库和非关系型数据库区别&#xff1a; &#xff08;1&#xff09;数据存储方式不同 &#xff08;2&#xff09;扩展方式不同 对事务性的支持不同 非关系型数据库产生背景 Redis简介…

C++初阶:5.STL简介(了解)

STL简介&#xff08;了解&#xff09; 一.什么是STL STL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且是一个包罗数据结构与算法的软件框架。 二. STL的版本 原始版本 Alexander Stepan…