网络模型
服务器的网络服务模型
- 单进程阻塞式i/o模型,一次只能处理一个链接,不支持多个长链接同时处理
- 多进程方式,服务器收到请求,子进程处理请求,一个进程处理一个请求,或者是,先开辟一个进程池,不够用再动态开辟进程(预派生子进程),php-fpm也是这样
- pstree 树形查看进程
- fastcgi将请求发送给master,master分配给worker执行,一个worker只处理一个请求,处理完请求销毁
- 资源开销大,大量请求时性能下降
- 多线程
- 单进程io阻塞复用,一个进程或者线程可以处理n个请求,性能好,稳定性差
- 多进程master-working io复用
单进程阻塞
- 创建一个socket,绑定端口,监听端口
- while循环,阻塞在sccept操作上,等待下一个请求
- 利用fread读取客户端socket当中数据,fwrite向客户端发送响应
简单实现
//index.php
<?php
require "worker.php";
//启动监听
$worker = new Workder("tcp://0.0.0.0:8080");
//接收到了客户端发送的消息,回调执行, $message为前端发送的数据,包括头
$worker->onMessage = function($fd,$connnect,$message){
//业务
//$connect->send($fd,$message); //同一次请求不能send 两次
//返回
$connect->send($fd,"hello world");
}
$worker->runAll(); //启动
worker.php
<?php
class Worker{
public $onMessage; //绑定一个消息触发的回调
private $_mainSocket; //保存socket服务端资源
//构造函数,接受监听端口等
public function __construct($addr){
$this->_mainSocket = stream_socket_server($addr); //创建socket,绑定端口并监听相当于socket_create socket_bind和socket_listen
}
//服务启动
public function runAll(){
$this->listen();
}
//监听服务端发送的请求
protected function listen(){
while(true){
//阻塞获取客户端请求
$clientSocket = stream_socket_accept($this->_mainSocket);
//从socket中读取数据,有就读取,没有就等待,得到的时文件描述符,这里是阻塞操作,等待客户端进入,程序睡眠,等到有新的请求,会被系统唤醒
//var_dump((int)$clientSocket) 实际上获取到的式客户端文件资源描述符
//从客户端的socket读取用户数据
$message = fread($clientSocket,65535); //如果是浏览器请求,获取到的式请求头和请求体
//如果有用户自定义的onMessage函数,就去执行
//var_dump($this->onMessage);
if(is_callable($this->onMessage)) {
call_user_func($this->onMessage, $clientSocket, $this, $message);
}
//fwrite($clientSocket,'返回的内容');
}
}
//发送消息 向那个client返回,返回的内容
public function send($fd,$message){
//增加http头
$http_resonse = "HTTP/1.1 200 OK\r\n";
$http_resonse .= "Content-Type: text/html;charset=UTF-8\r\n";
$http_resonse .= "Connection: keep-alive\r\n";
$http_resonse .= "Server: php socket server\r\n";
$http_resonse .= "Content-length: ".strlen($message)."\r\n\r\n";
$http_resonse .= $message;
fwrite($fd,$http_resonse);
//fclose($fd); //关闭连接,单进程的话,处理完请求直接关闭,并发处理很快,但是不适用于长连接情况,就是不进行连接的关闭
}
}
压测
ab -n2 -c2 -k
//-n请求数 -c客户端数 -k keep alive,长连接
单进程io复用 select/poll epoll
一个进程连接多个请求\
- select 方式,遍历所有的socket,如果某个达到了可读条件就进行拿出来执行,最多监听1024个
- 用户空间保存所有的连接
- 内核空间遍历,得到可读的
- 转交给用户空间处理
- poll方式,和select方式相似,只是没有监听个数限制,1w长连接,就要遍历1w个,即使有很多都没有信息,浪费cpu
- epoll,没有描述符限制,也没有遍历,当描述符状态改变时,触发事件回调,事件监听机制
- 需要安装libevent库(linux),php需要安装event扩展,pecl install event,才能使用epoll
- 使用swoole的event,这个比原生的更好理解
- 将每个fd和回调添加到内核中,内核监听网卡等状态,当有请求数据是针对某个fd时,就将其执行read的回调放到就绪队列中,等待执行
- fd在内核中以树状(红黑树)存储,方便查找
epoll
//Worker_epoll.php
<?php
class Worker
{
public $onMessage; //绑定一个消息触发的回调
private $_mainSocket; //保存socket服务端资源
//构造函数,接受监听端口等
public function __construct($addr)
{
$this->_mainSocket = stream_socket_server($addr); //创建socket,绑定端口并监听相当于socket_create socket_bind和socket_listen
}
//服务启动
public function runAll()
{
$this->listen();
}
//监听服务端发送的请求
protected function listen()
{
//异步监听
swoole_event_add($this->_mainSocket, function ($fd) { //添加一个socket到epoll的事件监听列表中
//参数为接收到的文件描述符
//var_dump($fd);
$client_socket = stream_socket_accept($fd); //获取客户端内容
//添加一个客户端socket到epoll事件监听,当socket状态法神改变的时候执行回调
//服务器端的socket是个监听,一般不会发生状态改变,客户端的这个socket接受数据,会有状态的改变
swoole_event_add($client_socket, function ($fd) {
if (feof($fd) || !is_resource($fd)) {
//如果到了末尾或者不是资源类型,说明客户端已经关闭,就不进行后续处理
swoole_event_del($fd); //删除事件监听
fclose($fd);
return false;
//或者触发onclose事件,
}
$message = fread($fd, 65535);
if (is_callable($this->onMessage)) {
call_user_func($this->onMessage, $fd, $this, $message);
}
});
});
}
//发送消息 向那个client返回,返回的内容
public function send($fd, $message)
{
//增加http头
$http_resonse = "HTTP/1.1 200 OK\r\n";
$http_resonse .= "Content-Type: text/html;charset=UTF-8\r\n";
$http_resonse .= "Connection: keep-alive\r\n";
$http_resonse .= "Server: php socket server\r\n";
$http_resonse .= "Content-length: " . strlen($message) . "\r\n\r\n";
$http_resonse .= $message;
fwrite($fd, $http_resonse);
//fclose($fd); //关闭连接,单进程的话,处理完请求直接关闭,并发处理很快,但是不适用于长连接情况,就是不进行连接的关闭
}
}
//启动监听
$worker = new Worker("tcp://0.0.0.0:8080");
//接收到了客户端发送的消息,回调执行, $message为前端发送的数据,包括头
$worker->onMessage = function($fd,$connnect,$message){
//业务
//$connect->send($fd,$message); //同一次请求不能send 两次
//返回
$connnect->send($fd,"hello world");
};
$worker->runAll(); //启动
网友评论