1.Websocket

1.OSI七层与TCP/IP五层模型

2.socket

Socket实际上是对TCP/IP协定的封装,自身并不是协定,而是一个调用接口(API).Socket的呈现只是使得程序员更不便地应用TCP/IP协定栈而已,是对TCP/IP协定的形象,从而造成了咱们晓得的一些最根本的函数接口.比方create、listen、connect、accept、send、read和write.

3.简介

WebSocket 是一种网络通信协定.WebSocket 协定在2008年诞生,2011年成为国际标准。所有浏览器都曾经反对了.服务器能够被动向客户端推送信息,客户端也能够被动向服务器发送信息,属于服务器推送技术的一种。`长链接`
# 服务器推送技术1    Webpush2    HTTP server push3    Pushlet4    Long polling5    Flash XMLSocket relays6    Reliable Group Data Delivery (RGDD)7    Push notification

4.与HTTP的比照

5.特点

(1)建设在 TCP 协定之上,服务器端的实现比拟容易。(2)与 HTTP 协定有着良好的兼容性。默认端口也是`80`和`443`,并且握手阶段采纳 HTTP 协定,因而握手时不容易屏蔽,能通过各种 HTTP 代理服务器。(3)数据格式比拟轻量,性能开销小,通信高效。(4)能够发送文本,也能够发送二进制数据。(5)没有`同源限度`,客户端能够与任意服务器通信。(6)协定标识符是`ws`(如果加密,则为wss),服务器网址就是 URL。   (scheme)
ws://example.com:80/uriwss://example.com:80/uri

6.示例

# 客户端let ws = new WebSocket("wss://echo.websocket.org");ws.onopen = function(evt) {   console.log("Connection open ...");   ws.send("Hello WebSockets!");};ws.onmessage = function(evt) {  console.log( "Received Message: " + evt.data);  ws.close();};ws.onclose = function(evt) {  console.log("Connection closed.");};# 服务端php -> socket_create(),  new Socketpython -> socket,go -> gorilla/websocketnode -> socket.io / socket.io -client
# 调试https://jsbin.com/?js,console,output
# webSocket.readyStatereadyState属性返回实例对象的以后状态,共有四种。CONNECTING:值为0,示意正在连接。OPEN:值为1,示意连贯胜利,能够通信了。CLOSING:值为2,示意连贯正在敞开。CLOSED:值为3,示意连贯曾经敞开,或者关上连贯失败。

2.swoole

1.简介

作者 韩天峰 pecl开发组成员php扩大Swoole 是一个 PHP 的 `协程` `高性能` 网络通信引擎,应用 C/C++ 语言编写,提供了多种通信协议的网络服务器和客户端模块。能够不便疾速的实现 TCP/UDP服务、高性能Web、WebSocket服务、物联网、实时通信、游戏、微服务等,使 PHP 不再局限于传统的 Web 畛域。4.4+之后, 全面协程化, PHP 协程框架 
# 文档https://www.swoole.com/

php-fpm` 解决申请

sapi---> 初始化http的环境变量

phpcore ---> 初始化php拓展, 初始化上下文环境

执行php脚本

2.示例

1.http server
        $http = new Swoole\Http\Server("127.0.0.1", 9501);    $http->on("start", function ($server) {        echo "Swoole http server is started at http://127.0.0.1:9501\n";    });    $http->on("request", function ($request, $response) {        $response->header("Content-Type", "text/plain");        $response->end("Hello World\n");    });    $http->start();
2.websocket server
        $server = new Swoole\Websocket\Server("127.0.0.1", 9502);    $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();
3.tcp server
        $server = new Swoole\Server("127.0.0.1", 9503);    $server->on('connect', function ($server, $fd){        echo "connection open: {$fd}\n";    });    $server->on('receive', function ($server, $fd, $reactor_id, $data) {        $server->send($fd, "Swoole: {$data}");        $server->close($fd);    });    $server->on('close', function ($server, $fd) {        echo "connection close: {$fd}\n";    });    $server->start();
4.udp server
        $serv = new Swoole\Server("127.0.0.1", 9502, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);    //监听数据接管事件    $serv->on('Packet', function ($serv, $data, $clientInfo) {        $serv->sendto($clientInfo['address'], $clientInfo['port'], "Server ".$data);        var_dump($clientInfo);    });    //启动服务器    $serv->start();
5.task
$server = new Swoole\Server("127.0.0.1", 9502);    $server->set(array('task_worker_num' => 4));    $server->on('receive', function($server, $fd, $reactor_id, $data) {        $task_id = $server->task("Async");        echo "Dispatch AsyncTask: [id=$task_id]\n";    });    $server->on('task', function ($server, $task_id, $reactor_id, $data) {        echo "New AsyncTask[id=$task_id]\n";        $server->finish("$data -> OK");    });    $server->on('finish', function ($server, $task_id, $data) {        echo "AsyncTask[$task_id] finished: {$data}\n";    });    $server->start();
6.coroutine
//睡眠 1 万次,读取,写入,检查和删除文件 1 万次,应用 PDO 和 MySQLi 与数据库通信 1 万次,创立 TCP 服务器和多个客户端互相通信 1 万次,//创立 UDP 服务器和多个客户端到互相通信 1 万次...... 一切都在一个过程一秒内完满实现!   Swoole\Runtime::enableCoroutine();//此行代码后,文件操作,sleep,Mysqli,PDO,streams等都变成异步IO,见文档'一键协程化'章节   $s = microtime(true);    //Co/run()见文档'协程容器'章节   Co\run(function() {    // i just want to sleep...    for ($c = 100; $c--;) {        go(function () {            for ($n = 100; $n--;) {                usleep(1000);            }        });    }    // 10k file read and write    for ($c = 100; $c--;) {        go(function () use ($c) {            $tmp_filename = "/tmp/test-{$c}.php";            for ($n = 100; $n--;) {                $self = file_get_contents(__FILE__);                file_put_contents($tmp_filename, $self);                assert(file_get_contents($tmp_filename) === $self);            }            unlink($tmp_filename);        });    }    // 10k pdo and mysqli read    for ($c = 50; $c--;) {        go(function () {            $pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8', 'root', 'root');            $statement = $pdo->prepare('SELECT * FROM `user`');            for ($n = 100; $n--;) {                $statement->execute();                assert(count($statement->fetchAll()) > 0);            }        });    }    for ($c = 50; $c--;) {        go(function () {            $mysqli = new Mysqli('127.0.0.1', 'root', 'root', 'test');            $statement = $mysqli->prepare('SELECT `id` FROM `user`');            for ($n = 100; $n--;) {                $statement->bind_result($id);                $statement->execute();                $statement->fetch();                assert($id > 0);            }        });    }    // php_stream tcp server & client with 12.8k requests in single process    function tcp_pack(string $data): string    {        return pack('n', strlen($data)) . $data;    }    function tcp_length(string $head): int    {        return unpack('n', $head)[1];    }    go(function () {        $ctx = stream_context_create(['socket' => ['so_reuseaddr' => true, 'backlog' => 128]]);        $socket = stream_socket_server(            'tcp://0.0.0.0:9502',            $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx        );        if (!$socket) {            echo "$errstr ($errno)\n";        } else {            $i = 0;            while ($conn = stream_socket_accept($socket, 1)) {                stream_set_timeout($conn, 5);                for ($n = 100; $n--;) {                    $data = fread($conn, tcp_length(fread($conn, 2)));                    assert($data === "Hello Swoole Server #{$n}!");                    fwrite($conn, tcp_pack("Hello Swoole Client #{$n}!"));                }                if (++$i === 128) {                    fclose($socket);                    break;                }            }        }    });    for ($c = 128; $c--;) {        go(function () {            $fp = stream_socket_client("tcp://127.0.0.1:9502", $errno, $errstr, 1);            if (!$fp) {                echo "$errstr ($errno)\n";            } else {                stream_set_timeout($fp, 5);                for ($n = 100; $n--;) {                    fwrite($fp, tcp_pack("Hello Swoole Server #{$n}!"));                    $data = fread($fp, tcp_length(fread($fp, 2)));                    assert($data === "Hello Swoole Client #{$n}!");                }                fclose($fp);            }        });    }    // udp server & client with 12.8k requests in single process    go(function () {        $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_DGRAM, 0);        $socket->bind('127.0.0.1', 9503);        $client_map = [];        for ($c = 128; $c--;) {            for ($n = 0; $n < 100; $n++) {                $recv = $socket->recvfrom($peer);                $client_uid = "{$peer['address']}:{$peer['port']}";                $id = $client_map[$client_uid] = ($client_map[$client_uid] ?? -1) + 1;                assert($recv === "Client: Hello #{$id}!");                $socket->sendto($peer['address'], $peer['port'], "Server: Hello #{$id}!");            }        }        $socket->close();    });    for ($c = 128; $c--;) {        go(function () {            $fp = stream_socket_client("udp://127.0.0.1:9503", $errno, $errstr, 1);            if (!$fp) {                echo "$errstr ($errno)\n";            } else {                for ($n = 0; $n < 100; $n++) {                    fwrite($fp, "Client: Hello #{$n}!");                    $recv = fread($fp, 1024);                    list($address, $port) = explode(':', (stream_socket_get_name($fp, true)));                    assert($address === '127.0.0.1' && (int)$port === 9503);                    assert($recv === "Server: Hello #{$n}!");                }                fclose($fp);            }        });    }  });  echo 'use ' . (microtime(true) - $s) . ' s';
7.Channel
 Co\run(function(){        //应用Channel进行协程间通信        $chan = new Swoole\Coroutine\Channel(1);        Swoole\Coroutine::create(function () use ($chan) {            for($i = 0; $i < 100000; $i++) {                co::sleep(1.0);                $chan->push(['rand' => rand(1000, 9999), 'index' => $i]);                echo "$i\n";            }        });        Swoole\Coroutine::create(function () use ($chan) {            while(1) {                $data = $chan->pop();                var_dump($data);            }        });  });

3.格调

服务端 + 客户端

1.异步格调
2.协程格调

过程 ---> 线程 ---> 协程

连接池 (server的manager模块)

# 创立`Coroutine::create` 或 `go` 办法创立协程反对 `waitgroup`
# 通信问题     过程             高性能共享内存 `Table`       `Process`模块 (代替php自带的 `pcntl` )   协程       `Coroutine\Channel`        并发编程: Coroutine::create() + setdefer()  ->   go() + channel退出协程`exit`禁用`异样捕捉`不能跨协程在多个协程间不能`共用`一个连贯禁止应用`动态类`或者`全局变量`保留上下文对象`sleep`不能用

4.根本须知

1.四种设置回调函数的形式
# 匿名函数$server->on('Request', function ($req, $resp) use ($a, $b, $c) {    echo "hello world";});Copy to clipboardErrorCopied可应用 use 向匿名函数传递参数
# 类静态方法class A{    static function test($req, $resp)    {        echo "hello world";    }}$server->on('Request', 'A::Test');$server->on('Request', array('A', 'Test'));Copy to clipboardErrorCopied对应的静态方法必须为 public
# 函数function my_onRequest($req, $resp){    echo "hello world";}$server->on('Request', 'my_onRequest');
# 对象办法class A{    function test($req, $resp)    {        echo "hello world";    }}$object = new A();$server->on('Request', array($object, 'test'));对应的办法必须为 public
2.同步 IO / 异步 IO
# 网络io模型:同步模型(synchronous IO)阻塞IO(bloking IO)非阻塞IO(non-blocking IO)多路复用IO(multiplexing IO) `poll/select`  -> `epoll`信号驱动式IO(signal-driven IO)异步IO(asynchronous IO)
3.EventLoop
所谓 EventLoop,即事件循环,能够简略的了解为 `epoll_wait`,咱们会把所有要产生事件的句柄(fd)退出到 epoll_wait 中,这些事件包含可读,可写,出错等。 咱们的过程就阻塞在 epoll_wait 这个内核函数上,当产生了事件 (或超时) 后 epoll_wait 这个函数就会完结阻塞返回后果,就能够回调相应的 PHP 函数,例如,收到客户端发来的数据,回调 OnRecieve 回调函数。当有大量的 fd 放入到了 epoll_wait 中,并且同时产生了大量的事件,epoll_wait 函数返回的时候咱们就会挨个调用相应的回调函数,叫做一轮事件循环,即 IO 多路复用,而后再次阻塞调用 epoll_wait 进行下一轮事件循环。
4.TCP粘包问题
tcp 封包 解包 粘包 数据封包协定规定:整个数据包蕴含2字节长度信息+数据包体。2字节长度信息蕴含自身着2字节。如:数据体是(abcdefg)7个字节,整体封包就是09abcdefg,总共是9个字节的协定
EOF 结束符协定固定包头 + 包体协定
5.IPC
同一台主机上两个过程间通信 (`Inter-Process Communication`)
在 Swoole 下应用了 2 种形式 :# Unix Socket :全名 UNIX Domain Socket, 简称 UDSSOCK_STREAM : 数据大用 (有粘包问题)SOCK_DGRAM : 数据小用 (64k)# sysvmsg :Linux 提供的音讯队列,这种 IPC 形式通过一个文件名来作为 key 进行通信,这种形式十分的不灵便,理论我的项目应用的并不多

5.装置

扩大抵触

xdebugphptraceaopmoltenxhprofphalcon(Swoole 协程无奈运行在 phalcon 框架中)

必须

php-7.1 或更高版本gcc-4.8 或更高版本makeautoconf
1.源码装置
#1. 下载 swoole 源码https://github.com/swoole/swoole-src/releaseshttp://pecl.php.net/package/swoolehttp://git.oschina.net/swoole/swoole#2. 从源码编译装置下载源代码包后,在终端进入源码目录,执行上面的命令进行编译和装置cd swoole-src && \phpize && \./configure && \--enable-openssl  \--enable-http2 && \make && sudo make install#3. 启用扩大编译装置到零碎胜利后,须要在 php.ini 中退出一行 extension=swoole.so 来启用 Swoole 扩大
2.pecl装置
pecl install swoole

3.swoft

version:2.x

1.环境筹备

#必要局部PHP,版本 >=7.1PHP 包管理器 ComposerPCRE 库PHP 扩大 Swoole,版本 >=4.3额定扩大:PDO、Redis#抵触局部XdebugXhprofBlackfireZendtraceUopz

2.装置形式

1.docker
docker run -p 18306:18306 --name swoft swoft/swoft
2.docker-compose
git clone https://github.com/swoft-cloud/swoftcd swoftdocker-compose up# sserver上面有以下文件配置docker-compose.ymldocker-sync -> rsync , native_osx
3.composer
composer create-project swoft/swoft Swoft
4.手动装置
git clone https://github.com/swoft-cloud/swoftcd swoftcomposer installcp .env.example .env
5.swoftcli
# 反对从不同模板我的项目中疾速创立一个洁净的 Swoft 利用php swoftcli.phar create:app --type full Swoft-Fullphp swoftcli.phar create:app --type ws Swoft-WebSocketphp swoftcli.phar create:app --type tcp Swoft-TCP# 应用cp swoftcli.phar /usr/local/bin/swoftcli && chmod a+x swoftcli

3.目录构造

├── app/    ----- 利用代码目录│   ├── Annotation/        ----- 定义注解相干   (`ReflectionCalss`)│   ├── Aspect/            ----- AOP 切面      (`Aspect-oriented programming`)│   ├── Common/            ----- 一些具备独立性能的 class bean│   ├── Console/           ----- 命令行代码目录│   ├── Exception/         ----- 定义异样类目录│   │   └── Handler/           ----- 定义异样解决类目录│   ├── Http/              ----- HTTP 服务代码目录│   │   ├── Controller/│   │   └── Middleware/│   ├── Helper/            ----- 助手函数│   ├── Listener/          ----- 事件监听器目录│   ├── Model/             ----- 模型、逻辑等代码目录(这些层并不限定,依据须要应用)│   │   ├── Dao/│   │   ├── Data/│   │   ├── Logic/│   │   └── Entity/│   ├── Rpc/               ----- RPC 服务代码目录│   │   └── Service/│   │   └── Middleware/│   ├── WebSocket/         ----- WebSocket 服务代码目录│   │   ├── Chat/│   │   ├── Middleware/│   │   └── ChatModule.php│   ├── Tcp/               ----- TCP 服务代码目录│   │   └── Controller/        ----- TCP 服务解决控制器目录│   ├── Application.php    ----- 利用类文件继承自swoft外围│   ├── AutoLoader.php     ----- 我的项目扫描等信息(利用自身也算是一个组件)│   └── bean.php├── bin/│   ├── bootstrap.php│   └── swoft              ----- Swoft 入口文件├── config/                ----- 利用配置目录│   ├── base.php               ----- 根底配置│   └── db.php                 ----- 数据库配置├── public/                ----- 公共目录├── resource/              ----- 利用资源目录│   ├── language/              ----- 语言资源目录  │   └── view/                  ----- 视图资源目录  ├── runtime/               ----- 长期文件目录(日志、上传文件、文件缓存等)├── test/                  ----- 单元测试目录│   └── bootstrap.php├── composer.json├── phar.build.inc└── phpunit.xml.dist

4.运行服务

如果在 .env 文件中开启了调试 SWOFT_DEBUG=1 将会在控制台中显示更多具体的信息。
1.http server
# 启动 HTTP 服务$ php ./bin/swoft http:start# 以守护过程模式启动$ php ./bin/swoft http:start -d# 重启 HTTP 服务$ php ./bin/swoft http:restart# 从新加载 HTTP 服务$ php ./bin/swoft http:reload# 进行 HTTP 服务$ php ./bin/swoft http:stop# swoftcliswoftcli -hswoftcli run -c ws:startswoftcli run -c http:start
2.websocket server
# 启动 WS 服务$ php ./bin/swoft ws:start# 以守护过程模式启动$ php ./bin/swoft ws:start -d# 重启 WS 服务$ php ./bin/swoft ws:restart# 从新加载 WS 服务$ php ./bin/swoft ws:reload# 敞开 WS 服务$ php ./bin/swoft ws:stop
3.rpc server
# 启动 RPC 服务$ php ./bin/swoft rpc:start# 以守护过程模式启动$ php ./bin/swoft rpc:start -d# 重启 RPC 服务$ php ./bin/swoft rpc:restart# 从新加载 RPC 服务$ php ./bin/swoft rpc:reload# 敞开 RPC 服务$ php ./bin/swoft rpc:stop

5.注解

use Swoft\Http\Message\Request;use Swoft\Http\Server\Annotation\Mapping\Controller;use Swoft\Http\Server\Annotation\Mapping\RequestMapping;/** * Class Home * * @Controller(prefix="home") */class Home{    /**     * 该办法路由地址为 /home/index     *     * @RequestMapping(route="/index", method="post")     *     * @param Request $request     */    public function index(Request $request)    {        // TODO:    }}

6.IoC - DI

IoC: Inversion of Control
DI: Dependency Injection

Bean容器

# 定义@Bean命名空间:\Swoft\Bean\Annotation\Beanname 定义 Bean 别名,缺省默认类名scope 注入 Bean 类型,默认单例,Scope::SINGLETON/Scope::PROTOTYPE(每次创立)ref 指定援用 Bean ,用于定义在接口下面,指定应用哪个接口实现。
# 注入@Inject命名空间:\Swoft\Bean\Annotation\Injectname 定义属性注入的bean名称,缺省属性主动类型名称
# 操作App::getBean("name");ApplicationContext::getBean('name');BeanFactory::getBean('name');BeanFactory::hasBean("name");