单进程服务器简陋版:

<?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. java调用c++函数的简单笔记

    java使用jni调用c++动态库函数. 步骤: 1.编写java测试代码如下: public class CallNativeDemo { native void func(); native do ...

  2. 读书笔记《PHP与MySQL程序设计》一

    第1章 PHP概述 1.1  历史(PHP4.PHP5.PHP5.3.PHP6[未发布]) 1.2 一般语言特性(实用性.强大功能.可选择性.成本[开源]) 第2章 环境配置 2.1 安装的前提条件( ...

  3. React问题集序

    问题描述 antd version: 2.7.4 OS and its version: windows7 Browser and its version: Chromium 55.0.2883.87 ...

  4. 【Java学习笔记之十】Java中循环语句foreach使用总结及foreach写法失效的问题

    foreach语句使用总结 增强for(part1:part2){part3}; part2中是一个数组对象,或者是带有泛性的集合. part1定义了一个局部变量,这个局部变量的类型与part2中的对 ...

  5. HDU 1005 Number Sequence【多解,暴力打表,鸽巢原理】

    Number Sequence Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)T ...

  6. hdu_1034(模拟题)

    很久没有打模拟题了,再次总结一下模拟题的做法: 仔细分析题意,弄清楚过程 理清楚模拟步骤,严格按照步骤编写代码 添加中间输出测试每步结果 虽然这是一个很简单的水题,但是没有松哥帮忙还是卡了很久,因为没 ...

  7. c#简单操作MongoDB_2.4

    一.MongoDB的安装 MongoDb在windows下的安装与以auth方式启用服务 二.下载驱动 使用nuget搜索“mongodb”,下载“MongoDB.Driver”(这是官方推荐的一个驱 ...

  8. 互联网App应用程序测试流程及测试总结

    互联网App应用程序测试流程及测试总结 1. APP测试基本流程 1.1流程图 仍然为测试环境 Pass 1.2测试周期 测试周期可按项目的开发周期来确定测试时间,一般测试时间为两三周(即15个工作日 ...

  9. 安装win8+Ubuntu14.04双系统的经验总结

    当时查资料,很多人推荐了easyBCD直接安装ubuntu,但是在我的笔记本上行不通.我的笔记本是Lenovo V480+win8正版系统.这是因为我的笔记本的引导结构是EFI,而不是MBR.我的方法 ...

  10. .27-浅析webpack源码之事件流make(2)

    上一节跑到了NormalModuleFactory模块,调用了原型方法create后,依次触发了before-rsolve.factory.resolver事件流,这节从resolver事件流开始讲. ...