Skip to main content

3 posts tagged with "php"

View All Tags

· 17 min read
sado

Redis 是一种内存数据库,它主要是用来提高网站性能的。

安装

Docker 安装+启动

  1. 下载 Redis 镜像
docker pull redis
  1. 创建并启动容器
docker run -d --name redis -p 6379:6379 redis

mac 安装+启动

1.安装命令

brew install redis

2.修改配置文件

vim /usr/local/etc/redis.conf

3.默认情况下绑定了本机127的ip,不允许远程访问

 #bind 127.0.0.1
protected-mode yes

改为

protected-mode no

4.测试是否安装成功

redis-cli ping

5.启动/关闭/重启 服务

brew services start/stop/restart redis 

redis-server /usr/local/etc/redis.conf

6.bash交互界面

redis-cli

windows 安装+启动

下载软件包,解压缩,软件包中最重要的三个文件:

  • redis-server:服务器程序
  • redis-cli:客户端程序
  • redis.windows.conf:配置文件

在 windows 中直接打开命令行并运行 redis-server 即可启动服务器。

方法一:

redis-server.exe redis.windows.conf  // 先切换到redis根目录,windows下启动,带配置文件

方法二:

1) 添加根目录为环境变量
2) 启动
redis-server.exe D:\redis\redis.windows.conf //配置文件目录用绝对路径

启动之后默认监听 6379 端口。

连接

服务器启动之后,就可以使用 redis-cli 来连接服务器进行操作。

打开命令行并执行 redis-cli

退出

在命令行中执行 exit 退出连接。

数据类型

Redis 中提供了多种数据类型来保存数据:

  • 字符串(string)
  • 列表(list)
  • 集合(set)
  • 有序集合(sorted set)
  • 哈希(hash)

每种数据类型都提供了很多指令来进行操作:

http://doc.redisfans.com/

字符串(string)

get、set

字符串是最基本的数据类型。

使用 set 指令来保存字符串数据:

set 键 值

值最大为 512M

使用 get 指令读取字符串数据:

get 键

incr、decr

还可以对数字类型使用 incrdecr 实现 加1 和 减1 操作。

set money 100
incr money // 101
incr money // 102
decr money // 101

也可以使用 incrby 数字decrby 数字 实现 加N 和 减N 操作。

set money 100
incrby money 50 // 150
decrby money 20 // 130

mget、mset

我们也可以使用 mgetmset 批量添加和读取数据。

mset a 10 b 10 c 10
mget a b c // 10 20 30

exists、del、type

exists :判断一个键是否存在。

del:删除一个键值对

type:查看数据类型

set name 'tom'
type name // string
exists name // 1
del name // 1
exists name // 0

expire

我们可以为 Redis 中保存的数据设置一个过期时间 ,过期时间到了之后会自动从 Redis 中删除该数据。

使用 expire 设置过期时间:

expre 键 过期时间(秒) //需要实现set设置过值

可以使用 ttl 指令查看剩余的过期时间。

ttl 键   // -1: 已过期    -2: 未设置

也可以在 set 时设置过期时间:

set 键 值 ex 过期时间

应用场景

主要用来做网站数据的缓存,比如:使用 Redis 保存 “销量最高的10件商品”、“最活跃的用户”等等。

列表(list)

列表( List )是一种按顺序保存一组数据的结构,有点类型于数组。

lpush、rpush

lpush:从列表左侧添加一个数据。

rpush:从列表右侧添加一个数据。

lpush 键 值

比如:

lpush class php     // php
lpush class js // js php
lpush class css // css js php
rpush class html // css js php html

// 也可以一次添加多个值
lpush class a b c // c b a css js php html

lrange

lrange:从左侧取出多个值,读取之后值还在列表中。

lrange 键 开始下标 结束下标

下标说明:

  1. 从 0 开始
  2. 可以使用负数,-1:最后一个 , -2:倒数第2个

lpop、rpop、rpoplpush

lpop:最出最左侧的一个值,然后从列表中删除该值。

rpop:从右侧取出一个值,然后从列表中删除该值。

rpoplpush:从列表右侧取出一个值,然后放到另一个列表的左侧。

lpush class a b c    // c b a
rpoplpush class class // a c b
rpop class // 得到 a ,列表变成 c b
lpop class // 得到 c , 列表变成 b

ltrim

ltrim:语法和 lrange 类似,功能是截取出列表中一个范围的数据,范围外的数据被删除。

lpush class a b c d   // d c b a
ltrim class 1 2 // 队列中只剩 c b

brpop

b:阻塞。

brpop:从列表右侧取出一个数据,如果列表中没有数据就阻塞等待,直到有另一个客户端向列表中添加了数据才返回。

brpop 键 等待时间(秒)

等待时间如果设置为 0 则代表永远等待直到有数据为止。

应用场景:消息队列。

应用场景

  1. 秒杀、抢票
  2. 保存最新前N的xxx数据(最新排行榜)
  3. 消息队列

哈希(hash)

哈希(Hash)用来保存多组键值对的数据,有些类似于 PHP 中的关联数组。

hmset

hmset:可以保存 hash 数据。

hmset 键 字段11 字段22 ...

示例、保存 ID=100 的用户信息

hmset user:100 name tom gender 男 birth 2019-10-10

hget 和 hgetall

hget:获取某一个字段值

hgetall:获取所有字段值

集合(set)

集合(set)是一组 无序不重复的字符串。

sadd、smembers、sismember

sadd:向集合中添加数据

smembers:获取集合中所有数据

sismember:判断一个数据是否在集合中

sadd nums 1 2 3  
smembers nums // 1 2 3
sismember nums 3 // 1
sismember nums 4 // 0

spop、srem、smove

spop:从集合中随机取出一个数据,并从集合中删除该数据。

srem:从集合中移动一个或者多个元素

smove:将一元素从一个集合移动到另一个集合

sunion、sinter、sdiff

sunion:返回多个集合的并集

sinter:返回多个集合的交集

sdiff:前一个集中有,后一个集合中没有的数据

交集:同时在多个集合中

并集:合并集合

总结

  1. 集合中的数据不重复
  2. 无序

应用场景

a. 好友系统 b. 发牌游戏 c. 抽奖等

有序集合(zset)

有序集合(sorted sets):是一组有 顺序不重复的一数据。

集合中的每个数都可以设置一个分值,然后就可以根据分值进行排序。

zadd

可以使用 zadd 向集合中添加数据。

zadd 键名 分值 数据

zrange

zrange:取出排在某个区间内的成员。

zrange 键名 开始下标 结束下标 withscores

示例、显示整个有序集成员

redis > ZRANGE salary 0 -1 WITHSCORES             
1) "jack"
2) "3500"
3) "tom"
4) "5000"
5) "boss"
6) "10086"

示例、显示排在2、3位的成员

redis > ZRANGE salary 1 2 WITHSCORES
1) "tom"
2) "5000"
3) "boss"
4) "10086"

zrank

zrank:获取某个成员的排名

zrank 键名 值

总结

  1. 值不重复
  2. 有顺序

应用场景

排行榜、浏览量、商品销量等等

频道(channel)

订阅

我们可以使用 subscribe 订阅一个或者多个频道,当有客户端向频道发消息时就可以收到。

subscribe 频道1 频道2 ...

发布

可以使用 publish 向一个频道中发送消息。

publish 频道 消息

场景

消息队列

事务(transaction)

事务可以使多个指令都执行成功,或者都执行失败,保存完整性。

multi 、exec、discard

multi:开启一个事务,后续执行的指令将都保存到事务队列中(开启事务)

exec:执行事务队列中所有的指令(提交事务)

discard:取消事务,清除事务列表中所有指令(取消事务)

watch、unwatch

watch:监听一个或者多个key,如果在执行一个事务时,监听的 key 被修改,那么中止执行会失败

unwatch:取消所有监听

常用应用

数据缓存

网站中很多数据频繁被使用,如果每次都查询数据库会非常 的慢,所以我们一般是先从数据库中把要使用的数据取出来然后保存到 Redis 中,然后设置一个个过期,比如30分钟,然后我们程序就从 Redis 中取出数据,如果数据过期,再重新 读取数据库,再保存到 Redis 中。

哪些可以缓存:网站中的数据只要不是实时都要变化的就都可以缓存,比如:销量排行(每天统计一次,统计完之后保存到 Redis 中)、精品推荐、最周最活跃的用户、最新的10篇等等 这些首屏的数据。

消息队列

消息队列可以对系统 异步解耦,以提高网站性能。

消费者程序

php.ini 中有一个配置项 default_socket_timeout ,这一项的意思:连接一个服务器的超时时间,默认是60秒,如果我们希望在队列中没有数据时一直保存连接等待那么就需要设置这一项值为-1。

使用 brpop 指令阻塞读取队列。

// 设置 socket 永不超时
ini_set('default_socket_timeout', -1);

// 循环监听一个列表
while(true)
{
// 从队列中取数据,设置为永久不超时
$data = $redis->brpop(队列名称, 0);
// 处理数据
....... (比如发邮件)
}

生产者程序

使用 lpush 向队列中加入数据。

$redis->lpush(队列名称, 数据);

简单实例

  1. 消费者程序

这个程序负责在后台一直运行,它负责发邮件,所以项目中当有功能需要发邮件时,直接把邮件内容放到 user_emails 列表中即可,然后这个后台程序会负责发邮件。

<?php
// 连接永不断开
ini_set('default_socket_timeout', -1);

// 消费者程序
// 模块发邮件
function sendMail($content)
{
sleep(2);
file_put_contents('./mails/'.time(), $content);
}

// 监听消息队列,从队列中取出消息然后发邮件
require('./vendor/autoload.php');

// 连接 Redis
$client = new \Predis\Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);

echo "发邮件程序已经启动...\n";

while(true)
{
// 从队列的右侧取出消息,如果队列中没有消息就阻塞
$message = $client->brpop('user_emails', 0);
echo "收到消息,准备发邮件...\n";
// 取出消息之后就发邮件
sendMail($message[1]);
echo "邮件发送成功,开始监听下一个!\n";
}



  1. 生产者程序

当有程序需要发邮件时,只需要把消息放到队列中然后消费者程序会在后台发给我们发邮件。

<?php
// 负责注册

$user = 'tom';
$password = '12123';

$sql = 'insert into USER ...';

echo '数据库成功!';
echo '注册成功!';

// 监听消息队列,从队列中取出消息然后发邮件
require('./vendor/autoload.php');

// 连接 Redis
$client = new \Predis\Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
// 出消息队列中放消息
$client->lpush('user_emails', '注册成功!欢迎加入!');

echo '完成 !!!';

总结:

  1. 消息队列用来解决系统的异步解耦
  2. 消息队列软件 :Redis、kafka、Rabbmq等等
  3. 消费者程序是在后台不间断的执行的
  4. 消费者程序在后台需要使用多进程不间断的执行(在PHP 里要使用 Swoole 或者 wokerman 来实现多进程)
  5. 在分布式系统和集群时使用

在连接 Redis 时可以使用 select 来切换数据库。

数据库主要用来在内存中把数据隔离开。

配置

复制

主从服务器。

image.png

搭建主从服务器只需要修改配置文件:然后开启这个服务器就是从服务器:

masterip:主服务器 IP 地址

masterport:主服务器端口号

image.png

安全

Redis 一般都是运行服务器内网中,不会暴露在外网,所以一般不需要设置密码,都是内部程序自己使用,如果要设置密码可以使用下面这一项:

image.png

一旦设置了密码,客户端在连接 Redis 之后,必须先使用 auth 密码 指令进行登录 ,登录成功之后才能执行其他的操作。

并发限制

Redis 默认允许 10000 个客户端同时连接,如果要修改也可以: image.png

快照(RDB)

为了防止 Redis 中的数据丢失,所以会定期向硬盘保存数据。

image.png

每次将内存中所有的数据都备份到硬盘 。

AOF

AOF:把数据从内存备份到硬盘。

只把新的数据追回到硬盘。

是否开启:

appendonly on

什么时候触发写硬盘:

always:一修改Redis 同时就写硬盘,(好处:数据不会丢失,缺点:性能慢)

everysec:每秒写硬盘一次,(好处:性能稍微好些,缺点:有可能丢失一秒的数据)

appendfsync always
appendfsync everysec
appendfsync no

扩展:集群方案。

使用不写硬盘数据有可能丢失,如果写硬盘性能会慢,所以这样一个方案。

搭建主从服务器,主服务器完全不写硬盘,从服务器写硬盘。

慢日志

MySQL 中也有慢日志。 image.png 慢日志:把执行慢的语句保存到一个日志文件中,然后我们就可以通过查看日志知道网站中哪些功能拖慢网站,然后就可以针对性的优化。

可以配置一个时间 ,当一个指令执行时间超过这个时间 就会记录到慢日志中。

· 18 min read
sado

1.Websocket

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

image-20200415173221790

2.socket

Socket实际上是对TCP/IP协议的封装,本身并不是协议,而是一个调用接口(API.

Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口.

比如create、listen、connect、accept、send、read和write.

3.简介

WebSocket 是一种网络通信协议.

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了.

服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,属于服务器推送技术的一种。

`长链接`

# 服务器推送技术
1 Webpush
2 HTTP server push
3 Pushlet
4 Long polling
5 Flash XMLSocket relays
6 Reliable Group Data Delivery (RGDD)
7 Push notification

4.与HTTP的对比

image-20200415174047471

5.特点


1)建立在 TCP 协议之上,服务器端的实现比较容易。

2)与 HTTP 协议有着良好的兼容性。默认端口也是`80``443`,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

3)数据格式比较轻量,性能开销小,通信高效。

4)可以发送文本,也可以发送二进制数据。

5)没有`同源限制`,客户端可以与任意服务器通信。

6)协议标识符是`ws`(如果加密,则为wss),服务器网址就是 URL (scheme)
ws://example.com:80/uri
wss://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 Socket
python -> socket,
go -> gorilla/websocket
node -> socket.io / socket.io -client
# 调试
https://jsbin.com/?js,console,output
# webSocket.readyState
readyState属性返回实例对象的当前状态,共有四种。
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拓展, 初始化上下文环境

image-20200417173904872

执行php脚本

image-20200417173929936

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, 简称 UDS
SOCK_STREAM : 数据大用 (有粘包问题)
SOCK_DGRAM : 数据小用 (64k)

# sysvmsg :
Linux 提供的消息队列,这种 IPC 方式通过一个文件名来作为 key 进行通讯,这种方式非常的不灵活,实际项目使用的并不多

5.安装

扩展冲突

xdebug
phptrace
aop
molten
xhprof
phalcon(Swoole 协程无法运行在 phalcon 框架中)

必须

php-7.1 或更高版本
gcc-4.8 或更高版本
make
autoconf

1.源码安装

#1. 下载 swoole 源码
https://github.com/swoole/swoole-src/releases
http://pecl.php.net/package/swoole
http://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.1
PHP 包管理器 Composer
PCRE
PHP 扩展 Swoole,版本 >=4.3
额外扩展:PDO、Redis

#冲突部分
Xdebug
Xhprof
Blackfire
Zend
trace
Uopz

2.安装方式

1.docker

docker run -p 18306:18306 --name swoft swoft/swoft

2.docker-compose

git clone https://github.com/swoft-cloud/swoft
cd swoft
docker-compose up

# sserver下面有以下文件配置
docker-compose.yml
docker-sync -> rsync , native_osx

3.composer

composer create-project swoft/swoft Swoft

4.手动安装

git clone https://github.com/swoft-cloud/swoft
cd swoft
composer install
cp .env.example .env

5.swoftcli

# 支持从不同模板项目中快速创建一个干净的 Swoft 应用
php swoftcli.phar create:app --type full Swoft-Full
php swoftcli.phar create:app --type ws Swoft-WebSocket
php 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

# swoftcli
swoftcli -h
swoftcli run -c ws:start
swoftcli 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\Bean

name 定义 Bean 别名,缺省默认类名
scope 注入 Bean 类型,默认单例,Scope::SINGLETON/Scope::PROTOTYPE(每次创建)
ref 指定引用 Bean ,用于定义在接口上面,指定使用哪个接口实现。
# 注入
@Inject
命名空间:\Swoft\Bean\Annotation\Inject

name 定义属性注入的bean名称,缺省属性自动类型名称
# 操作
App::getBean("name");
ApplicationContext::getBean('name');
BeanFactory::getBean('name');
BeanFactory::hasBean("name");