Swoole Server对象

Swoole允许通过PHP构建一个全新的Server,用于创建一个异步服务器程序,监听请求并做出响应。

  • 支持TCP、UDP、UnixSocket三种协议
  • 支持IPv4、IPv6
  • 支持SSL/TLS单向双向证书的隧道加密

使用注意

  • Swoole Server是一个独立的服务,只能用于php-cli环境,在其他环境下会抛出致命错误。
  • 不要在Swoole Server创建之前调用其它异步IO的API,否则将会创建失败。

运行流程

  1. 构建Server对象
  2. 设置运行时参数
  3. 注册事件回调函数
  4. 启动服务器
Swoole运行流程

构建Server对象

原型

swoole_server(string $host, int $port = 0, int $mode = SWOOLE_PROCESS,  int $socket_type = SWOOLE_SOCK_TCP)

参数

  • string $host 服务器主机地址
  • int $port 服务器端口号
  • int $mode 服务器运行模式,支持SWOOLE_PROCESS默认多进程模式、SWOOLE_BASE单线程模式
  • int $socket_type 套接字类型,支持TCPUDPUDP6UnixSocketStream/Dgram六种
$serv = new Swoole\Server("0.0.0.0", 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP);

服务器主机地址string $host

Server的创建需要绑定监听的IP地址和端口,如果IP地址指定为127.0.0.1表示客户端只能位于本机才能连接,其它计算机将无法连接。如果需要所有客户端都能连接则需要设置为0.0.0.0

服务器主机端口int $port

端口号的取值范围是[1, 655353],一般1000以下的端口都会被常用服务所占用。

Server绑定端口号时需要注意端口号是否已经被占用,如果端口被占用需要更改为其它端口。

查看端口是否被占用

$ ss -lntpd | grep :22
tcp    LISTEN     0      0                      *:22                    *:*
tcp    LISTEN     0      0                     :::22                   :::*
tcp    LISTEN     0      0                      *:22                    *:*
tcp    LISTEN     0      0                     :::22                   :::*
$ netstat -tnlp | grep :22
netstat: showing only processes with your user ID
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp        0      0 :::22                   :::*                    LISTEN      -

将某个服务通过某个端口对外提供服务的行为称为监听。例如Apache监听本机80端口,如果客户端通过80端口发送请求过来,操作系统会将请求转交给Apache,Apache就可以根据请求的具体内容进行处理并做出响应。

服务器运行模式 int $mode

多线程模式

多线程Worker模式中Reactor线程用来处理网络事件循环并读取数据,得到的请求会交给Worker线程去处理。当一个线程发生内存错误时整个进程会全部结束。由于PHP的ZenVM虚拟机在多线程模式下存在内存错误,多线程模式在Swoole1.6.0版本以后就关闭了。

多进程模式 SWOOLE_PROCESS

多进程模式用于大量的进程间通信、进程管理机制。适合业务逻辑非常复杂的场景,Swoole提供了完善的进程管理、内存保护机制,在业务逻辑非常复杂的情况下可以长期稳定的运行。

Swoole在Reactor线程中提供了Buffer功能,可以应对大量慢速连接和逐字节的恶意客户端。另外提供了CPU亲和设置选项,使程序运行效率更佳。

多进程模式的优点

  • 连接与数据请求发送是分离的,不会因为某些连接数据量大或某些连接数据量小而导致Worker进程不均衡。
  • Worker进程发送致命错误时连接并不会被切断
  • 可实现单连接并发,仅保持少量TCP连接,请求可并发地在多个Worker进程中处理。

多进程模式的缺点

  • 存在2次IPC开销,Master进程与Worker进程需要使用UnixSocket进行通信。
  • 不支持某些高级功能,如sendwaitpauseresume等操作。
Swoole Process模式结构

多线程模式操作流程

注意:不考虑TaskWorker进程的情况下

  1. Master进程fork创建Manager进程
  2. Master进程pthread_create创建Reactor线程
    注意这里的顺序,Master进程不能先pthread_create创建Reactor线程然后再fork创建Manager进程,因为fork时只能复制一个线程。
  3. Manager进程fork创建Worker进程
    Master进程fork创建的Manager进程、Manager进程fork创建的Worker进程,都会继承Master中已经打开的文件描述符。
  4. Master进程先创建多个套接字对,用于和Worker进程通信。

多进程模式请求处理流程

多进程模式请求处理流程

Swoole的Master进程与Worker进程之间的通信流程

  1. Master进程创建N个套接字对
  2. Master进程fork创建Manager管理进程
  3. Manager进程fork创建Worker进程
  4. Master进程与Worker进程都有sv[1] - sv[2n+2]2n+2个文件描述符,也就是fork出的子进程会自动继承父进程的文件描述符。
  5. Master进程会与Worker进程进行通信,Worker进程之间不会相互通信。

单线程模式SWOOLE_BASE

单线程模式是传统的异步非阻塞Server,与Nginx和Node.js等程序完全是一致的。单线程模式中会在事件循环中直接调用PHP的函数,而不是使用dispatch投递任务。如果回调函数中有阻塞操作会导致Server退化为同步模式。单线程模式下worker_num配置参数仍然有效,它会启动多个Worker进程。

单线程模式的特点

  • 单线程模式下没有Master进程的角色
  • 每个Worker进程同时承担了Process模式下Reactor线程和Worker进程两部分职责
  • 单线程模式下Manager进程是可选的,当设置了worker_num=1且没有使用Task和MaxRequest特性时,底层将会直接创建一个单独的Worker进程,而不会创建Manager进程。

单线程模式的优点

  • 没有IPC开销性能更好
  • 代码更简单不容易出错

单线程模式的缺点

  • TCP连接是在Worker进程中维持的
    当某个Worker进程挂掉时此Worker内的所有连接都将被关闭
  • 少量TCP长连接无法利用到所有Worker进程
  • TCP连接与Worker是绑定的
    长连接应用中某些连接的数量大,这些连接所在的Worker进程负载会非常高,但是某些连接数据量小时。某些连接数量小时所在的Worker进程的负载会非常低,不同的Worker进程无法实现均衡。

单线程模式适用场景

如果客户端连接之间不需要交互,可以使用单线程模式,如Memcache、HTTP服务器等。

例如:

服务器

$ vim server.php
<?php
class Server
{
    private $server;

    public function __construct($host = "0.0.0.0", $port = 9501, $configs = [])
    {
        //创建服务器对象
        $this->server = new swoole_server($host, $port);
        //设置服务器运行时参数
        $this->server->set($configs);
        //注册监听函数
        $this->server->on("Start", [$this, "onStart"]);
        $this->server->on("Connect", [$this, "onConnect"]);
        $this->server->on("Receive", [$this, "onReceive"]);
        $this->server->on("Close", [$this, "onClose"]);
        $this->server->on("Task", [$this, "onTask"]);
        $this->server->on("Finish", [$this, "onFinish"]);
        //启动服务器
        $this->server->start();
    }
    public function onStart(swoole_server $server)
    {

        $version = SWOOLE_VERSION;
        $master_pid = $server->master_pid;
        $manager_pid = $server->manager_pid;
        echo "[start] version:{$version} master:{$master_pid} manager:{$manager_pid}".PHP_EOL;
        echo json_encode($server).PHP_EOL;
    }
    public function onConnect(swoole_server $server, $fd, $reactor_id)
    {
        echo "[connect] reactor:{$reactor_id} client:{$fd}".PHP_EOL;
        echo json_encode($server).PHP_EOL;
    }
    public function onReceive(swoole_server $server, $fd, $reactor_id, $data)
    {
        echo PHP_EOL."[receive] reactor:{$reactor_id} client:{$fd} data:{$data}".PHP_EOL;
        echo json_encode($server).PHP_EOL;
        //send a task to task worker process
        $param = [];
        $param["fd"] = $fd;
        //start a task
        $message = json_encode($param);
        $server->task($message);
        echo "[receive] continue handle worker".PHP_EOL;
    }
    public function onClose(swoole_server $server, $fd)
    {
        echo "[close] client:{$fd}".PHP_EOL;
        echo json_encode($server).PHP_EOL;
    }
    public function onTask(swoole_server $server, $task_id, $reactor_id, $data)
    {
        echo "[task] task:{$task_id} reactor:{$reactor_id} data:{$data}".PHP_EOL;
        echo json_encode($server).PHP_EOL;
        for($i=0; $i<3; $i++){
            sleep(1);
            echo "[task] task {$task_id} handle {$i} times...".PHP_EOL;
        }

        $param = json_decode($data, true);
        $fd = $param["fd"];

        $message = "task {$task_id} success";
        $server->send($fd, $message);

        return "task {$task_id} finished";
    }
    public function onFinish(swoole_server $server, $task_id, $data)
    {
        echo "[finish] task:{$task_id} data:{$data}".PHP_EOL;
        echo json_encode($server).PHP_EOL;
    }
}

$host = "0.0.0.0";
$port = 9501;
$configs = [];
$configs["worker_num"] = 8;
$configs["task_worker_num"] = 8;
$configs["daemonize"] = false;
$configs["max_request"] = 1000;
$configs["log_file"] = "./swoole.log";
$server = new Server($host, $port, $configs);

客户端

$ vim client.php
<?php
class Client
{
    private $client;

    public function __construct($host, $port, $timeout=1)
    {
        $this->client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
        
        $this->client->on("Connect", [$this, "onConnect"]);
        $this->client->on("Receive", [$this, "onReceive"]);
        $this->client->on("Close", [$this, "onClose"]);
        $this->client->on("Error", [$this, "onError"]);

        if(!$this->isConnected()){
            $this->connect($host, $port, $timeout);
        }
    }
    public function isConnected()
    {
        return $this->client->isConnected();
    }
    public function connect($host, $port, $timeout)
    {
        $fp = $this->client->connect($host, $port, $timeout);
        if(!$fp)
        {
            echo "[connect] errno:{$fp->errCode} errmsg:{$fp->errMsg}".PHP_EOL;
            return;
        }
    }
    public function send($message)
    {
        $this->client->send($message);
    }
    public function onConnect(swoole_client $client)
    {
        fwrite(STDOUT, "send: ");
        swoole_event_add(STDIN, function(){
            fwrite(STDOUT, "send: ");
            $msg = trim(fgets(STDIN));
            $this->send($msg);
        });
    }

    public function onClose(swoole_client $client)
    {
        echo "[close]".PHP_EOL;
    }

    public function onReceive(swoole_client $client, $data)
    {
        echo "[receive] {$data}".PHP_EOL;
    }
    public function onError(swoole_client $client)
    {
        echo "[error]".PHP_EOL;
    }
}
$host = "127.0.0.1";
$port = 9501;
$timeout = 1;
$client = new Client($host, $port, $timeout);

运行服务器

$ php server.php
[start] version:4.3.2-alpha master:264 manager:265

查看当前服务器对象的值

{
    "setting": {
        "worker_num": 8,
        "task_worker_num": 8,
        "daemonize": false,
        "max_request": 1000,
        "log_file": "./swoole.log",
        "buffer_output_size": 2097152,
        "max_connection": 100000
    },
    "connections": {},
    "host": "0.0.0.0",
    "port": 9501,
    "type": 1,
    "mode": 2,
    "ports": [
        {
            "host": "0.0.0.0",
            "port": 9501,
            "type": 1,
            "sock": 4,
            "setting": {
                "worker_num": 8,
                "task_worker_num": 8,
                "daemonize": false,
                "max_request": 1000,
                "log_file": "./swoole.log"
            },
            "connections": {}
        }
    ],
    "master_pid": 264,
    "manager_pid": 265,
    "worker_id": -1,
    "taskworker": false,
    "worker_pid": 0
}

运行客户端

$ php client.php
send:

此时客户端处于输入等待状态,此时在查看服务端打印输入信息为

$ php server.php
[start] version:4.3.2-alpha master:264 manager:265
[connect] reactor:0 client:1

此时服务器对象与之前的区别

{
    "setting": {
        "worker_num": 8,
        "task_worker_num": 8,
        "daemonize": false,
        "max_request": 1000,
        "log_file": "./swoole.log",
        "buffer_output_size": 2097152,
        "max_connection": 100000
    },
    "connections": {},
    "host": "0.0.0.0",
    "port": 9501,
    "type": 1,
    "mode": 2,
    "ports": [
        {
            "host": "0.0.0.0",
            "port": 9501,
            "type": 1,
            "sock": 4,
            "setting": {
                "worker_num": 8,
                "task_worker_num": 8,
                "daemonize": false,
                "max_request": 1000,
                "log_file": "./swoole.log"
            },
            "connections": {}
        }
    ],
    "master_pid": 264,
    "manager_pid": 265,
    "worker_id": 1,
    "taskworker": false,
    "worker_pid": 276
}

对比发现,当前的服务器对象中

"worker_id": 1,
"worker_pid": 276

客户端向服务端发送消息

$ php client.php
send: hello
send:

观察服务器对象的变化

$ php server.php
[start] version:4.3.2-alpha master:264 manager:265
{"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log","buffer_output_size":2097152,"max_connection":100000},"connections":{},"host":"0.0.0.0","port":9501,"type":1,"mode":2,"ports":[{"host":"0.0.0.0","port":9501,"type":1,"sock":4,"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log"},"connections":{}}],"master_pid":264,"manager_pid":265,"worker_id":-1,"taskworker":false,"worker_pid":0}
[connect] reactor:0 client:1
{"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log","buffer_output_size":2097152,"max_connection":100000},"connections":{},"host":"0.0.0.0","port":9501,"type":1,"mode":2,"ports":[{"host":"0.0.0.0","port":9501,"type":1,"sock":4,"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log"},"connections":{}}],"master_pid":264,"manager_pid":265,"worker_id":1,"taskworker":false,"worker_pid":276}

[receive] reactor:0 client:1 data:hello
{"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log","buffer_output_size":2097152,"max_connection":100000},"connections":{},"host":"0.0.0.0","port":9501,"type":1,"mode":2,"ports":[{"host":"0.0.0.0","port":9501,"type":1,"sock":4,"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log"},"connections":{}}],"master_pid":264,"manager_pid":265,"worker_id":1,"taskworker":false,"worker_pid":276}
[receive] continue handle worker
[task] task:0 reactor:1 data:{"fd":1}
{"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log","buffer_output_size":2097152,"max_connection":100000},"connections":{},"host":"0.0.0.0","port":9501,"type":1,"mode":2,"ports":[{"host":"0.0.0.0","port":9501,"type":1,"sock":4,"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log"},"connections":{}}],"master_pid":264,"manager_pid":265,"worker_id":8,"taskworker":true,"worker_pid":267}
[task] task 0 handle 0 times...
[task] task 0 handle 1 times...
[task] task 0 handle 2 times...
[finish] task:0 data:task 0 finished
{"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log","buffer_output_size":2097152,"max_connection":100000},"connections":{},"host":"0.0.0.0","port":9501,"type":1,"mode":2,"ports":[{"host":"0.0.0.0","port":9501,"type":1,"sock":4,"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log"},"connections":{}}],"master_pid":264,"manager_pid":265,"worker_id":1,"taskworker":false,"worker_pid":276}

未完待续...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 前文再续,就书接上一回,随着与Server、TCP、Protocol的邂逅,Swoole终于迎来了自己的故事,今天...
    蜗牛淋雨阅读 1,725评论 1 14
  • 一、server 顾名思义,就是指服务端,我们平时接触的比较多的有nginx 、apache、IIS等。作为web...
    opop2012阅读 945评论 0 1
  • 前言 前文再续,就书接上一回,随着与Server、TCP、Protocol的邂逅,Swoole终于迎来了自己的故事...
    零一间阅读 5,254评论 0 17
  • 积极思考,为企业出谋划策 任何人都不是万能的,都有自己的弱项和局限性,单凭一个人的力量,不可能把企业带上高峰。所有...
    肖金娜阅读 329评论 1 0
  • (生活中会有时候,有人坐下来了,跟你说你不行,说你省点儿心吧,说你请静一静。 随着年龄的增长,人会趋于现实,不能像...
    东岳不是泰山阅读 399评论 0 1