网站做配置文件的作用,安康seo,福州营销网站建设老品牌,亚马逊官方网站怎么做大家好#xff0c;我是码农先森。
在这个大家都崇尚高性能的时代#xff0c;程序员的谈笑间句句都离不开高性能#xff0c;仿佛嘴角边不挂着「高性能」三个字都会显得自己很 Low#xff0c;其中众所皆知的 Nginx 就是高性能的代表。有些朋友可能连什么是高性能都不一定理解…大家好我是码农先森。
在这个大家都崇尚高性能的时代程序员的谈笑间句句都离不开高性能仿佛嘴角边不挂着「高性能」三个字都会显得自己很 Low其中众所皆知的 Nginx 就是高性能的代表。有些朋友可能连什么是高性能都不一定理解其实高性能就是单位时间内能处理更多的客户端请求如果要问具体能处理多少请求这个就要结合软硬件条件来评估了感兴趣的朋友可以在定性的条件下使用压力测试工具对自己的程序进行测试。
大家都知道 PHP-FPM 是 PHP 的进程管理器每一次来自 Ngixn 转发过来的客户端请求都会交由一个 PHP-FPM 子进程进行处理在同一时刻一个子进程只能处理一个客户端请求如果想要同一时刻能处理多个请求那么就需要启动多个子进程当遇到秒杀抢购这种瞬间大量请求的场景时PHP-FPM 对请求处理的模式显然无法满足需求。在这种情况下我们只能使用 Workerman 或 Swoole 这种 PHP 的高性能通信框架来解决类似特殊场景下的并发问题不过这次我分享的内容主要是 Workerman。
如标题所提到的 Workerman 立命之本那什么是其立命之本呢我认为是 IO 多路复用的 epoll 利器epoll 是高性能程序的根基解决 C10K 问题的尚方宝剑。接下来我会剖析 epoll 在 Workerman 源码中的使用不过在这之前我们需要先学习下 PHP 中如何将 Socket 与 Event 结合使用的案例。这里的 Event 可以理解为是对 epoll 的高度封装底层采用的就是 epoll 利器。
看了这段代码有助于你理解 Workerman 源码因为这段代码就是提炼了 Workerman 对事件循环的实现原理。stream_socket_server 函数把创建、绑定、监听一并实现了让代码显得更加简洁不像之前的 socket_create、socket_bind、socket_listen 搞了三个步骤略显繁琐。因为使用了事件循环所以需要对 Socket 设置成非阻塞模式只有当有读或写的通知时才会调用相应的回调函数。还有一点需要额外注意的需要针对客户端 Socket 创建的 Event 需要定义成静态变量或全局变量不然无法持久化连接到内存会造成客户端无法建立连接传输数据我看到网上很多人都踩到了这个坑上。最后启动事件循环 EventLoop 自此开启了 Socket 监听和事件循环双操作。
?php// 创建 TCP 服务器套接字
$server stream_socket_server(tcp://0.0.0.0:8080, $errno, $error);
echo 正在监听 8080 端口.... PHP_EOL; // 设置为非阻塞在 $server 对象没有数据可以读取或写入时不会阻塞其执行
stream_set_blocking($server, 0);// 创建事件基础对象
$event_base new EventBase();// 建立事件监听服务端 Socket 可读事件
$event new Event($event_base, $server, Event::READ | Event::PERSIST, function ($server) use ($event_base) {// 获取新的连接由于设置了非阻塞模式那么这里即使没有新的连接也不会一直阻塞在这$client stream_socket_accept($server, 0);if ($client) {echo 客户端( . $client . )连接建立. PHP_EOL; // 针对客户端过来的连接也要设置成非阻塞模式stream_set_blocking($client, 0);// 客户端连接创建监听可读事件// 这里需要特别注意客户端事件需要定义成静态变量或全局变量static $client_event;$client_event new Event($event_base, $client, Event::READ | Event::PERSIST, function ($client) {// 从客户端连接中读取数据每次只读取 1024 字节数据$buffer fread($client, 1024);// 如果没有读取到数据或者客户端已经不是资源句柄则关闭客户端连接if ($buffer false || !is_resource($client)) {// 关闭客户端连接fclose($client);echo 客户端( . $client . )连接关闭 . PHP_EOL; return;}echo 收到客户端( . $client . )数据: $buffer . PHP_EOL;// 回写数据给客户端$msg HTTP/1.0 200 OK\r\nContent-Length: 10\r\n\r\nServerOK\r\n;fwrite($client, $msg);}, $client);$client_event-add();}
}, $server);// 添加事件
$event-add();// 执行事件循环
$event_base-loop();使用 CURL 工具访问 http://127.0.0.1:8080 便能正确返回结果 ServerOK 这表明事件循环可以进入正常运行状态。
[manongsenroot php_event]$ curl -i http://127.0.0.1:8080
HTTP/1.0 200 OK
Content-Length: 10ServerOK看懂了上面那段代码之后接下来的内容就会更顺利了。下面这段代码是引至 Workerman 的示例通过 Worker 类构造了一个 HTTP 服务。onMessage 参数定义了一个回调函数当有事件通知时会回调到此处之后就是用户自行实现后续的处理逻辑了。runAll 函数会整体启动整个服务其中包括进程的创建、事件的循环等。
?php// 引用 Worker 类
use Workerman\Worker;// 自动加载 Composer
require_once __DIR__ . /vendor/autoload.php;// 定义 HTTP 服务并监听 8081 端口
$http_worker new Worker(http://0.0.0.0:8081);// 定义回调函数
$http_worker-onMessage function ($connection, $request) {//$request-get();//$request-post();//$request-header();//$request-cookie();//$request-session();//$request-uri();//$request-path();//$request-method();// Send data to client$connection-send(Hello World);
};// 启动服务
Worker::runAll();在 Worker.php 文件的 2367 行使用 stream_socket_server 函数创建了服务端 Socket 并且绑定、监听了 8081 端口。
// workerman/Worker.php:2367
$this-_mainSocket \stream_socket_server($local_socket, $errno, $errmsg, $flags, $this-_context);在 Worker.php 文件的 2394 行使用 stream_set_blocking 函数将 服务端 Socket 设置成非阻塞模式。
// workerman/Worker.php:2394
\stream_set_blocking($this-_mainSocket, false);在 Worker.php 文件的 2417 行将服务端的 _mainSocket 添加到事件循序中并且设置回调函数为 acceptConnection 。
// workerman/Worker.php:2417
static::$globalEvent-add($this-_mainSocket, EventInterface::EV_READ, array($this, acceptConnection));在 Worker.php 文件的 2561 行使用 stream_socket_accept 接收到来自客户端的连接 $new_socket 其中这个操作是在 acceptConnection 回到函数中所进行的。
// workerman/Worker.php:2561
$new_socket \stream_socket_accept($socket, 0, $remote_address);在 TcpConnection.php 文件的 285 行使用 stream_set_blocking 函数将客户端的 _socket 设置成非阻塞模式这里的 _socket 和上面的 new_socket 是同一个。
// workerman/Connection/TcpConnection.php:285
\stream_set_blocking($this-_socket, 0);在 TcpConnection.php 文件的 290 行将客户端的 _socket 添加到事件循环中并且设置其的回调函数为 baseRead 。
// workerman/Connection/TcpConnection.php:290
Worker::$globalEvent-add($this-_socket, EventInterface::EV_READ, array($this, baseRead));在 Worker.php 文件的 1638 行启动事件循环。
// workerman/Worker.php:1638
static::$globalEvent-loop();启动事件循环后当有客户端连接时便可以读取数据了。因此在 TcpConnection.php 文件的 583 行使用 fread 函数读取客户端 $socket 的数据。
// workerman/Connection/TcpConnection.php:583
$buffer \fread($socket, self::READ_BUFFER_SIZE);在 TcpConnection.php 文件的 647 行使用 parser::decode 函数将上面读取到的 buffer 数据解析成 $request 对象还有 $this 表示的是 $connection 对象这个 $this-onMessage 是最开始用户自定义的回调函数。最终通过 call_user_func 函数将 c o n n e c t i o n 、 connection、 connection、request 参数回调到 onMessage 方法。
// workerman/Connection/TcpConnection.php:647
\call_user_func($this-onMessage, $this, $parser::decode($one_request_buffer, $this));最后我们使用 CURL 工具调用一下 http://127.0.0.1:8081 通过返回的数据可以看出正确的回调到了 onMessage 函数。
[manongsenroot workerman]$ curl -i http://127.0.0.1:8081
HTTP/1.1 200 OK
Server: workerman
Connection: keep-alive
Content-Type: text/html;charsetutf-8
Content-Length: 13Hello World看到这里相信你已经对 Workerman 源码中的事件循环有些了解了如果有时间最好能够实践下最开始的那段案例代码然后再结合着看 Workerman 的源代码会颇有收获。Workerman 的高性能是站在了巨人 epoll 的肩膀上来实现没有了 epoll 则啥也不是。这里再重申一下 PHP 中的 Event 是对 epoll 的封装epoll 是 Linux 的底层技术。我们在日常的编程中是不会直接接触到 epoll 的最后回归一下主题 epoll 技术才是 Workerman 的立命之本。
感谢大家阅读个人观点仅供参考欢迎在评论区发表不同观点。 欢迎关注、分享、点赞、收藏、在看我是微信公众号「码农先森」作者。