首页
下载
文档
社区
视频
捐赠
源代码
赞助商
AOT 编译器
AI 助理
商业产品
PHP AOT 原生编译器
Swoole-Compiler 代码加密器
CRMEB 新零售社交电商系统
登录
注册
全部
提问
分享
讨论
建议
公告
开发框架
CodeGalaxy
发表新帖
reverb扩展-laravel-swoole-reverb
# 开篇 最近做了这个扩展包,学习了pusher协议标准。因此也做了一个合集,有兴趣可以看下[Laravel11 从0开发 Swoole-Reverb 扩展包](https://www.cnblogs.com/YangJieCheng/collections/26441) # 关于laravel-swoole-reverb [laravel-swoole-reverb](https://github.com/jiechengyang/laravel-swoole-reverb)是一个将 Swoole 与 Laravel Reverb 集成的包,采用php8.2版本编写,可用于高性能的 WebSocket 和 Pub/Sub 消息传递。这个包依赖 Laravel Reverb ,通过提供swoole:reverb start 操作来替代 reverb:start.同时所说的替代,指的是可以使用swoole:reverb start来运行swoole的服务,但同时也保留了reverb的所有服务。开发者选择一个进行。 swoole reverb 将reactPHP换成swoole并且swoole reverb高度复用reverb组件,因此,大家可以放心使用,目前经我测试,public private channel 正常,立即发送和队列发送广播正常,echojs 监听正常。欢迎大家使用哈,有问题提 Issue。 # 知识点分享 在实现多进程的过程中,我也加深学习了一些基础知识,这里还是要说:一定要看完swoole 基础知识说的内容,现在有AI后,也更加让你能够理解。话不多说,赶紧分享: ## fork 子进程后,子进程到底复制父进程的哪些资源,同时哪些资源又不能复制? 我觉得这个问题很有味道,了解这个后,我们在面对多进程、协程编程遇到的心智问题,会对我们有所帮助。然后在放出ai的总结内容前,我建议 朋友们可以去看看c++教程网出的socket编程系列视频(我也正在看,b站可以找到)。对于php go 等开发者,会有很大帮助,而且,我保证你不会c/c++ 也能看懂的。 为啥我觉得有用哈,因为下面会讲到fork 的子进程复制文件描述符是引用计数的策略,这个点在socket编程视频里面讲多进程回射服务器的时候也提到了:fork的子进程负责处理accept 后连接套接字,同时父进程使用close(conn)关闭连接套接字,就会说到引用计数-1 在 Unix/Linux 系统中,使用 `fork()` 创建子进程时,子进程会继承父进程的大部分资源,但某些资源不能直接复制。以下是具体的分类: ### **子进程会复制的资源** 1. **代码段(Text Segment)**: - 子进程与父进程共享相同的程序代码,但拥有独立的执行上下文。 2. **数据段(Data Segment)**: - 包括全局变量、静态变量的副本。子进程对这些变量的修改不会影响父进程。 3. **堆(Heap)**: - 复制父进程的堆数据,但现代操作系统通常使用**写时复制(Copy-On-Write, COW)**技术,在子进程修改数据之前,共享相同的物理内存。 4. **栈(Stack)**: - 复制父进程的栈,包括局部变量、函数调用帧等,但与堆一样,通常使用**写时复制**。 5. **文件描述符表(File Descriptors)**: - 继承父进程的打开文件描述符(包括文件、管道、套接字),文件偏移量是共享的。 6. **信号处理方式**: - 继承父进程的信号处理配置(如 `SIG_IGN` 或 `SIG_DFL`),但某些信号(如 `SIGCHLD`)的状态可能会有所不同。 7. **当前工作目录**: - 继承父进程的当前工作目录(`cwd`)。 8. **环境变量(Environment Variables)**: - 复制父进程的环境变量。 9. **资源限制(Resource Limits, `ulimit`)**: - 继承父进程的资源限制,如 CPU 时间、文件大小等。 10. **进程组和会话(Process Group and Session)**: - 子进程继承父进程的进程组 ID(PGID)和会话 ID(SID)。 --- ### **子进程不会复制或有特殊行为的资源** 1. **进程 ID(PID)**: - 子进程会获得一个新的 PID,而不是继承父进程的 PID。 2. **父进程 ID(PPID)**: - 子进程的 `PPID` 被设置为创建它的父进程的 PID。 3. **文件锁(File Locks)**: - `fcntl()` 文件锁不会继承,需要子进程重新加锁。 4. **挂起的信号(Pending Signals)**: - 父进程的挂起信号不会传递给子进程。 5. **线程(Threads)**: - `fork()` 仅复制调用它的线程,其他线程不会复制。推荐使用 `fork()` 时避免多线程,否则可能导致同步问题。 6. **IPC 机制(Inter-Process Communication)**: - **共享内存(Shared Memory, `shm`)**:子进程仍可访问,但由于共享性质,数据不会独立。 - **消息队列(Message Queues)和信号量(Semaphores)**:保持不变,但某些同步行为可能需要重新初始化。 7. **打开的 `socket` 的连接状态**: - 继承 `socket`,但对于 TCP 连接,某些协议级别的状态可能需要重新初始化。 8. **计时器(Timers)**: - `setitimer()` 和 `alarm()` 设定的定时器在子进程中不会继承。 --- ### **特殊情况** - **写时复制(Copy-On-Write, COW)** - 现代操作系统在 `fork()` 时通常不会真正复制堆和栈,而是采用 COW 机制,直到子进程修改数据时才进行真正的拷贝。 - **`vfork()`** - `vfork()` 直接共享父进程的地址空间,子进程执行 `exec()` 或 `exit()` 之前,父进程会被挂起。相比 `fork()`,`vfork()` 在某些情况下可以提高性能,但需要谨慎使用。 --- ### **总结** | 资源 | 继承/复制 | 备注 | |------|---------|------| | 代码段 | 共享 | 只读共享 | | 数据段 | 复制(COW) | 修改时才真正复制 | | 堆(Heap) | 复制(COW) | 修改时才真正复制 | | 栈(Stack) | 复制(COW) | 修改时才真正复制 | | 文件描述符 | 继承 | 共享文件偏移量 | | 信号处理方式 | 继承 | `SIG_IGN` 和 `SIG_DFL` 继承 | | 环境变量 | 复制 | 独立 | | 进程 ID(PID) | 新分配 | | | 父进程 ID(PPID) | 继承 | | | 线程 | 不继承 | 仅复制 `fork()` 调用的线程 | | 挂起信号 | 不继承 | | | IPC(共享内存等) | 共享 | 共享内存仍可访问 | | 进程组 & 会话 | 继承 | | | 文件锁 | 不继承 | `fcntl()` 需要重新加锁 | 因此,在 `fork()` 之后,父子进程是独立的进程,但由于 COW 和共享的文件描述符、信号处理等机制,它们仍然在某些方面有联系。 是的,关于 **文件描述符(File Descriptors)**,除了继承父进程的打开文件描述符之外,还有一个很重要的细节——**引用计数**。 ### **文件描述符的继承机制** 1. **子进程继承父进程的文件描述符表**: - `fork()` 之后,父进程和子进程的文件描述符表是**独立的拷贝**,但它们的文件描述符指向同一个**文件描述符表项(File Table Entry)**。 - 这个文件表项记录了**文件偏移量(File Offset)、打开标志(Flags)和文件引用计数(Reference Count)**。 2. **文件引用计数(Reference Count)**: - **文件引用计数会增加**,因为 `fork()` 之后,子进程的文件描述符仍然指向相同的文件结构。 - 只有当所有进程(父进程和子进程)都关闭(`close(fd)`)这个文件描述符后,文件才会真正关闭。 3. **共享文件偏移量**: - 由于父子进程的文件描述符指向相同的文件表项,它们会共享文件的**读写偏移量**(`lseek()` 影响彼此)。 - 如果父进程 `read()` 或 `write()`,会影响子进程的文件读写位置,反之亦然。 4. **文件状态标志的继承**: - `O_APPEND`、`O_NONBLOCK` 等文件标志(通过 `fcntl()` 设置)也是**共享的**。 ### **示例:文件描述符共享偏移量** ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> int main() { int fd = open("test.txt", O_RDWR | O_CREAT, 0644); if (fd < 0) { perror("open"); exit(1); } write(fd, "Hello", 5); // 写入 5 字节 pid_t pid = fork(); if (pid < 0) { perror("fork"); exit(1); } if (pid == 0) { // 子进程 write(fd, " Child", 6); // 继续写入 close(fd); exit(0); } else { // 父进程 wait(NULL); // 等待子进程结束 write(fd, " Parent", 7); // 继续写入 close(fd); } return 0; } ``` **预期输出 (`test.txt` 内容)**: ``` Hello Child Parent ``` **说明**: - 父进程 `write(fd, "Hello", 5)` 之后,文件偏移量变成 `5`。 - 子进程 `write(fd, " Child", 6)`,偏移量变成 `11`。 - 父进程等待子进程完成,再 `write(fd, " Parent", 7)`,最终文件内容是 `"Hello Child Parent"`。 - **由于共享偏移量,读写操作会影响对方!** ### **避免共享影响的方法** - **使用 `dup()` 或 `dup2()`**: - 这样可以创建一个新的文件表项,避免共享偏移量。 - **重新打开文件**: ```c int fd = open("test.txt", O_RDWR); ``` - 这样父子进程拥有不同的 `fd`,文件偏移量互不影响。 ### **总结** | 继承属性 | 共享情况 | |----------|---------| | 文件描述符(fd) | 继承 | | 文件引用计数 | **增加**(直到 `close()` 释放) | | 文件偏移量 | **共享**(读写影响对方) | | 文件状态标志 | **共享**(O_APPEND, O_NONBLOCK) | 所以,`fork()` 后子进程并不拥有文件的独立副本,而是共享同一个文件表项,只是它的文件描述符表是独立的。因此,如果父子进程都需要独立操作文件,应该使用 `dup()` 或者 `open()` 重新获取 `fd`。 # 总结 这一篇就到这,后续我会继续写具体实现过程的分享,同时,如果大家觉得框架太绕了的话,可以在我的laravel-swoole-reverb里面找到`swoole-reverb.php`这个单文件,文件基本实现了整个过程,目前还差http pusher 接口的具体逻辑没写,后面也会继续完善(看下面第二张图)  
发布于1年前 · 0 次浏览 · 来自
分享
wanzij
# 开篇 最近做了这个扩展包,学习了pusher协议标准。因此也做了一个合集,有兴趣可以看下[Laravel11 从0开发 Swoole-Reverb 扩展包](https://www.cnblogs.com/YangJieCheng/collections/26441) # 关于laravel-swoole-reverb [laravel-swoole-reverb](https://github.com/jiechengyang/laravel-swoole-reverb)是一个将 Swoole 与 Laravel Reverb 集成的包,采用php8.2版本编写,可用于高性能的 WebSocket 和 Pub/Sub 消息传递。这个包依赖 Laravel Reverb ,通过提供swoole:reverb start 操作来替代 reverb:start.同时所说的替代,指的是可以使用swoole:reverb start来运行swoole的服务,但同时也保留了reverb的所有服务。开发者选择一个进行。 swoole reverb 将reactPHP换成swoole并且swoole reverb高度复用reverb组件,因此,大家可以放心使用,目前经我测试,public private channel 正常,立即发送和队列发送广播正常,echojs 监听正常。欢迎大家使用哈,有问题提 Issue。 # 知识点分享 在实现多进程的过程中,我也加深学习了一些基础知识,这里还是要说:一定要看完swoole 基础知识说的内容,现在有AI后,也更加让你能够理解。话不多说,赶紧分享: ## fork 子进程后,子进程到底复制父进程的哪些资源,同时哪些资源又不能复制? 我觉得这个问题很有味道,了解这个后,我们在面对多进程、协程编程遇到的心智问题,会对我们有所帮助。然后在放出ai的总结内容前,我建议 朋友们可以去看看c++教程网出的socket编程系列视频(我也正在看,b站可以找到)。对于php go 等开发者,会有很大帮助,而且,我保证你不会c/c++ 也能看懂的。 为啥我觉得有用哈,因为下面会讲到fork 的子进程复制文件描述符是引用计数的策略,这个点在socket编程视频里面讲多进程回射服务器的时候也提到了:fork的子进程负责处理accept 后连接套接字,同时父进程使用close(conn)关闭连接套接字,就会说到引用计数-1 在 Unix/Linux 系统中,使用 `fork()` 创建子进程时,子进程会继承父进程的大部分资源,但某些资源不能直接复制。以下是具体的分类: ### **子进程会复制的资源** 1. **代码段(Text Segment)**: - 子进程与父进程共享相同的程序代码,但拥有独立的执行上下文。 2. **数据段(Data Segment)**: - 包括全局变量、静态变量的副本。子进程对这些变量的修改不会影响父进程。 3. **堆(Heap)**: - 复制父进程的堆数据,但现代操作系统通常使用**写时复制(Copy-On-Write, COW)**技术,在子进程修改数据之前,共享相同的物理内存。 4. **栈(Stack)**: - 复制父进程的栈,包括局部变量、函数调用帧等,但与堆一样,通常使用**写时复制**。 5. **文件描述符表(File Descriptors)**: - 继承父进程的打开文件描述符(包括文件、管道、套接字),文件偏移量是共享的。 6. **信号处理方式**: - 继承父进程的信号处理配置(如 `SIG_IGN` 或 `SIG_DFL`),但某些信号(如 `SIGCHLD`)的状态可能会有所不同。 7. **当前工作目录**: - 继承父进程的当前工作目录(`cwd`)。 8. **环境变量(Environment Variables)**: - 复制父进程的环境变量。 9. **资源限制(Resource Limits, `ulimit`)**: - 继承父进程的资源限制,如 CPU 时间、文件大小等。 10. **进程组和会话(Process Group and Session)**: - 子进程继承父进程的进程组 ID(PGID)和会话 ID(SID)。 --- ### **子进程不会复制或有特殊行为的资源** 1. **进程 ID(PID)**: - 子进程会获得一个新的 PID,而不是继承父进程的 PID。 2. **父进程 ID(PPID)**: - 子进程的 `PPID` 被设置为创建它的父进程的 PID。 3. **文件锁(File Locks)**: - `fcntl()` 文件锁不会继承,需要子进程重新加锁。 4. **挂起的信号(Pending Signals)**: - 父进程的挂起信号不会传递给子进程。 5. **线程(Threads)**: - `fork()` 仅复制调用它的线程,其他线程不会复制。推荐使用 `fork()` 时避免多线程,否则可能导致同步问题。 6. **IPC 机制(Inter-Process Communication)**: - **共享内存(Shared Memory, `shm`)**:子进程仍可访问,但由于共享性质,数据不会独立。 - **消息队列(Message Queues)和信号量(Semaphores)**:保持不变,但某些同步行为可能需要重新初始化。 7. **打开的 `socket` 的连接状态**: - 继承 `socket`,但对于 TCP 连接,某些协议级别的状态可能需要重新初始化。 8. **计时器(Timers)**: - `setitimer()` 和 `alarm()` 设定的定时器在子进程中不会继承。 --- ### **特殊情况** - **写时复制(Copy-On-Write, COW)** - 现代操作系统在 `fork()` 时通常不会真正复制堆和栈,而是采用 COW 机制,直到子进程修改数据时才进行真正的拷贝。 - **`vfork()`** - `vfork()` 直接共享父进程的地址空间,子进程执行 `exec()` 或 `exit()` 之前,父进程会被挂起。相比 `fork()`,`vfork()` 在某些情况下可以提高性能,但需要谨慎使用。 --- ### **总结** | 资源 | 继承/复制 | 备注 | |------|---------|------| | 代码段 | 共享 | 只读共享 | | 数据段 | 复制(COW) | 修改时才真正复制 | | 堆(Heap) | 复制(COW) | 修改时才真正复制 | | 栈(Stack) | 复制(COW) | 修改时才真正复制 | | 文件描述符 | 继承 | 共享文件偏移量 | | 信号处理方式 | 继承 | `SIG_IGN` 和 `SIG_DFL` 继承 | | 环境变量 | 复制 | 独立 | | 进程 ID(PID) | 新分配 | | | 父进程 ID(PPID) | 继承 | | | 线程 | 不继承 | 仅复制 `fork()` 调用的线程 | | 挂起信号 | 不继承 | | | IPC(共享内存等) | 共享 | 共享内存仍可访问 | | 进程组 & 会话 | 继承 | | | 文件锁 | 不继承 | `fcntl()` 需要重新加锁 | 因此,在 `fork()` 之后,父子进程是独立的进程,但由于 COW 和共享的文件描述符、信号处理等机制,它们仍然在某些方面有联系。 是的,关于 **文件描述符(File Descriptors)**,除了继承父进程的打开文件描述符之外,还有一个很重要的细节——**引用计数**。 ### **文件描述符的继承机制** 1. **子进程继承父进程的文件描述符表**: - `fork()` 之后,父进程和子进程的文件描述符表是**独立的拷贝**,但它们的文件描述符指向同一个**文件描述符表项(File Table Entry)**。 - 这个文件表项记录了**文件偏移量(File Offset)、打开标志(Flags)和文件引用计数(Reference Count)**。 2. **文件引用计数(Reference Count)**: - **文件引用计数会增加**,因为 `fork()` 之后,子进程的文件描述符仍然指向相同的文件结构。 - 只有当所有进程(父进程和子进程)都关闭(`close(fd)`)这个文件描述符后,文件才会真正关闭。 3. **共享文件偏移量**: - 由于父子进程的文件描述符指向相同的文件表项,它们会共享文件的**读写偏移量**(`lseek()` 影响彼此)。 - 如果父进程 `read()` 或 `write()`,会影响子进程的文件读写位置,反之亦然。 4. **文件状态标志的继承**: - `O_APPEND`、`O_NONBLOCK` 等文件标志(通过 `fcntl()` 设置)也是**共享的**。 ### **示例:文件描述符共享偏移量** ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> int main() { int fd = open("test.txt", O_RDWR | O_CREAT, 0644); if (fd < 0) { perror("open"); exit(1); } write(fd, "Hello", 5); // 写入 5 字节 pid_t pid = fork(); if (pid < 0) { perror("fork"); exit(1); } if (pid == 0) { // 子进程 write(fd, " Child", 6); // 继续写入 close(fd); exit(0); } else { // 父进程 wait(NULL); // 等待子进程结束 write(fd, " Parent", 7); // 继续写入 close(fd); } return 0; } ``` **预期输出 (`test.txt` 内容)**: ``` Hello Child Parent ``` **说明**: - 父进程 `write(fd, "Hello", 5)` 之后,文件偏移量变成 `5`。 - 子进程 `write(fd, " Child", 6)`,偏移量变成 `11`。 - 父进程等待子进程完成,再 `write(fd, " Parent", 7)`,最终文件内容是 `"Hello Child Parent"`。 - **由于共享偏移量,读写操作会影响对方!** ### **避免共享影响的方法** - **使用 `dup()` 或 `dup2()`**: - 这样可以创建一个新的文件表项,避免共享偏移量。 - **重新打开文件**: ```c int fd = open("test.txt", O_RDWR); ``` - 这样父子进程拥有不同的 `fd`,文件偏移量互不影响。 ### **总结** | 继承属性 | 共享情况 | |----------|---------| | 文件描述符(fd) | 继承 | | 文件引用计数 | **增加**(直到 `close()` 释放) | | 文件偏移量 | **共享**(读写影响对方) | | 文件状态标志 | **共享**(O_APPEND, O_NONBLOCK) | 所以,`fork()` 后子进程并不拥有文件的独立副本,而是共享同一个文件表项,只是它的文件描述符表是独立的。因此,如果父子进程都需要独立操作文件,应该使用 `dup()` 或者 `open()` 重新获取 `fd`。 # 总结 这一篇就到这,后续我会继续写具体实现过程的分享,同时,如果大家觉得框架太绕了的话,可以在我的laravel-swoole-reverb里面找到`swoole-reverb.php`这个单文件,文件基本实现了整个过程,目前还差http pusher 接口的具体逻辑没写,后面也会继续完善(看下面第二张图)  
赞
0
收藏
提问
分享
讨论
建议
公告
开发框架
CodeGalaxy
登录
后参与评论
评论
还没有评论!