限时 5折! 详情

swoole基础-swoole初识之异步多线程服务器

12050 0 0

从上一节评论中可以看出还是有不少人非常期待能够早早的掌握swoole的。的确,熟练运用php+swoole,提高phper们的可不仅仅是技术能力。

后期我们更新的进度要加快了,可能有些人注意到我们在swoole概述一文中所述的文章list时常在更新。这也是我本人在整理swoole资料的过程中,为了更好的安排一些知识点做出的微调。如果你有好的意见也欢迎给我留言。

今天我们的重点还是蛮多的,大概有下面四个:

  1. 同步和异步概念的区分
  2. socket编程模型
  3. 初步认识swoole的server
  4. 用swoole搭建一个异步多线程的服务器

同步和异步

我们在IO模型一文中解释过同步和异步的概念,并非是web开发模式下ajax这种异步的请求。在常见的web开发模式下,我们所碰到的几乎都是同步模式。

为什么这么说?无论是fpm还是httpd,同一时间内一个进程只能处理一个请求,如果当前进程处于繁忙,后面的请求也只能继续等待有新的空闲进程。如果负载稍微上去了些,我们还可以调整fpm和httpd的进程数,即增加worker进程的数量。但是,在服务器资源有限的情况下,随着worker进程数量的递增,系统消耗的资源也会逐步增加,直至over。

swoole是既支持全异步,也支持同步,同步模式我们后面结合fpm再说。

IO模型一文,我们也可以感受到异步很强大。为什么喃?

我们举一个一名老师指导多名学生解题的场景。

同步模式下,当该老师在给某学生A指导题目的时候,嘴里可能一边嘟囔着“这个要这么写...”,话没说完,另一个学生B喊道“老师快来,我这碰到难题了,快过来指导指导”。

“等会,没看见在忙吗?”

然后学生B只能乖乖的等老师给A解答完之后才可以。

异步模式就不同啦,老师在给A指导的同时,B又屁颠屁颠的喊着“老师老师...”,这个时候老师态度上就360大转弯,“来了来了”,顺便跟A说了“你先理解下我刚才说的,等会好了叫我”,然后呢,后面的剧情可能就是这样的

  1. A解答完毕跟老师说“谢谢”,B喊老师
  2. B先喊老师,A进入B一开始的状态,B解答完毕跟老师说“谢谢”
  3. 剧情很多,自己没事想吧

又重温了下什么是同步和异步的概念,禁止混淆。

socket编程

socket是什么?

在大部分的书本或者网络文章中,你都能找到一个解释:套接字,是属于应用层和传输层之间的抽象层。真想把发明这词的人拉出来暴打一顿,这也太抽象了。

socket即套接字,是用来与另一个进程进行跨网络通信的文件,说是“文件”,也很好理解哈,因为在linux中一切都可以理解为“文件”。比如客户端可以借助socket与服务器之间建立连接。你也可以把socket理解为一组函数库,它确实也就是一堆函数。

我们知道,常见的网络应用都是基于Client-Server模型的。即一个服务器进程和多个客户端进程组合而成,如果你还理解为是一台电脑对另一台电脑,可以回去把进程/线程一文再看看了。在Client-Server模型中,服务器管理某种资源,并且通过对它管理的资源进行操作来为客户端提供服务。

那Client和Server又如何实现通信呢?这就要利用socket一系列的函数实现了。

基于套接字接口的网络应用的描述,用下面这张图来理解就好。

217f4d714f-socket-client-server.png

大致可以描述为:服务器创建一个socket,绑定ip和端口,在该端口处进行监听,然后通过accept函数阻塞。当有新的客户端连接进来时,server接收客户端数据并处理数据,然后返回给客户端,客户端关闭连接,server关闭该客户端,一次连接交互完成。

初识server

友情提示,本篇作为学习swoole的起步,至关重要,初次接触的swoole的同学建议句句斟酌,有任何不懂的或者建议下面留言。

server,顾名思义,就是服务器。我们平时接触比较多的无非就是nginx和apache。作为webServer,二者都是通过监听某端口对外提供服务。

下面我们来创建一个简单的server。

1、创建一个server对象

server的创建,只需要绑定要监听的ip和端口,如果ip指定为127.0.0.1,则表示客户端只能位于本机才能连接,其他计算机无法连接。

$serv = new swoole_server('127.0.0.1', 9501);

端口这里指定为9501,可以通过netstat查看下该端口是否被占用。如果该端口被占用,可更改为其他端口,如9502,9503等。

2、配置

swoole的运行模式,默认是多进程模式,这根fpm有点像。怎么体现多进程呢?要开启几个进程才合适呢?

这个就需要我们做一些配置了,但是并非像fpm直接在文件内配置,我们可以在server创建后,通过$serv->set(array())指定配置项。当然,这个配置项也有很多,比如说我们可以指定日志文件记录具体的错误信息等等,你都可以在官网的手册上寻找有哪些配置项,我们也会在贯穿swoole的同时讲解一部分常用的配置项。

这里我们首要说明一下worker进程数的配置。

我们可以指定配置项worker_num等于某个正整数。这个正整数设置多少合适,即我要开多少个worker进程处理我们的业务逻辑才好呢?官方建议我们设置为CPU核数的1-4倍。因为我们开的进程越多,内存的占用也就更多,进程间切换也就需要耗费更多的资源。我们这里设置开启两个worker进程。默认该参数的值等于你机器的CPU核数。

$serv->set([
    'worker_num' => 2,
]);

3、swoole另外一个比较吸引人的地方,就是swoole_server是事件驱动的。我们在使用的过程中不需要关注底层怎么样怎么样,只需要对底层相应的动作注册相应的回调,在回调函数中处理我们的业务逻辑即可。

什么意思呢?我举个例子:

你启动了一个server,当客户端连接的时候,你不需要关心它是怎么连接的,你就单纯的注册一个connect函数,做一些连接后的处理即可。再比如server收到了client传递的数据,你用关心复杂的网络是怎么接受到的吗?不用,你只需要注册一个receive回调,处理数据就这么多。

让我们来看看几种常见的事件回调。

// 有新的客户端连接时,worker进程内会触发该回调
$serv->on('Connect', function ($serv, $fd) {
    echo "new client connected." . PHP_EOL;
});

参数$serv是我们一开始创建的swoole_server对象,

参数$fd是唯一标识,用于区分不同的客户端,同时该参数是1-1600万之间可以复用的整数。

我来解释下复用:假设现在客户端1、2、3处于连接中,客户端4要连接的话$fd就是4,但是不巧的是客户端3连接不稳定,断掉了,客户端4连接到server的话,$fd就是3,这样看的话,实际可能远不止1600W。那1600W个连接够用吗?我的妈呀,你丫单个业务先做到160W再考虑这个问题吧...

// server接收到客户端的数据后,worker进程内触发该回调
$serv->on('Receive', function ($serv, $fd, $fromId, $data) {
    // 收到数据后发送给客户端
    $serv->send($fd, 'Server '. $data);
});

Receive回调的前两个参数就不说了,刚说完。

上面说到的两个回调,都强调了是在worker进程内触发的。第三个参数$fromId指的是哪一个reactor线程,具体我们会在多进程模型一文中详细分析,先忽略吧。

我们看第四个参数,这个参数就是服务端接受到的数据,注意是字符串或者二进制内容哦,后面我们只谈字符串,不用怕。

注意我们在Receive回调内,调用了$serv的send方法,我们可以使用send方法,向client发起通知。

// 客户端断开连接或者server主动关闭连接时 worker进程内调用
$serv->on('Close', function ($serv, $fd) {
    echo "Client close." . PHP_EOL;
});

注意哦,当客户端与服务端的连接关闭的时候就会调用close回调,有些新手可能习惯性的会在close回调中继续调用$serv->close($fd),人都关闭了才去调用这个方法,你再调用是不是想找事?

到此呢,我们基本上已经搭建到了一个高性能的server。“我什么都没做,这就完啦?好没劲啊”

是的,非常简单,下面我们只需要调用start方法启动server即可。

// 启动server
$serv->start();

如此,便开启了一个server服务。

由于swoole_server只能运行在CLI模式下,所以不要试图通过浏览器进行访问,这样是无效的。不信的可以试试。

我们在命令行下面执行

php server.php

回车后,神奇的事情发生了,命令行下面只显示了类似这样的一句话:

PHP Deprecated: Comments starting with '#' are deprecated in /etc/php.d/swoole.ini on line 1 in Unknown on line 0

这句话不重要,我们先忽略掉。

随后继续回车随便输入点什么都没有效果,感觉当前终端卡住了有木有?

我们平时执行完一个指令,执行完就结束了,但是现在的情况正好相反,当前程序一直处于执行中的状态,并没有退出终端。退出状态一般为当前终端的执行权交给了终端,即可用在终端下进行其他操作。

还记得我们第一步初始化server所填写的ip和端口吗,也就是说server现在正在监听9501端口提供服务。

当前终端暂时不动,我们新开一个终端,看看是不是这样。

$ netstat -an| grep 9501
tcp4       0      0  127.0.0.1.9501         *.*                    LISTEN

发现本地的9501端口正在被监听对不对?server启动好了能干什么呢?常见的网络编程模式都是client-server的,也就是说我们还需要模拟一个客户端与之交互。

关于客户端,我们可以先通过telnet模拟

323aec7602-server-client.png

上图中左侧是开启的server窗口,右侧是我们用telnet模拟client的结果。

从结果中可以看出,客户端输入xxx,服务端就会直接返回 Server xxx,这正是我们在Receive回调方法中调用$serv->send方法发送给客户端的数据。而且在server启动的窗口下,也有我们在connect回调打印的信息。

在整个过程中,swoole server提供了类似web服务器的功能,监听端口,做出响应。

此外,swoole还提供了一套对socket客户端的封装,而且啊而且,这个要重点说一下,同步阻塞的swoole_client可以用于php-fpm或者apache环境。

swoole的大部分模块都只能运行在CLI模式下,像我们刚刚在cli下启动的server。但是对于面向web的应用怎么办?所以,swoole_client是我们与服务端交互的一个重要方法,先笔记记下。

下面我们用swoole_client来模拟下客户端。

新建一个Client.php文件。

代码如下:

// 创建一个同步阻塞的tcp socket
// 第一个参数是表示socket的类型,有下面四种类型选择,这里选则tcp socket就好
/**
 *     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 异步客户端
 */
$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC);

// 随后建立连接,连接失败直接退出并打印错误码
$client->connect('127.0.0.1', 9501) || exit("connect failed. Error: {$client->errCode}\n");
// 向服务端发送数据
$client->send("hello server.");
// 从服务端接收数据
$response = $client->recv();
// 输出接受到的数据
echo $response . PHP_EOL;
// 关闭连接
$client->close();

我们看到,客户端无非就是创建一个socket对象,然后指定ip和端口,连接server,随后向server发送了一段数据,而后接收server的数据并输出,最后关闭连接。

看下模拟结果

1512185656-server-client2.png

注意到无论是server还是client,都是在CLI下执行的。

从模拟的结果中我们也可以清晰的看到client与server交互的整个过程。

但是,相信很多人都会有疑问,尤其是phper,server和客户端都这么玩,完全看不到实际应用啊。先慢慢练习吧,我们这才刚打响与swoole之间的战役。

总结,自己总结吧,满满的都是笔记。client与server交互的过程千万要动手练练,所谓熟能生巧,练习的过程中有任何疑问的下面留言,我们共同交流。

补充,文中serverclient的练习源码,已上传github,可点击链接参考。

另,server的关闭,手动执行Ctrl+C即退出。