上午简单封装一个单进程的阻塞的http服务器。
单进程相当于一个客服,阻塞相当于一个电话号码只有一个电话机。
接听电话后,其他人再给你打电话,会提示“正在通话中,请您稍后再拨”。
我们把socket系列函数通过电话机来帮助理解。
- socket_create() ---- 购买个电话机
- socket_bind() ----- 绑定电话号
- socket_accept() ---- 等待连接-电话响起-拿起电话 建立双方连接
- socket_read() ---- 对方说了句“喂 你好”
- socket_write() ----- 你回复 “你好"
- socket_close() ---- 挂掉电话
- socket_accept() — 等待连接…
- 重复3-6
发生阻塞状态在accept(),即每次只能处理一个连接,挂掉电话后,才能继续接受连接。
request类从workman源码上扒的,缩略为只接受get,有兴趣可直接看workman源码。
<?php
class Request
{
public $header=array();
public $server=array();
public $request=array();
public $get=array();
public $post=array();
public $cookie='';
public $files=array();
public $raw_data=array();
public function get($param){
return $this->get[$param]?$this->get[$param]:'';
}
public function __construct($content)
{
$this->server = array(
'request_method' => '',
'request_uri' => '',
'server_protocol' => '',
'query_string' => '',
'server_name' => '',
'host' => '',
'user_agent' => '',
'accept' => '',
'accept_language' => '',
'accept_encoding' => '',
'cookie' => '',
'connection' => '',
'remote_addr' => '',
'remote_port' => '0',
'request_time' => time()
);
// 分离请求头 请求体
list($http_header, $http_body) = explode("\r\n\r\n", $content, 2);
$header_data = explode("\r\n", $http_header);
// GET /abc?a=1 HTTP/1.1
list($this->server['request_method'], $this->server['request_uri'], $this->server['server_protocol']) = explode(' ',
$header_data[0]);
$this->server['query_string'] = parse_url($this->server['request_uri'], PHP_URL_QUERY);
if ($this->server['query_string']) {
parse_str($this->server['query_string'], $this->get);
} else {
$this->server['query_string'] = '';
}
unset($header_data[0]);
// 继续解析 其余都是key:value
foreach ($header_data as $content) {
if (empty($content)) {
continue; //略过空
}
list($key, $value)= explode(':', $content, 2);
$key = str_replace('-', '_', strtolower($key));
$value = trim($value);
$this->server[$key] = $value;
}
// REQUEST
$this->request = array_merge($this->get, $this->post);
}
}
<?php
class Response{
public $conn;
public $is_end_called=false;
public function __construct($conn)
{
$this->conn=$conn;
}
public function end($content=""){
$this->is_end_called=true;
$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($content)."\r\n\r\n";
$http_resonse .= $content;
fwrite($this->conn, $http_resonse);
}
}
httpserver
<?php
include 'lib/Response.php';
include 'lib/Request.php';
class Http_server
{
protected $socket=NULL; //server_socket
protected $clnt_sock=NULL; //client_socket
protected $onRequest=NULL; //回调
public $request=NULL; //request对象
public $response=NULL; //response对象
public function __construct($sock_addr){
$this->socket=stream_socket_server($sock_addr);
}
public function on($event,$closure)
{
if(empty($event) && is_callable($closure)){
echo "parameters error!".PHP_EOL;
exit;
}
$event=lcfirst($event);
switch ($event) {
case 'request':
$this->onRequest = $closure;
break;
default:
echo "invalid event!".PHP_EOL;
exit;
break;
}
}
public function start()
{
while(true)
{
$this->clnt_sock=$clnt_sock = stream_socket_accept($this->socket,3600);
if($this->clnt_sock == false){ //连接失败
continue;
}
$buf=fread($clnt_sock,2048);
if(empty($buf)){
fclose($clnt_sock);
}
$this->request = $request = new Request($buf);
$this->response =new Response($clnt_sock);
if($request->server['request_uri']=="/favicon.ico"){
continue; //屏蔽goole请求/favicon.ico
}
if(!empty($buf) && is_callable($this->onRequest)) //执行回调
{
call_user_func($this->onRequest,$this->request,$this->response);
}
if($this->response->is_end_called == 0){ //若回调中未调用end默认end
$this->response->end('you dont send anything! this is default content!');
}
$this->request = $this->response=NULL;
fclose($clnt_sock);
}
}
}
$http_server=new Http_server('tcp://0.0.0.0:6001');
$http_server->on('request',function($request,$response){
$end ='';
$end.='REQUEST全部数据:'.json_encode($request->server)."</br>";
$end.='请求方法:'.$request->server['request_method']."</br>";
$end.='请求资源路径:'.$request->server['request_uri']."</br>";
$end.='参数:'.$request->server['query_string']."</br>";
$end.='接收a参数:'.$request->get('a')."</br>";
$response->end($end);
});
$http_server->start();
测试

ab测试
阻塞式的不用压测了,手动访问都能卡住一个服务器。。
本文介绍了一个简单的单进程阻塞HTTP服务器实现,通过PHP语言模拟了服务器接收和响应HTTP请求的过程。该服务器能处理GET请求,并展示了如何通过回调函数进行响应。

被折叠的 条评论
为什么被折叠?



