单进程服务器简陋版:

<?php
/**
* Single http server.
*
* Access http://127.0.0.1:8081
*
* @license Apache-2.0
* @author farwish
*/ $s_socket_uri = 'tcp://0.0.0.0:8081';
$s_socket = stream_socket_server($s_socket_uri, $errno, $errstr) OR
trigger_error("Failed to create socket: $s_socket_uri, Err($errno) $errstr", E_USER_ERROR); while(1)
{
while($connection = @stream_socket_accept($s_socket, 60, $peer))
{
echo "Connected with $peer. Request info...\n"; $client_request = "";
// Read until double \r
while( !preg_match('/\r?\n\r?\n/', $client_request) )
{
$client_request .= fread($connection, 1024);
} if (!$client_request)
{
trigger_error("Client request is empty!");
}
$headers = "HTTP/1.1 200 OK\r\n"
."Server: nginx\r\n"
."Content-Type: text/html; charset=utf-8\r\n"
."\r\n";
$body = "<h1>hello world</h1><br><br>";
if ((int) fwrite($connection, $headers . $body) < 1) {
trigger_error("Write to socket failed!");
}
fclose($connection);
}
}

HTTP 底层基于 TCP,所以 socket 地址指定为 tcp 协议没有问题;stream_socket_server 功能相当于执行了 socket => bind => listen,stream_socket_accept 阻塞等待 client 连接,并设置了超时时间,默认的 timeout 时间使用在 php.ini 中设置。

注意这里的错误抑制符@,抑制 accept 超时情况产生的 PHP Warning,如果用到 stream_select 也需要加错误抑制符 @ 来避免中断信号引起的 PHP Warning。

HTTP 响应报文格式包含 响应状态码、响应首部、响应主体(如果有的话),响应首部每行以 \r\n 结尾,响应头部结束单独一行 \r\n 结尾,后面就是响应主体了,响应头部加响应主体以 fwrite 写入 socket 连接,fclose 关闭连接。

注意:上面的简陋版既没有设置 socket 上下文选项,也没有使用 I/O 复用,更不是多进程的,只是作为请求响应的演示。

较为严谨的HTTP协议处理版:

        $method             = '';
$url = '';
$protocol_version = ''; $request_header = [];
$content_type = 'text/html; charset=utf-8';
$content_length = 0;
$request_body = '';
$end_of_header = false; // @see http://php.net/manual/en/function.fread.php
$buffer = fread($connection, 8192); if (false !== $buffer) { // Http request format check.
if (false !== strstr($buffer, "\r\n")) {
$list = explode("\r\n", $buffer);
} if ($list) {
foreach ($list as $line) {
if ($end_of_header) {
if (strlen($line) === $content_length) {
$request_body = $line;
} else {
throw new \Exception("Content-Length {$content_length} not match request body length " . strlen($line) . "\n");
}
break;
} if ( empty($line) ) {
$end_of_header = true;
} else {
// Header.
//
if (false === strstr($line, ': ')) {
$array = explode(' ', $line); // Request line.
if (count($array) === 3) {
$method = $array[0];
$url = $array[1];
$protocol_version = $array[2];
}
} else {
$array = explode(': ', $line); // Request header.
list ($key, $value) = $array;
$request_header[$key] = $value; if ( strtolower($key) === strtolower('Content-type') ) {
$content_type = $value;
} // Have request body.
if ($key === 'Content-Length') {
$content_length = $value;
}
}
}
}
}
} // No request body, show buffer from read.
$response_body = $request_body ?: $buffer;
$response_header = "HTTP/1.1 200 OK\r\n";
$response_header .= "Content-type: {$content_type}\r\n";
if (empty($content_length) && (strlen($response_body) > 0)) {
$response_header .= "Content-Length: " . strlen($response_body) . "\r\n";
}
foreach ($request_header as $k => $v) {
$response_header .= "{$k}: {$v}\r\n";
}
$response_header .= "\r\n";
fwrite($connection, $response_header . $response_body);
fclose($connection);

以上程序属于 accept 之后的处理步骤,外层逻辑这里已省略,也适用在多进程服务器中子进程的处理部分。

这里还是用 fread 统一读取数据,设置读取的长度 8192(fread 的默认也是8192),$buffer 含有头部信息和数据,按 \r\n 分解成数组元素再处理,处理方式按照 HTTP 请求报文格式。

首先是请求行,如  GET /index HTTP/1.1    三部分以 “空格” 隔开,行尾以 \r\n 结束。

其次是报文首部,如 Content-Type: text/html  键值中间以 “冒号” 加 “空格” 隔开,行尾以 \r\n 结束。

请求报文头部以一行 \r\n 结束。

最后是请求主体(如果有的话),如果报文首部中有 Content-Length 值,就说明有请求主体。

响应数据按照 响应头部 加 响应主体,写入 socket 连接,最后关闭连接。

HTTP协议格式示意:

小结:

完整步骤 1.创建服务端套接字 2.接受请求 3.解释请求行确定请求的特定文件 4.从文件系统中获取请求文件 5.创建被请求的文件组成的HTTP响应报文,发送给客户端 6.如果请求的文件不存在,服务端返回 404 Not Found 报文。

无重逻辑(字符输出)的场景中,ab 100并发1000次访问压测,对比传统调优后的 Nginx + PHP,PHP 实现的多进程非阻塞 I/O HTTP Server 的 QPS 性能稍高,有一个理由可以解释:它不需要 Nginx 来转发请求。

PHP实现的 HTTP Server 有很多细节需要自身处理,不同因素会对处理请求的性能产生直接影响以及面临某些场景下才能产生的BUG,所以稳定性上需要经受更多考验。

Reference: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Overview

Link:http://www.cnblogs.com/farwish/p/8418969.html

[HTTP] PHP 实现 HTTP Server 原理的更多相关文章

  1. MXNet之ps-lite及parameter server原理

    MXNet之ps-lite及parameter server原理 ps-lite框架是DMLC组自行实现的parameter server通信框架,是DMLC其他项目的核心,例如其深度学习框架MXNE ...

  2. Spark job server原理初探

    Spark job server是一个基于Spark的服务系统,提供了管理SparkJob,context,jar的RestFul接口. 专注标注原文链接 http://www.cnblogs.com ...

  3. Tomcat Server 原理

    构成: 1.server代表整个catalina serverlet容器 2.service:由一个或多个connector以及一个共享的engine处理引擎组成 3.connector 在指定端口上 ...

  4. kubernetes核心原理之API Server原理分析

    kubernetes API Server的核心功能是提供了Kubernetes各类资源对象(Pod,RC,Service等)的增删改查及Watch等HTTP Rest接口,成为集群内各个功能模块之间 ...

  5. [原创]安全系列之端口敲门服务(Port Knocking for Ubuntu 14.04 Server)

    Port Knocking for Ubuntu 14.04 Server OS:ubuntu 14.04 server 原理简单分析: 端口敲门服务,即:knockd服务.该服务通过动态的添加ipt ...

  6. FQ原理

    笔者在nginx反向代理篇讲了正向代理和反向代理的区别,今天着重讲其中的FQ是实现原理. 一.普遍的两种方式 1.vpn vpn它将客户端的IP数据报经过加密和二次封装后转发出去,客户端通过vpn上网 ...

  7. kubernetes 核心原理

    3.1 K8s API Server 原理分析 K8s API server核心提供对各种资源对象的增.删.改.查以及Watch等HTTPRest接口,是集群内各个模块之间数据交互和通信的中心枢纽,是 ...

  8. 028.核心组件-API Server

    一 Kubernetes API Server原理 1.1 API Server功能 Kubernetes API Server的核心功能是提供Kubernetes各类资源对象(如Pod.RC.Ser ...

  9. 2021升级版微服务教程6—Ribbon使用+原理+整合Nacos权重+实战优化 一篇搞定

    2021升级版SpringCloud教程从入门到实战精通「H版&alibaba&链路追踪&日志&事务&锁」 教程全目录「含视频」:https://gitee.c ...

随机推荐

  1. IE6的兼容性以及处理方法

    1. 当我们写了一段正确的代码,但是在不同浏览器下,产生一些不正确的解析,这叫做兼容性问题 2. 在IE6下 ,标准盒模型: width/height = content; 可视宽/高 = conte ...

  2. java使用poi自定义excel标题头并导出(springmvc+poi)

    项目使用的是jeecg开源框架(springmvc+spring+hibernate+......等)此代码仅供参考!如有更好的意见或建议可留言. 1 controller 层 /** * excel ...

  3. vue.js之数据传递和数据分发slot

    一.组件间的数据传递 1.父组件获取子组件的数据 *子组件把自己的数据,发送到父级 *vm.$emit(事件名,数据); *v-on: @ 示例用法:当点击send按钮的时候,"111&qu ...

  4. Java与算法之(4) - 数字全排列

    全排列是指n个数(或其他字符)所有可能的排列顺序,例如1 2 3三个数字的全排列是 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1 那么问题来了,任意输入一个大于1的数字n,列 ...

  5. Java面向对象抽象类实例练习

    abstract class Animal { abstract void eat(); } class Cat extends Animal { void eat() { System.out.pr ...

  6. UVA 10382 - Watering Grass【贪心+区间覆盖问题+高精度】

    UVa 10382 - Watering Grass n sprinklers are installed in a horizontal strip of grass l meters long a ...

  7. Oracle忘记密码如何重

    ---恢复内容开始--- 昨天安装Oracle11g R2的时候给scott用户设置密码,当时没有显示而且还只以输入一次,可能密码输入错误,结果今天用scott用户登录果然密码不对,还好sys和sys ...

  8. post请求测试

    也可以用下图来指定测试参数

  9. DOM解析原理示意

    DOM解析原理示意  

  10. robots书写说明:

    "User-Agent"搜索引擎的名字. 针对所有搜索引擎写法:User-Agent: *,针对百度搜索引擎写法:User-Agent:Baiduspider "Allo ...