php socket网络编程基础知识(四):多进程

说明

  • php在web编程时是不需要考虑多进程的,但整个php流程是涉及到多进程的,只不过nginxphp-fpm帮我们处理好了,我们配置他们参数时就需要设置进程个数相关参数
  • php在多进程涉及到的是PCNTL扩展POSIX扩展,这两个扩展交叉涉及到进程和信号相关,他们只支持Unix平台,windows平台不支持,而且只支持cli模式下运行。
  • php也提供了其它函数来处理多进程的场景,例如exec相关的函数,所以并不是涉及到多进程都需要上方扩展,推荐symfony/process,对php此类相关函数进行的封装和兼容处理,同时支持linux和windows,非常好用的多进程库,一般的中小项目完全满足需求。
  • 我们只从上方扩展中找进程相关的函数来总结,信号相关的在下节中总结

概念

这里涉及到一些linux系统相关的的概念,可能需要先做了解

相关函数

  • PCNTL函数

    • pcntl_fork ( void ) : int
      • 产生子进程分支
      • 此函数后的代码,父子进程都会执行,执行顺序则是随机的。此函数会在父进程和子进程内产生不同的返回值,子进程返回0,父进程则返回子进程的pid,如果失败则返回-1,由于父子进程都会执行后续代码,所以我们一般用if来判定哪个是父进程哪个是子进程。
    • pcntl_wait ( int &$status [, int $options = 0 ] ) : int
      • 用于子进程结束时,回收子进程资源
    • pcntl_waitpid ( int $pid , int &$status [, int $options = 0 ] ) : int
      • pcntl_wait基本一样,但是多了指定pid的参数,当$pid=-1时,相当于pcntl_wait,其它的$pid参数自行查看文档
    • 上面两函数第二个参数$status是个引用,可以获取到子进程结束时的状态信息,通过调用其它函数来解析status具体含义
    • 上面两函数第三个参数则比较费解,除了0外还有两个选项WNOHANGWUNTRACED。上面两个函数默认是阻塞的,直到有子进程结束时,才会有响应动作,如果想不阻塞,可以将$options参数设置为WNOHANG,当没有子进程退出时不再阻塞等待,而是直接返回0,而如果设置为WUNTRACED参数则和设置为0一样还是阻塞,但是除了子进程结束会响应外SIGTTIN, SIGTTOU, SIGTSTP,SIGSTOP这些信号也会响应。
  • POSIX函数

    • posix_getpid ( void ) : int
      • 获取当前进程ID,常用于区分是否为父进程
    • posix_getppid ( void ) : int
      • 获取父进程ID
    • posix_getuid ( void ) : int
      • 获取当前进程的用户ID
    • posix_setuid ( int $uid ) : bool
      • 设置当前进程的用户ID
    • posix_getgid( void ) : int
      • 获取当前进程所属组ID
    • posix_setgid( int $gid ) : bool
      • 设置当前进程组ID
    • posix_getsid ( int $pid ) : int
      • 获取某进程的会话ID,需要注意的是参数需要传递,可以先通过posix_getpid获取pid参数,再获取sid
    • posix_setsid( void ) : int
      • 设置当前进程为会话的领头进程,需要注意参数,只能设置当前的进程
    • posix_getpwuid( int $uid ) : array
      • 根据用户ID获取此用户的信息,例如用户的名称、密码,组等信息
    • posix_getpwnam ( string $username ) : array
      • 根据用户名获取此用户的信息,例如用户的ID、密码、组等信息
    • posix_getgrgid ( int $gid ) : array
      • 根据组ID获取此组的信息,例如组名称、密码、成员等信息
    • posix_getgrnam( string $name ) : array
      • 根据组名称获取此组的信息,例如组ID、密码、成员等信息
    • posix_initgroups ( string $name , int $base_group_id ) : bool
      • 初始化组清单,将$name所属的附加组也加入到此进程的组权限中。
      • 这个函数不太好理解,因为和setgid有些类似。用户是有附加组的,同一个用户可能属于多个组,setuid只是设置用户ID,setgid是设置组ID,但用户ID可能同时还有其它组的权限,要想获取其它组的权限,则需要运行此命令。

相关代码

  • 创建子进程
    $pid = pcntl_fork();
    //父进程和子进程都会执行下面代码
    if ($pid == -1) {
        //错误处理:创建子进程失败时返回-1.
        die(‘could not fork‘);
    } else if ($pid) {
        //父进程会得到子进程号,所以这里是父进程执行的逻辑
        pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程。
    } else {
        //子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
    }
    
  • 设置用户ID和组
    $user = ‘www‘;
    $group = ‘www‘;
    // Get uid.
    $user_info = posix_getpwnam($user);
    if (!$user_info) {
        echo "Warning: User {$user} not exsits";
        exit;
    }
    $uid = $user_info[‘uid‘];
    // Get gid.
    if ($group) {
        $group_info = posix_getgrnam($group);
        if (!$group_info) {
            echo "Warning: Group {$group} not exsits";
            exit;
        }
        $gid = $group_info[‘gid‘];
    } else {
        $gid = $user_info[‘gid‘];
    }
    
    // Set uid and gid.
    if ($uid != posix_getuid() || $gid != posix_getgid()) {
        if (!posix_setgid($gid) || !posix_initgroups($user_info[‘name‘], $gid) || !posix_setuid($uid)) {
            echo "Warning: change gid or uid fail.";
            exit;
        }
    }
    

需要注意

  • 在fork前的变量,fork后父子都可以引用,fork后可以各自修改,但已经是独立的会互不影响,在fork后父子各自创建的变量也不会互相影响,如果想要传递信息,就需要用到进程间的通讯了,可以参考进程间通信的方式——信号、管道、消息队列、共享内存 PHP多进程初探 --- 进程间通信二三事
  • 在循环中的fork,第一次fork后,会产生两个进程,而这两个进程是在循环中的,所以再次循环时,这两个进程都会创建各自的子进程,类似细胞分裂,所以循环的个数会远小于实际产生的子进程数。那如何避免呢,一种方法是让子进程一直while循环,这样他就无法继续执行下一次的fork(保险期间还是加上exit比较好),另一种就是子进程处理完后就exit退出,自然是不会执行下一次的fork了。
  • 上方POSIX函数的主要应用场景是设置子进程以非root的权限运行,例如我们在nginx中设置的参数user:www www,就是在指定子进程运行的用户名和用户组,否则子进程也用root来运行的话权限过大,存在安全隐患。
  • 回收子进程的几种方式:
    • 在主进程中通过waitwaitpid来阻塞或者是循环来处理子进程回收
    • 通过信号SIGCHLD来实现,每个子进程结束时都会发送SIGCHLD信号,父进程进行捕捉调用waitwaitpid来回收
    • linux下还可以通过直接忽略SIGCHLD来实现:signal(SIGCHLD, SIG_IGN),此时子进程结束时,直接由内核init进程来负责回收
      上面几种方式看似第一种不太好,因为主进程都用来处理子进程的回收了,就不能做其它的事情了,但实际上主进程本来就是来管理子进程的,并没有多少自己的逻辑,而且越少处理逻辑越安全,所以直接在循环中处理并没有什么问题。

关联

php socket网络编程基础知识(四):多进程

上一篇:vue 对css样式进行修改


下一篇:SpringBoot主程序注解@SpringBootApplication简单分析