2019-4-5 模仿swoole封装单进程阻塞http_server

本文介绍了一个简单的单进程阻塞HTTP服务器实现,通过PHP语言模拟了服务器接收和响应HTTP请求的过程。该服务器能处理GET请求,并展示了如何通过回调函数进行响应。

上午简单封装一个单进程的阻塞的http服务器。
单进程相当于一个客服,阻塞相当于一个电话号码只有一个电话机。
接听电话后,其他人再给你打电话,会提示“正在通话中,请您稍后再拨”。

我们把socket系列函数通过电话机来帮助理解。

  1. socket_create() ---- 购买个电话机
  2. socket_bind() ----- 绑定电话号
  3. socket_accept() ---- 等待连接-电话响起-拿起电话 建立双方连接
  4. socket_read() ---- 对方说了句“喂 你好”
  5. socket_write() ----- 你回复 “你好"
  6. socket_close() ---- 挂掉电话
  7. socket_accept() — 等待连接…
  8. 重复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测试

阻塞式的不用压测了,手动访问都能卡住一个服务器。。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值