阅读(3231) (0)

将 Socket 服务器运行于多线程环境中

2017-06-13 15:42:41 更新

很多人存在误解,认为 PHP 不支持多线程,实际上,是支持的,只是默认情况下,PHP 安装包中并不包含多线程扩展,需要自己到 PHP 扩展社区库(PECL)网站上下载,链接为 http://pecl.php.net/package/pthreads。如果是 Windows 用户,也可以在点击这个链接进行下载:http://windows.php.net/downloads/pecl/releases/pthreads/

多线程有很多好处,也不只可以用于 Socket 通讯,但是,由于 Socket 领域更有可能需要多线程,这篇文章只讲述如何让 ModPHP 的 Socket 服务器能力在多线程上得到体现。

假设你安装了多线程的 PHP 环境,并且对其有了一定的了解,如果没有,请自行到 PHP 官网上进行学习。

ModPHP 的 Socket 服务器要想运行在多线程中,其实很简单,当然你需要将 ModPHP 更新为 1.8.4 版本或者更新(如果有)。下面这段代码是 2.0.1 版本中 socket-server-thread.php 文件中实现多线程Socket 服务器的代码,你可以直接使用或者参考它编写更出色的程序逻辑:

<?php 
if(!class_exists('Thread')){
    fwrite(STDOUT, "PHP does not support multi-threading yet.\n");
    goto console;
}
include 'mod/classes/socket-server.class.php'; //引入 WebSocket 扩展
/** 监听端口 */
$server = SocketServer::listen(@$_SERVER['argv'][1] ?: 8080, function($server, $port){
    fwrite(STDOUT, "Socket server $server started on $port at ".date('m-d-Y H:i:s').".\n");
}, false); //将第三个参数($autoStart)设置为 false
/** 创建线程类 */
class SocketServerThread extends Thread{
    /** 将服务器资源传入线程作用域 */
    function __construct($server){
        $this->server = $server;
    }

 
    function run(){
        SocketServer::server($this->server); //设置服务器
        include 'socket-server.php'; //引入 Socket 服务
        SocketServer::start(); //开启服务
    }
}
$threads = array(); //线程组
/** 创建若干个线程并加入线程组 */
for ($i=0; $i < (@$_SERVER['argv'][2] ?: 5); $i++) {
    $threads[$i] = new SocketServerThread($server);
    $threads[$i]->start();
}
/** 引入交互式控制台,可以监控线程组 */
console:
include 'mod.php';

因为 PHP 的多线程扩展是线程安全的,所以你基本上不用考虑线程直间会互相干预,它们是完全独立的(与其他语言不同)。但也由于这样,PHP 程序的子线程的运行级别要比存粹的 PHP 程序更低,因此它里面可能会出现某些无法预料的情况,如某些常量未定义,即使在主线程中这些常量是可用的。ModPHP 默认提供了几个在子线程中缺失的 PHP 内置常量,因此这个问题也并不是很重要。

还有一点就是,在上述代码中,无法使用文件锁,这可能是 pthreads 扩展的 BUG,或者专门这么设计。解决的办法是,在引入的交互式控制台中,输入并运行 PHP 代码,以此来启用文件锁。比如,你应该在启用 Socket 服务器之后,锁定站点根目录下的 .socket-server 文件,从而告知所有网站程序 Socket 服务器已启动,例如 ModCMS 中,就是通过判断 .socket-server 文件是否被锁定来判断是否开启了 Socket 服务器。

你可以在交互式控制台中通过下面的代码来锁定 .socket-server 文件:

$file = fopen('.socket-server', 'r');
echo flock($file, LOCK_EX | LOCK_NB) ? 'File locked.' : 'Lock failed.';