刚刚 up 了解了下
swoole
并安装了环境接下来 up 迫不及待的了解下server
与client
一、server
###那么什么是server呢 ?
顾名思义就是服务端。up 平时接触比较多的无非就是 nginx
和 apache
。作为 webServer
,二者都是通过监听某端口对外提供服务,swoole
的 server
也不例外同样需要绑定端口,同时能够提供给客户端相关的服务。
创建一个server对象
创建 server
的步骤
- 实例化
Server
对象 - 设置运行时参数
- 注册事件回调函数
- 启动服务器
示例
server
的创建,只需要绑定要监听的 ip
和端口
,如果ip指定为 127.0.0.1
,则表示客户端只能位于本机才能连接,其他计算机无法连接,如果需要所有的客户端都能连接则可以设置 0.0.0.0
端口这里指定为 9501
,可以通过 netstat
查看下该端口是否被占用。如果该端口被占用,可更改为其他端口,如 9502
,9503
等。
配置
在享受 swoole
的 server
之前,同样 up 需要配置一下 server
,比如调几个人来提供服务(几个进程),以及是否是后台执行(守护进程)等等一些其它的配置。
这个就需要我们做一些配置了,但是并非像 fpm
直接在文件内配置,我们可以在 server
创建后,通过 set
方法指定配置项。
同时,这个配置项也有很多,比如说我们可以指定日志文件记录具体的错误信息等等,你都可以在官网的手册上寻找有哪些配置项。
这里 up 首要说明一下 worker
进程数的配置,因为 swoole
是多进程的异步服务器所以需要设置工作进程数,提升服务器性能。
我们可以指定配置项 worker_num
等于某个正整数。这个正整数设置多少合适,即我要开多少个worker
进程处理们的业务逻辑才好呢我?官方建议我们设置为 CPU
核数的 1-4倍
。因为我们开的进程越多,内存的占用也就更多,进程间切换也就需要耗费更多的资源。up 这里设置开启两个 worker进程
。默认该参数的值等于你机器的CPU核数。
事件驱动
swoole
另外一个比较吸引人的地方,就是swoole_server
是事件驱动的。我们在使用的过程中不需要关注底层是怎么实现的,底层是 C
写的php
只是做了个传递的作用,所以只需要对底层相应的动作注册相应的回调,在回调函数中处理业务逻辑即可。
什么意思呢?up 举个例子:
你启动了一个server
,当客户端连接的时候(触发事件),你不需要关心它是怎么连接的,你就单纯的注册一个connect
函数,做一些连接后的业务处理即可(执行业务)。类似于js的事件监听,比如触发了click
事件,就会执行相应的闭包。
####Swoole监听的事件
up来看看几种常见的事件回调。
参数 $serv
是我们一开始创建的 swoole_server
对象,参数 $fd
是唯一标识,用于区分不同的客户端,同时该参数是 1-1600万之间
可以复用的整数。简单解释下复用:假设现在客户端1、2、3处于连接中,客户端4要连接的话 $fd
就是4,但是不巧的是客户端3连接不稳定,断掉了,客户端4连接到 server
的话,$fd
就是3,这样看的话。1600W
个连接够用吗?单机业务百万连接,已经是很厉害了,不用担心
监听客户端数据发送,触发回调
worker
进程内触发的。第三个参数 $fromId
指的是哪一个 reactor
线程,具体 up 会在学习多进程模型当中详细分析这里先忽略。。直接看第四个参数,这个参数就是服务端接受到的数据,注意是字符串或者二进制内容,注意我们在 Receive
回调内,调用了$serv
的send
方法,我们可以使用send
方法,向client
发起通知。
监听客户端关闭,触发回调
这个很简单,当客户端关闭,或者服务端主动关闭连接的时候会触发。到此呢,up基本上已经搭建到了一个高性能的 server
。当然,非常简单,下面 up 只需要调用 start
方法启动 server
即可。
由于swoole_server
只能运行在CLI
模式下,所以不要试图通过浏览器进行访问,有听说过apache
是基于nginx
运行的吗,大家地位相同,只能配合,没有上下关系,up在命令行下面执行,自行去连接,这里不补充基础知识,tese.php
就是刚刚服务器的方法
up 平时执行完一个指令,执行完就结束了,但是现在的情况正好相反,当前程序一直处于执行中的状态,并没有退出终端。同时因为swoole
的server
是常驻内存运行的,所以如果修改了代码,需要ctrl c
中断,重新运行才行。在up在第一步初始化server
时填写了ip和端口,就是说 server
现在正在监听9501
端口提供服务。当前终端暂时不动,up新开一个终端,查看端口发现确实如此。
server
启动好了能干什么呢?常见的网络编程模式都是client-server
的,也就是说up还需要模拟一个客户端与之交互。
二、同步client跟异步client
默认的swoole
的server
是可以提供tcp/udp
socket请求协议
,然后根据请求数据,执行相应的逻辑
在PHP
中,我们常用socket函数
来创建TCP连接
,用CURL库
来创建Http连接
。同样的,为了简化操作,Swoole
也提供了同样的Client类
用于实现客户端的功能,并且增加了异步非阻塞的模式,让用户在客户端也能使用事件循环。
swoole_client
的构造函数
从上图可以看出一共三个参数
- 第一个参数:
SWOOLE_SOCK_TCP
创建tcp socket
SWOOLE_SOCK_TCP6
创建tcp ipv6 socket
SWOOLE_SOCK_UDP
创建udp socket
SWOOLE_SOCK_UDP6
创建udp ipv6 socket
- 第二个参数表示是同步还是异步
SWOOLE_SOCK_SYNC
同步客户端SWOOLE_SOCK_ASYNC
异步客户端 - 第三个参数(暂不了解)
用于长连接的
Key
,默认使用IP:PORT
作为key
。相同key
的连接会被复用
新建一个同步客户端
同步client
是同步阻塞的。一整套connect->send()->rev()->close()
是同步进行的。如果需要大量的数据处理,后台不能在规定的时间内返回数据会导致接收超时,并且因为是同步执行所以需要等待后台数据的返回。
同步异步概念
swoole
是既支持全异步,也支持同步,同步跟异步的概念,我们需要了解
同步与异步的重点在消息通知的方式上,也就是调用结果通知的方式。
- 同步: 当一个同步调用发出去后,调用者要一直等待调用结果的通知后,才能进行后续的执行。
- 异步:当一个异步调用发出去后,调用者不能立即得到调用结果的返回。
生活中的例子:
- 同步买奶茶:小明点单交钱,然后等着拿奶茶;
- 异步买奶茶:小明点单交钱,店员给小明一个小票,等小明奶茶做好了,再来取。
异步客户端
当设定 swoole_client
为异步模式后,swoole_client
就不能使用recv
方法了,而需要通过on
方法提供指定的回调函数,然后在回调函数当中处理,也就是小明等待奶茶做好了异步通知,消息发送跟接收并不是同步运行的。
心跳检测
4.3被移除但是可能有其它的设备或者是语言是长连接,并且用来演示心跳检测
心跳是什么?
顾名思义,心跳是判断一个事物生还是死的一个标准,在swoole
里,心跳是指用来判断一个连接是正常还是断开的
为什么要心跳?
心跳的目的其实是通过判断客户端是否存活,从而回收fd
,系统为什么要回收fd
,因为fd
资源是有限的,所以必需重复利用 心跳作用主要有两个:
- 客户端定时给服务端发送点数据,防止连接由于长时间没有通讯而被某些节点的防火墙关闭导致连接断开的情况。
- 服务端可以通过心跳来判断客户端是否在线,如果客户端在规定时间内没有发来任何数据,就认为客户端下线。这样可以检测到客户端由于极端情况(断电、断网等)下线的事件。
心跳在swoole里的实现?
swoole
会在主进程独立起一个心跳线程,通过定时轮询所有的连接,来判断连接的生死,所以swoole
的心跳不会堵塞任何业务逻辑。
设置完成了之后,你会发现设置了定时检测之后,如果客户端没在规定的时间之内发送数据就会关闭。
heartbeat_check_interval
: 服务器定时检测在线列表的时间heartbeat_idle_time
: 连接最大的空闲时间 (如果最后一个心跳包的时间与当前时间之差超过这个值,则认为该连接失效)
配置建议
建议 heartbeat_idle_time
为heartbeat_check_interval
的两倍多一点。
这个两倍是为了进行容错,允许丢一个包而多一点是考虑到网络的延时。
为什么需要心跳包?客户端如何维持心跳?
在从客户端到服务器的一条巨大的链路中会经过无数的路由器,其中每一个路由器都有可能会有检测到多少秒时间内无数据包则自动关闭连接的这种节能机制,为了让这个可能会出现的节能机制失效,客户端可以设置一个定时器,每隔固定的时间都发一个随机字符的一字节的数据包,通常我们把这种数据包就叫做心跳包。
实战案例
客户端设备发送一个请求,服务端接收,根据业务逻辑的需求服务端A需要发送一个请求到服务端B获取数据,再返回给服务端A,服务端A返回给客户端A。 服务端A既是客户端也是服务器,服务端A要发送请求到服务端B,然后服务端B返回消息给服务端A
client
代码语言:javascript复制$client = new swoole_client(SWOOLE_SOCK_TCP);
$client->connect('127.0.0.1',9800) || exit('连接失败');
$client->send("我是客户端1");
$client->send("我是客户端2");
sleep(2);
$client->send("我是客户端3");
$res = $client->recv();
$data = json_decode($res,1);
var_dump($data);//接收消息
//关闭
$client->close();
server1
代码语言:javascript复制echo "运行脚本1".PHP_EOL;
$server = new SwooleServer('0.0.0.0',9800);
$server->set([
'worker_num' => 1,//设置进程数
// 'heartbeat_idle_time' =>10,//连接最大的空闲时间
// 'heartbeat_check_interval' => 3, //服务器定时检查
'open_eof_check' => true, //开启EOF结束协议
'package_eof' => "rn", //设置结尾字符
'open_eof_split' => true //自动拆分
]);
//监听事件
$server->on('connect',function ($sev,$fd){
echo "新的连接进入:".$fd.PHP_EOL;
});
//消息发送过来
$server->on('receive',function (swoole_server $server, int $fd,$reactor_id,$data){
echo "消息发送过来:".$fd.PHP_EOL;
echo "消息内容:".$data.PHP_EOL;
echo "请求服务端2:".PHP_EOL;
$client = new swoole_client(SWOOLE_SOCK_TCP);
if (!$client->connect('127.0.0.1', 9900))
{
exit("connect failed. Error: {$client->errCode}n");
}
$client->send("我是服务端1");
$data = array(
'server1' => '我是服务端1',
'server2' => $client->recv(),
);
$client->close();
$server->send($fd,json_encode($data));
});
//消息关闭
$server->on('close',function (){
echo "消息关闭".PHP_EOL;
});
//服务开启
echo "开启服务".PHP_EOL;
$server->start();
server2
代码语言:javascript复制echo "运行脚本2".PHP_EOL;
$server = new SwooleServer('0.0.0.0',9900);
$server->set([
'worker_num' => 1,//设置进程数
// 'heartbeat_idle_time' =>10,//连接最大的空闲时间
// 'heartbeat_check_interval' => 3, //服务器定时检查
'open_eof_check' => true, //开启EOF结束协议
'package_eof' => "rn", //设置结尾字符
'open_eof_split' => true //自动拆分
]);
//监听事件
$server->on('connect',function ($sev,$fd){
echo "新的连接进入:".$fd.PHP_EOL;
});
//消息发送过来
$server->on('receive',function (swoole_server $server, int $fd,$reactor_id,$data){
echo "消息发送过来:".$fd.PHP_EOL;
echo "消息内容:".$data.PHP_EOL;
$server->send($fd,'我是服务端2');
});
//消息关闭
$server->on('close',function (){
echo "消息关闭".PHP_EOL;
});
//服务开启
echo "开启服务".PHP_EOL;
$server->start();